@hanzo/build 0.2.41 → 0.2.42
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/lib/api/models.d.ts +15 -18
- package/lib/api/models.js +11 -41
- package/lib/automations.js +5 -9
- package/lib/customize/agents.js +3 -12
- package/lib/landing.js +8 -4
- package/lib/pick.d.ts +2 -0
- package/lib/pick.js +21 -0
- package/lib/run.js +8 -5
- package/lib/settings/code.js +4 -3
- package/package.json +4 -4
- package/lib/access.d.ts +0 -4
- package/lib/access.js +0 -16
package/lib/api/models.d.ts
CHANGED
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
import { type Target } from './call.ts';
|
|
2
|
-
export interface Model {
|
|
3
|
-
id: string;
|
|
4
|
-
label: string;
|
|
5
|
-
/** Listed and never chosen. */
|
|
6
|
-
disabled?: boolean;
|
|
7
|
-
hint?: string;
|
|
8
|
-
/** Where a person asks for access to a model they cannot call. */
|
|
9
|
-
request?: string;
|
|
10
|
-
}
|
|
11
|
-
export declare const ENSO = "enso";
|
|
12
|
-
/** A readable name for an id: `anthropic/claude-opus-5.5` → `Claude Opus 5.5`, `zen5-coder` → `Zen5 Coder`. */
|
|
13
|
-
export declare function label(id: string): string;
|
|
14
|
-
export declare function models(t: Target): Promise<Model[]>;
|
|
15
1
|
/**
|
|
16
|
-
* The models
|
|
17
|
-
*
|
|
18
|
-
*
|
|
2
|
+
* The models a run can be routed through, from the platform's own catalog.
|
|
3
|
+
*
|
|
4
|
+
* GET /v1/models → {data: [{id, class, family, …}]}
|
|
5
|
+
*
|
|
6
|
+
* Read by `@hanzo/ui/models` `parseModels`, so a model's class and family are
|
|
7
|
+
* the gateway's fields, never guessed from its id. `ENSO` is the router: it
|
|
8
|
+
* picks a model per step, which is why it is the default, and a run that names
|
|
9
|
+
* it names no model at all.
|
|
19
10
|
*/
|
|
20
|
-
|
|
11
|
+
import { type ModelCatalogEntry } from '@hanzo/ui/models/catalog';
|
|
12
|
+
import { type Target } from './call.ts';
|
|
13
|
+
export { ENSO } from '@hanzo/ui/models/catalog';
|
|
14
|
+
export type Model = ModelCatalogEntry;
|
|
15
|
+
export declare function models(t: Target): Promise<Model[]>;
|
|
16
|
+
/** The payer's plan usage, as `GET /v1/ai/limits` answers it: what a picker marks paused. */
|
|
17
|
+
export declare function limits(t: Target, signal?: AbortSignal): Promise<unknown>;
|
package/lib/api/models.js
CHANGED
|
@@ -1,50 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The models a run can be routed through, from the platform's own catalog.
|
|
3
3
|
*
|
|
4
|
-
* GET /v1/models → {data: [{id, …}]}
|
|
4
|
+
* GET /v1/models → {data: [{id, class, family, …}]}
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* @hanzo/ui's `RESEARCH`: listed so a person knows they exist, disabled so no
|
|
11
|
-
* run is ever started on one, with where to ask for access.
|
|
6
|
+
* Read by `@hanzo/ui/models` `parseModels`, so a model's class and family are
|
|
7
|
+
* the gateway's fields, never guessed from its id. `ENSO` is the router: it
|
|
8
|
+
* picks a model per step, which is why it is the default, and a run that names
|
|
9
|
+
* it names no model at all.
|
|
12
10
|
*/
|
|
13
|
-
import {
|
|
11
|
+
import { parseModels } from '@hanzo/ui/models/catalog';
|
|
14
12
|
import { call } from './call.js';
|
|
15
|
-
export
|
|
16
|
-
/** A readable name for an id: `anthropic/claude-opus-5.5` → `Claude Opus 5.5`, `zen5-coder` → `Zen5 Coder`. */
|
|
17
|
-
export function label(id) {
|
|
18
|
-
const tail = id.slice(id.lastIndexOf('/') + 1).replace(/:.*$/, '');
|
|
19
|
-
return tail
|
|
20
|
-
.split('-')
|
|
21
|
-
.filter(Boolean)
|
|
22
|
-
.map((w) => (/^\d/.test(w) ? w : w[0].toUpperCase() + w.slice(1)))
|
|
23
|
-
.join(' ');
|
|
24
|
-
}
|
|
13
|
+
export { ENSO } from '@hanzo/ui/models/catalog';
|
|
25
14
|
export async function models(t) {
|
|
26
|
-
|
|
27
|
-
const rows = Array.isArray(raw?.data) ? raw.data : [];
|
|
28
|
-
const ids = rows
|
|
29
|
-
.map((r) => (r && typeof r === 'object' && typeof r.id === 'string' ? r.id : ''))
|
|
30
|
-
.filter((id) => id && !id.endsWith(':batch'));
|
|
31
|
-
const out = [...new Set([ENSO, ...ids])];
|
|
32
|
-
return [
|
|
33
|
-
...out.map((id) => ({ id, label: label(id) })),
|
|
34
|
-
...RESEARCH.filter((m) => !out.includes(m.id)).map((m) => ({
|
|
35
|
-
id: m.id,
|
|
36
|
-
label: m.label ?? label(m.id),
|
|
37
|
-
disabled: true,
|
|
38
|
-
hint: 'Research preview',
|
|
39
|
-
request: m.request,
|
|
40
|
-
})),
|
|
41
|
-
];
|
|
15
|
+
return parseModels(await call(t, 'GET', '/v1/models'));
|
|
42
16
|
}
|
|
43
|
-
/**
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
* embedding, guard, rerank, speech or vision models, which hold no conversation.
|
|
47
|
-
*/
|
|
48
|
-
export function skus(list) {
|
|
49
|
-
return list.filter((m) => /^(enso|zen\d*|free)(-[a-z0-9]+)*$/.test(m.id) && !/-(embedding|guard|rerank|scribe|vl|voice)\b/.test(m.id));
|
|
17
|
+
/** The payer's plan usage, as `GET /v1/ai/limits` answers it: what a picker marks paused. */
|
|
18
|
+
export function limits(t, signal) {
|
|
19
|
+
return call(t, 'GET', '/v1/ai/limits', undefined, { signal });
|
|
50
20
|
}
|
package/lib/automations.js
CHANGED
|
@@ -15,13 +15,14 @@ import { SizableText, XStack, YStack } from '@hanzo/gui';
|
|
|
15
15
|
import { ChevronLeft, Plus, Workflow } from '@hanzogui/lucide-icons-2';
|
|
16
16
|
import { Button, Input, Switch, Textarea } from '@hanzo/ui';
|
|
17
17
|
import { StatusDot } from '@hanzo/ui/chat';
|
|
18
|
+
import { ModelPicker } from '@hanzo/ui/models';
|
|
18
19
|
import { ChipSelect } from '@hanzo/ui/product';
|
|
20
|
+
import { useLimits } from '@hanzo/ui/product/useLimits';
|
|
19
21
|
import { useEffect, useId, useMemo, useRef, useState } from 'react';
|
|
20
22
|
import { ago } from './ago.js';
|
|
21
|
-
import { Access } from './access.js';
|
|
22
23
|
import { automation, automations, changes, create, remove, runs, save, start, words } from './api/auto.js';
|
|
23
24
|
import { Refusal } from './api/call.js';
|
|
24
|
-
import { ENSO,
|
|
25
|
+
import { ENSO, limits as readLimits, models } from './api/models.js';
|
|
25
26
|
import { Choice, Confirm, Field, Line } from './customize/ui.js';
|
|
26
27
|
import { useRead } from './data.js';
|
|
27
28
|
import { useHost, useTarget } from './host.js';
|
|
@@ -166,6 +167,7 @@ export function Editor({ id }) {
|
|
|
166
167
|
const saved = useRead(signed && !fresh ? () => automation(t, id) : null, null, [t, signed, id, tick]);
|
|
167
168
|
const history = useRead(signed && !fresh ? () => runs(t, id) : null, [], [t, signed, id, beat]);
|
|
168
169
|
const catalog = useRead(signed ? () => models(t) : null, [], [t, signed]);
|
|
170
|
+
const { limits } = useLimits(signed ? (signal) => readLimits(t, signal) : null, t);
|
|
169
171
|
// `base` is what the form started from; a save sends what changed since, and only that.
|
|
170
172
|
const [base, setBase] = useState(BLANK);
|
|
171
173
|
const [d, setD] = useState(BLANK);
|
|
@@ -212,12 +214,6 @@ export function Editor({ id }) {
|
|
|
212
214
|
const set = (p) => setD((x) => ({ ...x, ...p }));
|
|
213
215
|
const setWhen = (p) => setD((x) => ({ ...x, schedule: { ...x.schedule, ...p } }));
|
|
214
216
|
const wrong = unready(d.schedule);
|
|
215
|
-
const items = useMemo(() => {
|
|
216
|
-
const list = skus(catalog.value).filter((m) => m.id !== ENSO);
|
|
217
|
-
const kept = d.model && !list.some((m) => m.id === d.model) ? [{ id: d.model, label: named(d.model) }] : [];
|
|
218
|
-
return [{ id: ENSO, label: 'Enso', hint: 'picks a model for each step' }, ...kept, ...list];
|
|
219
|
-
}, [catalog.value, d.model]);
|
|
220
|
-
const chosen = items.find((m) => m.id === (d.model ?? ENSO)) ?? items[0];
|
|
221
217
|
const zones = useMemo(() => {
|
|
222
218
|
try {
|
|
223
219
|
return ['UTC', ...Intl.supportedValuesOf('timeZone').filter((z) => z !== 'UTC')].map((z) => ({ id: z, label: z }));
|
|
@@ -285,7 +281,7 @@ export function Editor({ id }) {
|
|
|
285
281
|
? 'Name it, say what it should do, and when. It runs as you, in this organization.'
|
|
286
282
|
: a?.draft
|
|
287
283
|
? 'A draft: say what it should do and save it, and it runs.'
|
|
288
|
-
: [words(a.schedule), a.next && a.enabled ? `next ${local(a.next)}` : a.schedule.kind === 'manual' ? '' : 'off'].filter(Boolean).join(' · ') })] }), _jsx(Field, { label: "Name", children: _jsx(Input, { value: d.name, onChangeText: (v) => set({ name: v }), placeholder: "Morning briefing", "aria-label": "Automation name", autoFocus: fresh }) }), _jsx(Field, { label: "Instructions", hint: "What the agent does each run. It works in a Dev sandbox with this organization's connectors, MCP servers and skills.", children: _jsx(Textarea, { value: d.instructions, onChangeText: (v) => set({ instructions: v }), placeholder: "Summarize what needs my attention today across calendar, email and messages.", "aria-label": "Instructions", rows: 7 }) }), _jsx(Field, { label: "When it runs", hint: s.kind === 'cron' ? 'Five fields: minute, hour, day of month, month, day of week.' : undefined, children: _jsxs(XStack, { gap: "$2", flexWrap: "wrap", rowGap: "$2", items: "center", children: [_jsx(ChipSelect, { name: "When it runs", label: KINDS.find((k) => k.id === s.kind).label, chosen: KINDS.find((k) => k.id === s.kind), items: KINDS, onChange: (k) => set({ schedule: shaped(k.id, s) }), placement: "bottom-start", width: 200 }), s.kind === 'weekly' ? _jsx(ChipSelect, { name: "Day", label: DAYS.find((x) => x.id === s.day)?.label ?? 'Monday', chosen: DAYS.find((x) => x.id === s.day), items: DAYS, onChange: (x) => setWhen({ day: x.id }), placement: "bottom-start", width: 180 }) : null, s.kind === 'daily' || s.kind === 'weekdays' || s.kind === 'weekly' ? (_jsx(YStack, { width: 110, children: _jsx(Input, { value: s.at ?? '', onChangeText: (v) => setWhen({ at: v.trim() }), placeholder: "09:00", "aria-label": "Time of day", inputMode: "text" }) })) : null, s.kind === 'hourly' ? (_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(SizableText, { size: "$2", color: "$soft", children: "at minute" }), _jsx(YStack, { width: 70, children: _jsx(Input, { value: (s.at ?? '00:00').slice(3), onChangeText: (v) => setWhen({ at: `00:${v.replace(/\D/g, '').slice(0, 2)}` }), placeholder: "00", "aria-label": "Minute", inputMode: "numeric" }) })] })) : null, s.kind === 'cron' ? (_jsx(YStack, { width: 200, children: _jsx(Input, { value: s.cron ?? '', onChangeText: (v) => setWhen({ cron: v }), placeholder: "0 9 * * 1-5", "aria-label": "Cron", autoCapitalize: "none" }) })) : null, s.kind !== 'manual' ? _jsx(ChipSelect, { name: "Time zone", label: s.tz || 'UTC', chosen: { id: s.tz || 'UTC', label: s.tz || 'UTC' }, items: zones, onChange: (z) => setWhen({ tz: z.id }), placeholder: "Search zones\u2026", placement: "bottom-start", width: 260 }) : null] }) }), _jsx(Field, { label: "Model", children: _jsx(XStack, { children: _jsx(
|
|
284
|
+
: [words(a.schedule), a.next && a.enabled ? `next ${local(a.next)}` : a.schedule.kind === 'manual' ? '' : 'off'].filter(Boolean).join(' · ') })] }), _jsx(Field, { label: "Name", children: _jsx(Input, { value: d.name, onChangeText: (v) => set({ name: v }), placeholder: "Morning briefing", "aria-label": "Automation name", autoFocus: fresh }) }), _jsx(Field, { label: "Instructions", hint: "What the agent does each run. It works in a Dev sandbox with this organization's connectors, MCP servers and skills.", children: _jsx(Textarea, { value: d.instructions, onChangeText: (v) => set({ instructions: v }), placeholder: "Summarize what needs my attention today across calendar, email and messages.", "aria-label": "Instructions", rows: 7 }) }), _jsx(Field, { label: "When it runs", hint: s.kind === 'cron' ? 'Five fields: minute, hour, day of month, month, day of week.' : undefined, children: _jsxs(XStack, { gap: "$2", flexWrap: "wrap", rowGap: "$2", items: "center", children: [_jsx(ChipSelect, { name: "When it runs", label: KINDS.find((k) => k.id === s.kind).label, chosen: KINDS.find((k) => k.id === s.kind), items: KINDS, onChange: (k) => set({ schedule: shaped(k.id, s) }), placement: "bottom-start", width: 200 }), s.kind === 'weekly' ? _jsx(ChipSelect, { name: "Day", label: DAYS.find((x) => x.id === s.day)?.label ?? 'Monday', chosen: DAYS.find((x) => x.id === s.day), items: DAYS, onChange: (x) => setWhen({ day: x.id }), placement: "bottom-start", width: 180 }) : null, s.kind === 'daily' || s.kind === 'weekdays' || s.kind === 'weekly' ? (_jsx(YStack, { width: 110, children: _jsx(Input, { value: s.at ?? '', onChangeText: (v) => setWhen({ at: v.trim() }), placeholder: "09:00", "aria-label": "Time of day", inputMode: "text" }) })) : null, s.kind === 'hourly' ? (_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(SizableText, { size: "$2", color: "$soft", children: "at minute" }), _jsx(YStack, { width: 70, children: _jsx(Input, { value: (s.at ?? '00:00').slice(3), onChangeText: (v) => setWhen({ at: `00:${v.replace(/\D/g, '').slice(0, 2)}` }), placeholder: "00", "aria-label": "Minute", inputMode: "numeric" }) })] })) : null, s.kind === 'cron' ? (_jsx(YStack, { width: 200, children: _jsx(Input, { value: s.cron ?? '', onChangeText: (v) => setWhen({ cron: v }), placeholder: "0 9 * * 1-5", "aria-label": "Cron", autoCapitalize: "none" }) })) : null, s.kind !== 'manual' ? _jsx(ChipSelect, { name: "Time zone", label: s.tz || 'UTC', chosen: { id: s.tz || 'UTC', label: s.tz || 'UTC' }, items: zones, onChange: (z) => setWhen({ tz: z.id }), placeholder: "Search zones\u2026", placement: "bottom-start", width: 260 }) : null] }) }), _jsx(Field, { label: "Model", children: _jsx(XStack, { children: _jsx(ModelPicker, { size: "sm", name: "Model", models: catalog.value, scope: "chat", limits: limits, value: d.model ?? ENSO, onChange: (id) => set({ model: id === ENSO ? null : id }), loading: catalog.loading, error: catalog.error?.message ?? null }) }) }), _jsx(Field, { label: "Permissions", hint: d.permissions === 'auto' ? 'Works and uses connectors without stopping.' : 'Reads and researches, changes nothing, and ends with the actions it would take for you to approve.', children: _jsx(Choice, { label: "Permissions", value: d.permissions, options: PERMISSIONS, onChange: (p) => set({ permissions: p }) }) }), _jsxs(XStack, { items: "center", gap: "$3", children: [_jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$2", color: "$ink", children: "Email me when a run ends" }), _jsx(SizableText, { size: "$1", color: "$soft", children: "One line on how it went, with a link to the run." })] }), _jsx(Switch, { checked: d.notify, onCheckedChange: (v) => set({ notify: v }), "aria-label": "Email me when a run ends" })] }), _jsx(Line, { children: note || (dirty ? wrong : '') }), _jsxs(XStack, { gap: "$2", items: "center", children: [a ? (_jsx(Button, { size: "sm", variant: "ghost", disabled: Boolean(busy), onPress: () => setAsking(true), children: "Delete" })) : null, _jsx(XStack, { flex: 1 }), a && dirty ? (_jsx(Button, { size: "sm", variant: "ghost", disabled: Boolean(busy), onPress: () => {
|
|
289
285
|
setBase(drafted(a));
|
|
290
286
|
setD(drafted(a));
|
|
291
287
|
}, children: "Discard changes" })) : null, _jsx(Button, { size: "sm", disabled: Boolean(busy) || !dirty || !d.name.trim() || !d.instructions.trim() || Boolean(wrong), onPress: () => void keep(), children: busy === 'save' ? 'Saving…' : fresh ? 'Create' : 'Save' })] }), a ? (_jsxs(YStack, { gap: "$2", children: [_jsx(SizableText, { size: "$3", color: "$ink", children: "Runs" }), history.error && !history.value.length ? (_jsx(Line, { children: history.error.message })) : !history.value.length ? (_jsx(SizableText, { size: "$2", color: "$soft", children: history.loading ? 'Reading its runs…' : a.draft ? 'It runs once its instructions are saved.' : 'No runs yet. Run now starts one.' })) : (_jsx(YStack, { role: "list", borderWidth: 1, borderColor: "$borderColor", rounded: "$3", overflow: "hidden", "aria-label": "Run history", children: history.value.map((r, i) => (_jsxs(XStack, { role: "listitem", items: "flex-start", gap: "$3", px: "$3", py: "$2.5", borderTopWidth: i ? 1 : 0, borderColor: "$borderColor", children: [_jsx(YStack, { pt: "$1", children: _jsx(StatusDot, { status: DOT[r.status] ?? 'idle' }) }), _jsxs(YStack, { flex: 1, minW: 0, gap: "$0.5", children: [_jsx(SizableText, { size: "$2", color: "$ink", children: `${WORD[r.status] ?? r.status} · ${local(r.at)}` }), _jsx(SizableText, { size: "$1", color: "$soft", style: { whiteSpace: 'pre-wrap' }, children: r.summary || (live(r.status) ? 'Working…' : '') })] }), r.session ? (_jsx(Button, { size: "sm", variant: "ghost", onPress: () => host.go(r.session), "aria-label": `Open the Dev run of ${local(r.at)}`, children: "Open run" })) : null] }, r.id))) }))] })) : null] }), a ? (_jsx(Confirm, { what: a.name, says: "It stops running, and its schedule and its runs go with it. A run going now is stopped.", open: asking, onOpenChange: setAsking, onYes: async () => {
|
package/lib/customize/agents.js
CHANGED
|
@@ -13,17 +13,15 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
13
13
|
import { SizableText, XStack, YStack } from '@hanzo/gui';
|
|
14
14
|
import { Bot } from '@hanzogui/lucide-icons-2';
|
|
15
15
|
import { Button, Input, Switch, Textarea } from '@hanzo/ui';
|
|
16
|
-
import {
|
|
16
|
+
import { ModelPicker } from '@hanzo/ui/models';
|
|
17
17
|
import { useEffect, useMemo, useState } from 'react';
|
|
18
18
|
import { agents, ALL, create, draft, EMPTY, fromPreset, one, PERIODS, presets, remove, update } from '../api/agents.js';
|
|
19
|
-
import {
|
|
20
|
-
import { label as named, models } from '../api/models.js';
|
|
19
|
+
import { models } from '../api/models.js';
|
|
21
20
|
import { tools } from '../api/tools.js';
|
|
22
21
|
import { useRead } from '../data.js';
|
|
23
22
|
import { useHost, useTarget } from '../host.js';
|
|
24
23
|
import { Add, Choice, Confirm, Field, Grid, Line, Mark, matches, mono, Sheet, Soft, Tile, Visitor } from './ui.js';
|
|
25
24
|
/** The model a new agent runs on when none is chosen: the deployment's own default. */
|
|
26
|
-
const DEFAULT = 'default';
|
|
27
25
|
const money = (m) => `$${(m / 1_000_000).toFixed(m % 10_000 ? 4 : 2).replace(/\.?0+$/, '')}`;
|
|
28
26
|
export function Agents({ view, q, adding, onAdding, onView }) {
|
|
29
27
|
const host = useHost();
|
|
@@ -96,13 +94,6 @@ function Editor({ agent, start, onClose, onSaved, onDeleted, }) {
|
|
|
96
94
|
return [...kept, ...listed];
|
|
97
95
|
}, [on.value, d.tools]);
|
|
98
96
|
const shownTools = choices.filter((x) => matches(find, x.name, x.source, x.description));
|
|
99
|
-
const items = useMemo(() => {
|
|
100
|
-
const list = [{ id: DEFAULT, label: 'Default', hint: 'the deployment’s' }, ...catalog.value];
|
|
101
|
-
if (d.model && !list.some((m) => m.id === d.model))
|
|
102
|
-
list.splice(1, 0, { id: d.model, label: named(d.model) });
|
|
103
|
-
return agent ? list.filter((m) => m.id !== DEFAULT) : list;
|
|
104
|
-
}, [catalog.value, d.model, agent]);
|
|
105
|
-
const chosen = items.find((m) => m.id === (d.model || DEFAULT)) ?? null;
|
|
106
97
|
const save = async () => {
|
|
107
98
|
setWorking(true);
|
|
108
99
|
setNote('');
|
|
@@ -119,7 +110,7 @@ function Editor({ agent, start, onClose, onSaved, onDeleted, }) {
|
|
|
119
110
|
}
|
|
120
111
|
};
|
|
121
112
|
const pick = (name, yes) => set({ tools: yes ? [...d.tools, name] : d.tools.filter((n) => n !== name) });
|
|
122
|
-
return (_jsx(Sheet, { title: agent ? agent.name : 'New agent', open: true, onOpenChange: (o) => !o && onClose(), width: 680, children: agent && full.error && !full.value ? (_jsx(Soft, { children: full.error.message })) : !ready ? (_jsx(Soft, { children: "Reading 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 }), placeholder: "helper", "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:
|
|
113
|
+
return (_jsx(Sheet, { title: agent ? agent.name : 'New agent', open: true, onOpenChange: (o) => !o && onClose(), width: 680, children: agent && full.error && !full.value ? (_jsx(Soft, { children: full.error.message })) : !ready ? (_jsx(Soft, { children: "Reading 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 }), placeholder: "helper", "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?.message ?? 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: "Reading the tools that are on\u2026" })) : on.error && !on.value.length ? (_jsx(Soft, { children: on.error.message })) : !shownTools.length ? (_jsx(Soft, { children: choices.length ? 'No tool matches.' : 'No tool is on for your organization. Switch skills and connectors on 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 () => {
|
|
123
114
|
await remove(t, agent.id || agent.name);
|
|
124
115
|
onDeleted();
|
|
125
116
|
} })) : null] })) }));
|
package/lib/landing.js
CHANGED
|
@@ -18,13 +18,15 @@ import { Button } from '@hanzo/ui';
|
|
|
18
18
|
import { ModeSelect } from '@hanzo/ui/agents';
|
|
19
19
|
import { Composer } from '@hanzo/ui/chat';
|
|
20
20
|
import { BranchSelect, ChipSelect, FooterLink, HanzoMark, RepoSelect } from '@hanzo/ui/product';
|
|
21
|
+
import { ModelPicker } from '@hanzo/ui/models';
|
|
22
|
+
import { useLimits } from '@hanzo/ui/product/useLimits';
|
|
21
23
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
22
24
|
import { start, unhonoured } from './api/coding.js';
|
|
23
25
|
import { asRepo, chosen, codebases, one } from './api/codebases.js';
|
|
24
26
|
import { read, SETUP } from './api/environment.js';
|
|
25
27
|
import { SetupDialog } from './environment.js';
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
+
import { ENSO, limits as readLimits, models } from './api/models.js';
|
|
29
|
+
import { usePick } from './pick.js';
|
|
28
30
|
import { ready, SANDBOX } from './api/places.js';
|
|
29
31
|
import { isForge } from './choice.js';
|
|
30
32
|
import { useKept, usePlaces, useRead } from './data.js';
|
|
@@ -102,6 +104,8 @@ export function Landing({ onStarted }) {
|
|
|
102
104
|
}, []);
|
|
103
105
|
const places = usePlaces(t, signed);
|
|
104
106
|
const catalog = useRead(signed ? () => models(t) : null, [], [t, signed]);
|
|
107
|
+
const { limits } = useLimits(signed ? (signal) => readLimits(t, signal) : null, t);
|
|
108
|
+
const [model, pickModel] = usePick(kept.model, (id) => set({ model: id }), catalog.value);
|
|
105
109
|
// The chosen codebase's environment, so New can say when it has none yet.
|
|
106
110
|
const codebase = isForge(kept.repo) ? kept.repo.name : '';
|
|
107
111
|
const env = useRead(signed && codebase ? () => read(t, codebase) : null, null, [t, signed, codebase]);
|
|
@@ -146,7 +150,7 @@ export function Landing({ onStarted }) {
|
|
|
146
150
|
base: repo ? kept.branch : undefined,
|
|
147
151
|
targetId: place.id,
|
|
148
152
|
mode: kept.mode,
|
|
149
|
-
model:
|
|
153
|
+
model: model === ENSO ? undefined : model,
|
|
150
154
|
effort: kept.effort,
|
|
151
155
|
});
|
|
152
156
|
setDraft('');
|
|
@@ -209,7 +213,7 @@ export function Landing({ onStarted }) {
|
|
|
209
213
|
});
|
|
210
214
|
},
|
|
211
215
|
}, placeholder: "New project", disabled: !signed }, round), isForge(kept.repo) ? _jsx(BranchSelect, { value: branch, onChange: (b) => set({ branch: b }), load: loadBranches }) : null, _jsx(Files, { files: files, onFiles: setFiles })] }));
|
|
212
|
-
const foot = (_jsxs(XStack, { flex: 1, items: "center", gap: "$2", flexWrap: "wrap", rowGap: "$1", children: [_jsx(Attach, { files: files, onFiles: setFiles, onNote: setNote }), _jsx(Dictate, { onText: (said) => setDraft((d) => (d ? `${d} ${said}` : said)), onNote: setNote }), _jsx(ModeSelect, { modes: MODES, value: kept.mode, onChange: (m) => set({ mode: m }), bg: "transparent", minH: 24, px: "$1.5", self: "center" }), _jsx(XStack, { flex: 1 }), _jsx(
|
|
216
|
+
const foot = (_jsxs(XStack, { flex: 1, items: "center", gap: "$2", flexWrap: "wrap", rowGap: "$1", children: [_jsx(Attach, { files: files, onFiles: setFiles, onNote: setNote }), _jsx(Dictate, { onText: (said) => setDraft((d) => (d ? `${d} ${said}` : said)), onNote: setNote }), _jsx(ModeSelect, { modes: MODES, value: kept.mode, onChange: (m) => set({ mode: m }), bg: "transparent", minH: 24, px: "$1.5", self: "center" }), _jsx(XStack, { flex: 1 }), _jsx(ModelPicker, { quiet: true, size: "sm", name: "Model", models: catalog.value, scope: "chat", limits: limits, value: model, onChange: pickModel, loading: catalog.loading, error: catalog.error?.message ?? null }), _jsx(ChipSelect, { quiet: true, name: "Effort", label: EFFORTS.find((e) => e.id === kept.effort)?.label ?? 'Medium', chosen: EFFORTS.find((e) => e.id === kept.effort) ?? null, items: EFFORTS, onChange: (e) => set({ effort: e.id }), placement: "top-end", width: 180 })] }));
|
|
213
217
|
return (_jsxs(YStack, { flex: 1, minH: 0, minW: 0, items: "center", justify: "center", px: "$4", pb: "$10", overflow: "scroll", children: [_jsxs(YStack, { width: "100%", maxW: COLUMN, gap: "$5", children: [_jsxs(XStack, { role: "heading", "aria-level": 1, justify: "center", items: "center", gap: "$3", flexWrap: "wrap", children: [_jsx(HanzoMark, { size: 26 }), _jsx(SizableText, { size: "$8", color: "$ink", style: { textAlign: 'center' }, children: prefs.callName ? `What’s up next, ${prefs.callName}?` : 'What’s up next?' })] }), _jsxs(YStack, { gap: "$2", children: [env.value && env.value.state !== 'ready' && !place.id ? (_jsxs(XStack, { items: "center", justify: "space-between", gap: "$3", px: "$3", py: "$2", rounded: "$10", borderWidth: 1, borderColor: "$borderColor", children: [_jsx(SizableText, { size: "$2", color: "$ink", flex: 1, minW: 0, children: env.value.state === 'proposed'
|
|
214
218
|
? `A proposed environment for ${codebase} is waiting for review.`
|
|
215
219
|
: `${codebase} has no environment yet, so every run starts it bare.` }), env.value.state === 'proposed' && env.value.session ? (_jsx(Button, { size: "sm", variant: "outline", onPress: () => host.go(env.value.session), children: "Review" })) : (_jsx(Button, { size: "sm", onPress: () => setOffering(true), children: "Set up environment" }))] })) : null, note || unhonoured(kept.mode, place.id) ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: note || unhonoured(kept.mode, place.id) })) : null, _jsx(Composer, { inline: true, ...pane, value: draft, onChange: setDraft, onSend: () => void send(), busy: busy, placeholder: "Describe a task or ask a question", label: "Describe a task or ask a question", head: head, foot: foot })] })] }), _jsx(Publish, { source: adding, onClose: () => setAdding(null) }), codebase ? (_jsx(SetupDialog, { repo: codebase, open: offering, onOpenChange: setOffering, onStart: setup, onSaved: () => {
|
package/lib/pick.d.ts
ADDED
package/lib/pick.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The model a surface starts its next run on, and how a pick changes it.
|
|
3
|
+
*
|
|
4
|
+
* A premium pick holds HERE, for the runs started from this screen, and is
|
|
5
|
+
* never kept as the default — the next New opens on what was kept, Enso unless
|
|
6
|
+
* the person kept another. Any other pick is kept. A kept premium model (from
|
|
7
|
+
* before this rule) is not a default either, so Enso stands in for it.
|
|
8
|
+
*/
|
|
9
|
+
import { useState } from 'react';
|
|
10
|
+
import { ENSO } from './api/models.js';
|
|
11
|
+
export function usePick(kept, keep, catalog) {
|
|
12
|
+
const [once, setOnce] = useState(null);
|
|
13
|
+
const premium = (id) => catalog.find((m) => m.id === id)?.class === 'premium';
|
|
14
|
+
const pick = (id) => {
|
|
15
|
+
if (premium(id))
|
|
16
|
+
return setOnce(id);
|
|
17
|
+
setOnce(null);
|
|
18
|
+
keep(id);
|
|
19
|
+
};
|
|
20
|
+
return [once ?? (premium(kept) ? ENSO : kept), pick];
|
|
21
|
+
}
|
package/lib/run.js
CHANGED
|
@@ -29,10 +29,12 @@ import { Button, Dialog, DialogContent, DialogTitle, DropdownMenu, Input } from
|
|
|
29
29
|
import { Steer } from '@hanzo/ui/agents';
|
|
30
30
|
import { Composer } from '@hanzo/ui/chat';
|
|
31
31
|
import { ChipSelect } from '@hanzo/ui/product';
|
|
32
|
+
import { ModelPicker } from '@hanzo/ui/models';
|
|
33
|
+
import { useLimits } from '@hanzo/ui/product/useLimits';
|
|
32
34
|
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
33
|
-
import { Access } from './access.js';
|
|
34
35
|
import { approve, followUp, headline, retry, start } from './api/coding.js';
|
|
35
|
-
import { ENSO,
|
|
36
|
+
import { ENSO, limits as readLimits, models } from './api/models.js';
|
|
37
|
+
import { usePick } from './pick.js';
|
|
36
38
|
import { list, message, pause, publish, rename, resume, stop, story, took } from './api/sessions.js';
|
|
37
39
|
import { answer, cards, outcome, pull, settled, steps } from './api/turn.js';
|
|
38
40
|
import { verdict } from './api/verdict.js';
|
|
@@ -102,9 +104,10 @@ export function Run({ id }) {
|
|
|
102
104
|
// The model and effort a run started here uses: New's choice, which a change here changes too.
|
|
103
105
|
const { prefs } = usePrefs();
|
|
104
106
|
const [chose, choose] = useKept(`hanzo.build.new.${host.org ?? 'none'}`, null);
|
|
105
|
-
const model = text(chose?.model) || prefs.code?.model || ENSO;
|
|
106
|
-
const pace = EFFORTS.find((e) => e.id === chose?.effort) ?? EFFORTS.find((e) => e.id === prefs.code?.effort) ?? EFFORTS[1];
|
|
107
107
|
const catalog = useRead(signed ? () => models(t) : null, [], [t, signed]);
|
|
108
|
+
const { limits } = useLimits(signed ? (signal) => readLimits(t, signal) : null, t);
|
|
109
|
+
const [model, pickModel] = usePick(text(chose?.model) || prefs.code?.model || ENSO, (id) => choose({ ...chose, model: id }), catalog.value);
|
|
110
|
+
const pace = EFFORTS.find((e) => e.id === chose?.effort) ?? EFFORTS.find((e) => e.id === prefs.code?.effort) ?? EFFORTS[1];
|
|
108
111
|
const mode = record?.mode ?? '';
|
|
109
112
|
// A run that narrated no ask of its own opens with the one its title records, once it has said anything.
|
|
110
113
|
const shown = useMemo(() => {
|
|
@@ -323,7 +326,7 @@ export function Run({ id }) {
|
|
|
323
326
|
? 'Follow up — continues in a new run'
|
|
324
327
|
: 'Reading this run…';
|
|
325
328
|
// What a new run from here is made with; words that steer this run use neither.
|
|
326
|
-
const tuning = signed && !steering ? (_jsxs(XStack, { flex: 1, items: "center", gap: "$2", justify: "flex-end", flexWrap: "wrap", rowGap: "$1", children: [_jsx(
|
|
329
|
+
const tuning = signed && !steering ? (_jsxs(XStack, { flex: 1, items: "center", gap: "$2", justify: "flex-end", flexWrap: "wrap", rowGap: "$1", children: [_jsx(ModelPicker, { quiet: true, size: "sm", name: "Model", models: catalog.value, scope: "chat", limits: limits, value: model, onChange: pickModel, loading: catalog.loading, error: catalog.error?.message ?? null }), _jsx(ChipSelect, { quiet: true, name: "Effort", label: pace.label, chosen: pace, items: EFFORTS, onChange: (e) => choose({ ...chose, effort: e.id }), placement: "top-end", width: 180 })] })) : undefined;
|
|
327
330
|
return (_jsxs(XStack, { ref: measure, "data-slot": "run-split", flex: 1, minH: 0, minW: 0, width: "100%", children: [_jsxs(YStack, { "data-slot": "run", ...pane, flex: 1, minH: 0, minW: 0, width: "100%", px: "$6", "$max-md": { px: '$4' }, children: [_jsxs(XStack, { pt: "$3", pb: "$2", gap: "$3", items: "center", minH: 44, children: [_jsxs(YStack, { flex: 1, minW: 0, items: "flex-start", children: [_jsx(DropdownMenu, { trigger: _jsxs(XStack, { render: "button", "aria-label": "Manage this run", items: "center", gap: "$1.5", maxW: "100%", px: "$1.5", mx: "$-1.5", py: "$0.5", rounded: "$2", hoverStyle: { bg: '$hover' }, children: [_jsx(SizableText, { render: "h1", size: "$5", color: "$ink", numberOfLines: 1, minW: 0, children: title }), _jsx(ChevronDown, { size: 14 })] }), items: manage }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, maxW: "100%", children: [state || (detail.loading ? 'reading' : ''), record?.repo, record?.branch, record?.published ? 'shared' : ''].filter(Boolean).join(' · ') })] }), desk ? null : (_jsx(XStack, { render: "button", "aria-label": "Show the side pane", px: "$2", py: "$1", rounded: "$2", hoverStyle: { bg: '$hover' }, onPress: () => setDesk(true), "$max-md": { display: 'none' }, children: _jsx(PanelRight, { size: 16 }) })), pr.href ? (_jsx(Out, { href: pr.href, label: `Open pull request ${pr.label}`, children: _jsxs(XStack, { items: "center", gap: "$1.5", px: "$2.5", py: "$1.5", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, children: [_jsx(GitPullRequest, { size: 14 }), _jsx(SizableText, { size: "$2", color: "$ink", children: pr.label }), _jsx(ExternalLink, { size: 12, opacity: 0.6 })] }) })) : null, sharing.length ? (_jsx(DropdownMenu, { trigger: _jsxs(XStack, { render: "button", "aria-label": "Share", items: "center", gap: "$1.5", px: "$2.5", py: "$1.5", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, children: [_jsx(Share, { size: 14 }), _jsx(SizableText, { size: "$2", color: "$ink", children: "Share" })] }), items: sharing })) : null] }), _jsx(Transcript, { cards: shown,
|
|
328
331
|
// A paused run's agent was stopped where it stood; what it was running is cut off.
|
|
329
332
|
live: running && !paused, header: hidden ? (_jsx(SizableText, { size: "$1", color: "$soft", children: `${hidden} earlier ${hidden === 1 ? 'turn is' : 'turns are'} not shown. The run’s Git tab has everything it pushed.` })) : null, onApprove: planned && earlier ? (p) => void act(() => carry(p, 'approve')) : undefined, approving: busy, onVerdict: (v) => verdict(t, id, record?.project ?? '', v), empty: signed ? (_jsx(SizableText, { size: "$2", color: "$soft", children: detail.error
|
package/lib/settings/code.js
CHANGED
|
@@ -8,9 +8,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
8
8
|
import { YStack } from '@hanzo/gui';
|
|
9
9
|
import { ChevronRight } from '@hanzogui/lucide-icons-2';
|
|
10
10
|
import { ChipSelect } from '@hanzo/ui/product';
|
|
11
|
+
import { ModelPicker } from '@hanzo/ui/models';
|
|
11
12
|
import { useState } from 'react';
|
|
12
13
|
import { unhonoured } from '../api/coding.js';
|
|
13
|
-
import { Access } from '../access.js';
|
|
14
14
|
import { ENSO, models } from '../api/models.js';
|
|
15
15
|
import { SANDBOX } from '../api/places.js';
|
|
16
16
|
import { usePlaces, useRead } from '../data.js';
|
|
@@ -44,11 +44,12 @@ export function Code() {
|
|
|
44
44
|
}));
|
|
45
45
|
// The sandbox is always listed, so a place not found is a machine that is not: it reads as its id.
|
|
46
46
|
const place = where.find((w) => w.place === now.place) ?? { id: now.place, label: now.place, place: now.place };
|
|
47
|
-
|
|
47
|
+
// A default is never premium: a premium model is picked for one run, in New.
|
|
48
|
+
const offered = catalog.value.filter((m) => m.class !== 'premium');
|
|
48
49
|
const go = (section) => host.go(path({ kind: 'settings', section }));
|
|
49
50
|
const effort = EFFORTS.find((e) => e.id === now.effort);
|
|
50
51
|
const mode = MODES.find((m) => m.id === now.mode);
|
|
51
|
-
return (_jsxs(YStack, { gap: "$6", children: [_jsx(Heading, { title: "Code", detail: "How a new run starts. New begins from these until you choose otherwise there." }), _jsxs(Group, { title: "Defaults", children: [_jsxs(Card, { children: [_jsx(Row, { first: true, title: "Model", detail: "Enso picks one for each step; any other model runs them all.", trailing: _jsx(
|
|
52
|
+
return (_jsxs(YStack, { gap: "$6", children: [_jsx(Heading, { title: "Code", detail: "How a new run starts. New begins from these until you choose otherwise there." }), _jsxs(Group, { title: "Defaults", children: [_jsxs(Card, { children: [_jsx(Row, { first: true, title: "Model", detail: "Enso picks one for each step; any other model runs them all.", trailing: _jsx(ModelPicker, { size: "sm", name: "Default model", models: offered, scope: "chat", value: now.model || ENSO, onChange: (id) => keep({ model: id }), loading: catalog.loading, error: catalog.error?.message ?? null }) }), _jsx(Row, { title: "Effort", detail: "How long the agent thinks before it acts.", trailing: _jsx(ChipSelect, { name: "Default effort", label: effort.label, chosen: effort, items: EFFORTS, onChange: (e) => keep({ effort: e.id }), placement: "bottom-end", width: 180 }) }), _jsx(Row, { title: "Mode", detail: mode.hint, trailing: _jsx(ChipSelect, { name: "Default mode", label: mode.label, chosen: mode, items: MODES, onChange: (m) => keep({ mode: m.id }), placement: "bottom-end", width: 260 }) }), _jsx(Row, { title: "Where it runs", detail: "The Hanzo sandbox, or one of your organization\u2019s machines.", trailing: _jsx(ChipSelect, { name: "Default place", label: place.label, chosen: place, items: where, onChange: (w) => keep({ place: w.place }), placeholder: "Search machines\u2026", placement: "bottom-end", loading: places.loading, error: places.error?.message ?? null }) })] }), _jsx(Note, { children: unhonoured(now.mode, now.place) })] }), _jsx(Group, { title: "Workspace", children: _jsxs(Card, { children: [_jsx(Row, { first: true, title: "Environments", detail: "What a run installs and starts in each codebase, and the secrets it exports.", trailing: _jsx(Link, { label: "Environments", onPress: () => go('environments') }) }), _jsx(Row, { title: "Machines", detail: "Your organization\u2019s own computers that take runs.", trailing: _jsx(Link, { label: "Machines", onPress: () => go('machines') }) })] }) }), _jsx(Note, { children: note })] }));
|
|
52
53
|
}
|
|
53
54
|
function Link({ label, onPress }) {
|
|
54
55
|
return (_jsx(YStack, { render: "button", "aria-label": `Open ${label}`, p: "$1", rounded: "$2", hoverStyle: { bg: '$hover' }, onPress: onPress, children: _jsx(ChevronRight, { size: 16 }) }));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/build",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.42",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0 OR MIT",
|
|
6
6
|
"description": "Hanzo Build — say what you want, watch it built, deployed and running. The builder as a component, mounted by the Hanzo app at hanzo.ai.",
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
"devDependencies": {
|
|
11
11
|
"@hanzo/font": "1.9.3",
|
|
12
12
|
"@hanzo/gui": "8.3.5",
|
|
13
|
-
"@hanzo/iam": "0.21.
|
|
14
|
-
"@hanzo/ui": "8.27.
|
|
13
|
+
"@hanzo/iam": "0.21.28",
|
|
14
|
+
"@hanzo/ui": "8.27.67",
|
|
15
15
|
"@hanzo/vite": "0.1.4",
|
|
16
16
|
"@hanzo/voice": "0.1.8",
|
|
17
17
|
"@hanzogui/lucide-icons-2": "8.3.5",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
58
|
"@hanzo/gui": ">=8.3.5",
|
|
59
|
-
"@hanzo/ui": ">=8.27.
|
|
59
|
+
"@hanzo/ui": ">=8.27.67",
|
|
60
60
|
"@hanzogui/lucide-icons-2": ">=8.3.5",
|
|
61
61
|
"react": ">=19.2.8",
|
|
62
62
|
"@hanzo/voice": ">=0.1.8"
|
package/lib/access.d.ts
DELETED
package/lib/access.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* Under a model list: the models it lists and cannot choose, and where to ask
|
|
4
|
-
* for them. Nothing while every model listed answers.
|
|
5
|
-
*
|
|
6
|
-
* A link cannot sit inside a listbox option, so it sits here, under the list,
|
|
7
|
-
* where the keyboard reaches it from the search field.
|
|
8
|
-
*/
|
|
9
|
-
import { SizableText, XStack } from '@hanzo/gui';
|
|
10
|
-
import { Out } from './out.js';
|
|
11
|
-
export function Access({ items }) {
|
|
12
|
-
const locked = items.filter((m) => m.disabled && m.request);
|
|
13
|
-
if (!locked.length)
|
|
14
|
-
return null;
|
|
15
|
-
return (_jsxs(XStack, { items: "center", gap: "$2", flexWrap: "wrap", children: [_jsxs(SizableText, { size: "$1", color: "$soft", children: [locked.map((m) => m.label).join(', '), " ", locked.length > 1 ? 'are' : 'is', " in research preview."] }), _jsx(Out, { href: locked[0].request, children: _jsx(SizableText, { size: "$1", color: "$ink", textDecorationLine: "underline", children: "Request access" }) })] }));
|
|
16
|
-
}
|