@hanzo/build 0.2.80 → 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 +13 -0
- package/lib/activity.d.ts +3 -7
- package/lib/activity.js +18 -5
- package/lib/api/agents.d.ts +1 -20
- package/lib/api/agents.js +16 -8
- 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.js +78 -31
- package/lib/customize/connectors.js +62 -48
- package/lib/customize/index.js +43 -10
- 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/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/run.js
CHANGED
|
@@ -24,6 +24,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
24
24
|
* the run. Share opens its story to the public build route.
|
|
25
25
|
*/
|
|
26
26
|
import { SizableText, useMedia, XStack, YStack } from '@hanzo/gui';
|
|
27
|
+
import { phrase } from '@hanzo/i18n';
|
|
28
|
+
import { useI18n } from '@hanzo/i18n/react';
|
|
27
29
|
import { ChevronDown, ChevronRight, Clock, Copy, ExternalLink, GitPullRequest, Globe, Pencil, Share } from '@hanzogui/lucide-icons-2';
|
|
28
30
|
import { Button, Dialog, DialogContent, DialogTitle, DropdownMenu, Input } from '@hanzo/ui';
|
|
29
31
|
import { Steer } from '@hanzo/ui/agents';
|
|
@@ -53,7 +55,16 @@ import { PAGE, useDeck, useKey, usePanel } from './tabs.js';
|
|
|
53
55
|
import { compose, Files, pick } from './tools.js';
|
|
54
56
|
import { Transcript } from './transcript.js';
|
|
55
57
|
const LIVE = new Set(['running', 'paused', '']);
|
|
58
|
+
/** A run's status as the line under its title says it; one the service adds later is drawn as it comes. */
|
|
59
|
+
const STATUS = {
|
|
60
|
+
running: phrase('running'),
|
|
61
|
+
paused: phrase('paused'),
|
|
62
|
+
done: phrase('done'),
|
|
63
|
+
error: phrase('error'),
|
|
64
|
+
stopped: phrase('stopped'),
|
|
65
|
+
};
|
|
56
66
|
export function Run({ id }) {
|
|
67
|
+
const { t: say } = useI18n();
|
|
57
68
|
const host = useHost();
|
|
58
69
|
const t = useTarget();
|
|
59
70
|
// Signed out, there is nothing to read: the gateway answers a bare run with a refusal.
|
|
@@ -85,11 +96,11 @@ export function Run({ id }) {
|
|
|
85
96
|
const mode = record?.mode ?? '';
|
|
86
97
|
// A run that narrated no ask of its own opens with the one its title records, once it has said anything.
|
|
87
98
|
const shown = useMemo(() => {
|
|
88
|
-
const drawn = cards(events, mode);
|
|
99
|
+
const drawn = cards(events, mode, say);
|
|
89
100
|
const ask = record ? headline(record.title, record.repo) : '';
|
|
90
101
|
const opens = drawn[0]?.kind === 'said' && drawn[0].who === 'person';
|
|
91
102
|
return ask && drawn.length && !opens ? [{ kind: 'said', key: 'ask', who: 'person', text: ask }, ...drawn] : drawn;
|
|
92
|
-
}, [events, mode, record]);
|
|
103
|
+
}, [events, mode, record, say]);
|
|
93
104
|
const end = outcome(events);
|
|
94
105
|
const plan = useMemo(() => steps(events), [events]);
|
|
95
106
|
const kept = useMemo(() => settled(events), [events]);
|
|
@@ -106,7 +117,7 @@ export function Run({ id }) {
|
|
|
106
117
|
useEffect(() => {
|
|
107
118
|
if (!end.published)
|
|
108
119
|
return;
|
|
109
|
-
side.open({ kind: 'preview', title: 'Preview' });
|
|
120
|
+
side.open({ kind: 'preview', title: say('Preview') });
|
|
110
121
|
setDesk(true);
|
|
111
122
|
// On each publish, not on each render.
|
|
112
123
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -124,7 +135,7 @@ export function Run({ id }) {
|
|
|
124
135
|
const steering = running && !(paused && held);
|
|
125
136
|
// A follow-up clones what the run kept, so it waits until the run has said where that is.
|
|
126
137
|
const waiting = (paused && held && !kept.settled) || (finished && stopping && !kept.settled);
|
|
127
|
-
const title = record?.title || (detail.loading ? '' : 'Untitled run');
|
|
138
|
+
const title = record?.title || (detail.loading ? '' : say('Untitled run'));
|
|
128
139
|
// A setup run explores and installs before it answers, which takes minutes.
|
|
129
140
|
const setup = mode === 'setup';
|
|
130
141
|
const [seen, setSeen] = useKept('hanzo.build.setup.seen', false);
|
|
@@ -158,17 +169,17 @@ export function Run({ id }) {
|
|
|
158
169
|
if (!notify || running)
|
|
159
170
|
return;
|
|
160
171
|
if (Notification.permission === 'granted')
|
|
161
|
-
new Notification(record?.title || 'Run finished', { body: 'This run has finished.' });
|
|
172
|
+
new Notification(record?.title || say('Run finished'), { body: say('This run has finished.') });
|
|
162
173
|
setNotify(false);
|
|
163
174
|
}, [notify, running, record?.title]);
|
|
164
175
|
const ask = async () => {
|
|
165
176
|
if (typeof Notification === 'undefined') {
|
|
166
|
-
setNote('This browser cannot send a notification.');
|
|
177
|
+
setNote(say('This browser cannot send a notification.'));
|
|
167
178
|
return;
|
|
168
179
|
}
|
|
169
180
|
const perm = Notification.permission === 'granted' ? 'granted' : await Notification.requestPermission();
|
|
170
181
|
if (perm !== 'granted') {
|
|
171
|
-
setNote('Notifications stay off until this browser allows them.');
|
|
182
|
+
setNote(say('Notifications stay off until this browser allows them.'));
|
|
172
183
|
return;
|
|
173
184
|
}
|
|
174
185
|
setNotify(true);
|
|
@@ -199,7 +210,7 @@ export function Run({ id }) {
|
|
|
199
210
|
// A setup run's work IS its environment: the panel opens on it.
|
|
200
211
|
useEffect(() => {
|
|
201
212
|
if (mode === 'setup')
|
|
202
|
-
side.open({ kind: 'environment', title: 'Environment' });
|
|
213
|
+
side.open({ kind: 'environment', title: say('Environment') });
|
|
203
214
|
// Once the run says what it is.
|
|
204
215
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
205
216
|
}, [mode]);
|
|
@@ -214,7 +225,7 @@ export function Run({ id }) {
|
|
|
214
225
|
*/
|
|
215
226
|
const carry = async (said, how = 'follow') => {
|
|
216
227
|
if (!earlier)
|
|
217
|
-
throw new Error('This run is still being read');
|
|
228
|
+
throw new Error(say('This run is still being read'));
|
|
218
229
|
if (paused)
|
|
219
230
|
await stop(t, id, 'Continued in a follow-up run').catch(() => undefined);
|
|
220
231
|
const ask = how === 'approve' ? approve(earlier, said) : how === 'retry' ? retry(earlier, said) : followUp(earlier, said);
|
|
@@ -232,7 +243,7 @@ export function Run({ id }) {
|
|
|
232
243
|
await message(t, id, words);
|
|
233
244
|
setDraft('');
|
|
234
245
|
setFiles([]);
|
|
235
|
-
return 'Sent — recorded on this run';
|
|
246
|
+
return say('Sent — recorded on this run');
|
|
236
247
|
});
|
|
237
248
|
return;
|
|
238
249
|
}
|
|
@@ -244,13 +255,13 @@ export function Run({ id }) {
|
|
|
244
255
|
void act(async () => {
|
|
245
256
|
await stop(t, id);
|
|
246
257
|
setStopping(true);
|
|
247
|
-
return 'Stop requested — the run keeps its work on its branch';
|
|
258
|
+
return say('Stop requested — the run keeps its work on its branch');
|
|
248
259
|
});
|
|
249
260
|
return;
|
|
250
261
|
case 'pause':
|
|
251
262
|
void act(async () => {
|
|
252
263
|
await pause(t, id);
|
|
253
|
-
return held ? 'Pause requested — the run keeps its work on its branch and waits' : 'Pause asked — the machine pauses when it reads it';
|
|
264
|
+
return held ? say('Pause requested — the run keeps its work on its branch and waits') : say('Pause asked — the machine pauses when it reads it');
|
|
254
265
|
});
|
|
255
266
|
return;
|
|
256
267
|
case 'resume':
|
|
@@ -259,7 +270,7 @@ export function Run({ id }) {
|
|
|
259
270
|
else
|
|
260
271
|
void act(async () => {
|
|
261
272
|
await resume(t, id);
|
|
262
|
-
return 'Resume asked — the machine goes on when it reads it';
|
|
273
|
+
return say('Resume asked — the machine goes on when it reads it');
|
|
263
274
|
});
|
|
264
275
|
return;
|
|
265
276
|
}
|
|
@@ -270,13 +281,13 @@ export function Run({ id }) {
|
|
|
270
281
|
setCopied(what);
|
|
271
282
|
}
|
|
272
283
|
catch {
|
|
273
|
-
setNote('This browser would not copy.');
|
|
284
|
+
setNote(say('This browser would not copy.'));
|
|
274
285
|
}
|
|
275
286
|
};
|
|
276
287
|
const share = (on) => act(async () => {
|
|
277
288
|
await publish(t, id, on);
|
|
278
289
|
detail.reload();
|
|
279
|
-
return on ? 'Shared — anyone with the link can read this run’s story' : 'No longer shared';
|
|
290
|
+
return on ? say('Shared — anyone with the link can read this run’s story') : say('No longer shared');
|
|
280
291
|
});
|
|
281
292
|
// What the title's menu does to the run.
|
|
282
293
|
const manage = [
|
|
@@ -284,7 +295,7 @@ export function Run({ id }) {
|
|
|
284
295
|
? [
|
|
285
296
|
{
|
|
286
297
|
key: 'rename',
|
|
287
|
-
label: 'Rename',
|
|
298
|
+
label: say('Rename'),
|
|
288
299
|
icon: _jsx(Pencil, { size: 16 }),
|
|
289
300
|
onSelect: () => {
|
|
290
301
|
setName(title);
|
|
@@ -293,15 +304,15 @@ export function Run({ id }) {
|
|
|
293
304
|
},
|
|
294
305
|
]
|
|
295
306
|
: []),
|
|
296
|
-
{ key: 'copy', label: copied === 'id' ? 'Copied' : 'Copy run id', icon: _jsx(Copy, { size: 16 }), description: id, onSelect: () => void copy('id', id) },
|
|
307
|
+
{ key: 'copy', label: copied === 'id' ? say('Copied') : say('Copy run id'), icon: _jsx(Copy, { size: 16 }), description: id, onSelect: () => void copy('id', id) },
|
|
297
308
|
// What this run was asked, as a task that runs again on a schedule: the ask it opened with.
|
|
298
309
|
...(signed && record
|
|
299
310
|
? [
|
|
300
311
|
{
|
|
301
312
|
key: 'schedule',
|
|
302
|
-
label: 'Schedule this',
|
|
313
|
+
label: say('Schedule this'),
|
|
303
314
|
icon: _jsx(Clock, { size: 16 }),
|
|
304
|
-
description: 'Run it again on a schedule',
|
|
315
|
+
description: say('Run it again on a schedule'),
|
|
305
316
|
onSelect: () => {
|
|
306
317
|
const first = shown.find((c) => c.kind === 'said' && c.who === 'person');
|
|
307
318
|
schedule(first && 'text' in first ? first.text : headline(record.title, record.repo), host.go);
|
|
@@ -315,77 +326,84 @@ export function Run({ id }) {
|
|
|
315
326
|
const sharing = signed && record?.project
|
|
316
327
|
? [
|
|
317
328
|
record.published
|
|
318
|
-
? { key: 'unshare', label: 'Stop sharing', icon: _jsx(Globe, { size: 16 }), onSelect: () => void share(false) }
|
|
319
|
-
: { key: 'share', label: 'Share publicly', icon: _jsx(Globe, { size: 16 }), description: 'Anyone with the link can read it', onSelect: () => void share(true) },
|
|
320
|
-
...(link ? [{ key: 'link', label: copied === 'link' ? 'Copied' : 'Copy public link', icon: _jsx(Copy, { size: 16 }), description: link, onSelect: () => void copy('link', link) }] : []),
|
|
329
|
+
? { key: 'unshare', label: say('Stop sharing'), icon: _jsx(Globe, { size: 16 }), onSelect: () => void share(false) }
|
|
330
|
+
: { key: 'share', label: say('Share publicly'), icon: _jsx(Globe, { size: 16 }), description: say('Anyone with the link can read it'), onSelect: () => void share(true) },
|
|
331
|
+
...(link ? [{ key: 'link', label: copied === 'link' ? say('Copied') : say('Copy public link'), icon: _jsx(Copy, { size: 16 }), description: link, onSelect: () => void copy('link', link) }] : []),
|
|
321
332
|
]
|
|
322
333
|
: [];
|
|
323
334
|
const save = () => void act(async () => {
|
|
324
335
|
await rename(t, id, name);
|
|
325
336
|
setNaming(false);
|
|
326
337
|
detail.reload();
|
|
327
|
-
return 'Renamed';
|
|
338
|
+
return say('Renamed');
|
|
328
339
|
});
|
|
329
340
|
const placeholder = !signed
|
|
330
|
-
? 'Sign in to follow up'
|
|
341
|
+
? say('Sign in to follow up')
|
|
331
342
|
: waiting
|
|
332
343
|
? paused
|
|
333
|
-
? 'Pausing — the run is keeping its work…'
|
|
334
|
-
: 'Stopping — the run is keeping its work…'
|
|
344
|
+
? say('Pausing — the run is keeping its work…')
|
|
345
|
+
: say('Stopping — the run is keeping its work…')
|
|
335
346
|
: paused && held
|
|
336
|
-
? 'Continue this run with a follow up'
|
|
347
|
+
? say('Continue this run with a follow up')
|
|
337
348
|
: running
|
|
338
349
|
? setup
|
|
339
|
-
? 'Add a follow up for the setup agent'
|
|
340
|
-
: 'Add a follow up'
|
|
350
|
+
? say('Add a follow up for the setup agent')
|
|
351
|
+
: say('Add a follow up')
|
|
341
352
|
: finished
|
|
342
|
-
? 'Follow up — continues in a new run'
|
|
343
|
-
: 'Reading this run…';
|
|
344
|
-
const desk_ = (sheet) => (_jsx(Desk, { id: id, repo: record?.repo ?? '', branch: record?.branch ?? '', base: record?.base ?? '', environment: record?.environment ?? '', mode: mode, pr: pr, title: title, project: record?.project ?? '', sandbox: record?.sandbox ?? '', site: site, published: end.published, events: events, live: running, menu: [...manage, ...sharing], refused: signed ? refused || (detail.error ? detail.error.message : '') : 'Sign in to follow this run.', retry: signed ? 'Retry' : 'Sign in', onRetry: signed ? detail.reload : () => host.signIn?.(), side: side, sheet: sheet, onHide: () => setDesk(false) }));
|
|
345
|
-
return (_jsxs(XStack, { "data-slot": "run-split", flex: 1, minH: 0, minW: 0, width: "100%", children: [_jsxs(YStack, { "data-slot": "run", ref: split.main, style: split.mainStyle, ...pane, flex: 1, minH: 0, minW: 0, width: "100%", children: [_jsxs(XStack, { px: "$4", pt: "$3", pb: "$2", gap: "$2", items: "center", minH: 44, children: [_jsxs(YStack, { flex: 1, minW: 0, items: "flex-start", children: [_jsx(DropdownMenu, { trigger: _jsxs(XStack, { render: "button", "aria-label":
|
|
346
|
-
side.open({ kind: 'diff', title: 'Diff' });
|
|
353
|
+
? say('Follow up — continues in a new run')
|
|
354
|
+
: say('Reading this run…');
|
|
355
|
+
const desk_ = (sheet) => (_jsx(Desk, { id: id, repo: record?.repo ?? '', branch: record?.branch ?? '', base: record?.base ?? '', environment: record?.environment ?? '', mode: mode, pr: pr, title: title, project: record?.project ?? '', sandbox: record?.sandbox ?? '', site: site, published: end.published, events: events, live: running, menu: [...manage, ...sharing], refused: signed ? refused || (detail.error ? say(detail.error.message) : '') : say('Sign in to follow this run.'), retry: signed ? say('Retry') : say('Sign in'), onRetry: signed ? detail.reload : () => host.signIn?.(), side: side, sheet: sheet, onHide: () => setDesk(false) }));
|
|
356
|
+
return (_jsxs(XStack, { "data-slot": "run-split", flex: 1, minH: 0, minW: 0, width: "100%", children: [_jsxs(YStack, { "data-slot": "run", ref: split.main, style: split.mainStyle, ...pane, flex: 1, minH: 0, minW: 0, width: "100%", children: [_jsxs(XStack, { px: "$4", pt: "$3", pb: "$2", gap: "$2", items: "center", minH: 44, children: [_jsxs(YStack, { flex: 1, minW: 0, items: "flex-start", children: [_jsx(DropdownMenu, { trigger: _jsxs(XStack, { render: "button", "aria-label": say('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 ? say(STATUS[state] ?? state) : detail.loading ? say('reading') : '', record?.repo, record?.branch, record?.published ? say('shared') : ''].filter(Boolean).join(' · ') })] }), pr.href ? (_jsx(Out, { href: pr.href, label: say('Open pull request {pr}', { pr: 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": say('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: say('Share') })] }), items: sharing })) : null, _jsx(PanelToggle, { open: desk, onToggle: () => setDesk(!desk) })] }), _jsx(Transcript, { cards: shown, onOpen: look, onDiff: () => {
|
|
357
|
+
side.open({ kind: 'diff', title: say('Diff') });
|
|
347
358
|
setDesk(true);
|
|
348
|
-
}, listen: signed ? listen : undefined, end: failed ? (_jsx(Failure, { onRetry: busy ? undefined : () => void act(() => carry(again, 'retry')), children: end.problem
|
|
359
|
+
}, listen: signed ? listen : undefined, end: failed ? (_jsx(Failure, { onRetry: busy ? undefined : () => void act(() => carry(again, 'retry')), children: end.problem
|
|
360
|
+
? say('This run stopped before it finished.')
|
|
361
|
+
: `${say('This run stopped with an error.')} ${say('Its work so far is on its branch.')}` })) : null,
|
|
349
362
|
// A paused run's agent was stopped where it stood; what it was running is cut off.
|
|
350
|
-
live: running && !paused, header: hidden ? (_jsx(SizableText, { size: "$1", color: "$soft", children: `${hidden
|
|
351
|
-
? detail.error.message
|
|
363
|
+
live: running && !paused, header: hidden ? (_jsx(SizableText, { size: "$1", color: "$soft", children: `${hidden === 1 ? say('{n} earlier turn is not shown.', { n: hidden }) : say('{n} earlier turns are not shown.', { n: hidden })} ${say('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
|
|
364
|
+
? say(detail.error.message)
|
|
352
365
|
: detail.loading
|
|
353
|
-
? 'Reading this run…'
|
|
366
|
+
? say('Reading this run…')
|
|
354
367
|
: running
|
|
355
368
|
? setup
|
|
356
|
-
? 'Setting up environment'
|
|
357
|
-
: 'Starting…'
|
|
358
|
-
: 'Waiting for the run’s first step…' })) : (_jsxs(YStack, { gap: "$3", items: "flex-start", children: [_jsx(SizableText, { size: "$2", color: "$soft", children:
|
|
369
|
+
? say('Setting up environment')
|
|
370
|
+
: say('Starting…')
|
|
371
|
+
: say('Waiting for the run’s first step…') })) : (_jsxs(YStack, { gap: "$3", items: "flex-start", children: [_jsx(SizableText, { size: "$2", color: "$soft", children: say('Sign in to follow this run.') }), _jsx(Button, { size: "sm", onPress: () => host.signIn?.(), children: say('Sign in') })] })) }), _jsxs(YStack, { pb: "$3", pt: "$2", gap: "$2", children: [_jsxs(YStack, { ...measure, gap: "$2", children: [end.problem ? (_jsx(SizableText, { size: "$1", color: "$soft", children: say('The branch is pushed, and the pull request could not be opened.') })) : null, note || (signed && refused) ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: note || refused })) : null, plan.length ? (_jsxs(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$3", overflow: "hidden", children: [_jsxs(XStack, { render: "button", "aria-label": say('Steps · {n}', { n: plan.length }), "aria-expanded": unfolded, onPress: () => setUnfolded(!unfolded), px: "$3", py: "$1.5", gap: "$2", items: "center", hoverStyle: { bg: '$hover' }, children: [unfolded ? _jsx(ChevronDown, { size: 14 }) : _jsx(ChevronRight, { size: 14 }), _jsx(SizableText, { size: "$2", color: "$ink", children: say('Steps · {n}', { n: plan.length }) }), current && !unfolded ? (_jsx(SizableText, { flex: 1, minW: 0, size: "$1", color: "$soft", numberOfLines: 1, text: "end", children: current.name })) : null] }), unfolded
|
|
359
372
|
? plan.map((s) => {
|
|
360
373
|
const on = s === current;
|
|
361
374
|
return (_jsxs(XStack, { items: "center", gap: "$2", px: "$3", py: "$1.5", borderTopWidth: 1, borderColor: "$borderColor", children: [_jsx(SizableText, { size: "$2", color: s.done || on ? '$ink' : '$soft', children: s.done ? '✓' : on ? '●' : '○' }), _jsx(SizableText, { size: "$2", color: s.done ? '$soft' : '$ink', numberOfLines: 1, children: s.name })] }, s.name));
|
|
362
375
|
})
|
|
363
376
|
: null] })) : null, setup && running && !seen ? _jsx(Onboarding, { onDone: () => setSeen(true) }) : null, running ? (_jsxs(XStack, { items: "center", gap: "$2", px: "$3", py: "$2", rounded: "$10", borderWidth: 1, borderColor: "$borderColor", flexWrap: "wrap", rowGap: "$1", children: [_jsx(SizableText, { size: "$2", color: "$ink", flex: 1, minW: 160, children: paused
|
|
364
377
|
? kept.pushed
|
|
365
|
-
? 'Paused — its work so far is on its branch.'
|
|
366
|
-
: 'Paused.'
|
|
378
|
+
? say('Paused — its work so far is on its branch.')
|
|
379
|
+
: say('Paused.')
|
|
367
380
|
: setup
|
|
368
381
|
? span
|
|
369
|
-
?
|
|
370
|
-
: 'Environment setup takes several minutes.'
|
|
371
|
-
: 'This run is still working.' }), _jsx(Steer, { onCommand: command, withhold: [
|
|
382
|
+
? say('Environment setup takes ~{minutes} minutes.', { minutes: span[0] === span[1] ? span[0] : `${span[0]}–${span[1]}` })
|
|
383
|
+
: say('Environment setup takes several minutes.')
|
|
384
|
+
: say('This run is still working.') }), _jsx(Steer, { onCommand: command, withhold: [
|
|
372
385
|
...(paused ? ['pause'] : ['resume']),
|
|
373
386
|
...(busy || waiting ? ['pause', 'resume', 'stop'] : []),
|
|
374
|
-
] }), _jsx(Button, { size: "sm", disabled: notify, onPress: () => void ask(), children: notify ? 'You will be notified' : 'Notify me' })] })) : null] }), _jsx(Prompt, { framed: true, value: draft, onChange: setDraft, onSend: send, ready: Boolean(draft.trim()), disabled: !signed || busy || waiting || !(running || finished), placeholder: placeholder, label: steering ? 'Steer this run' : 'Follow up on this run', head: files.length ? _jsx(XStack, { gap: "$1.5", flexWrap: "wrap", children: _jsx(Files, { files: files, onFiles: setFiles }) }) : undefined, plus: { upload: () => pick(files, setFiles, setNote) },
|
|
387
|
+
] }), _jsx(Button, { size: "sm", disabled: notify, onPress: () => void ask(), children: notify ? say('You will be notified') : say('Notify me') })] })) : null] }), _jsx(Prompt, { framed: true, value: draft, onChange: setDraft, onSend: send, ready: Boolean(draft.trim()), disabled: !signed || busy || waiting || !(running || finished), placeholder: placeholder, label: steering ? say('Steer this run') : say('Follow up on this run'), head: files.length ? _jsx(XStack, { gap: "$1.5", flexWrap: "wrap", children: _jsx(Files, { files: files, onFiles: setFiles }) }) : undefined, plus: { upload: () => pick(files, setFiles, setNote, say) },
|
|
375
388
|
// What a new run from here is made with; words that steer this run use neither.
|
|
376
389
|
mind: signed && !steering ? mind : null, limits: limits, voice: voice })] })] }), desk && !wide ? desk_(true) : null, desk && wide ? (_jsxs(YStack, { "data-slot": "desk", ref: split.aside, ...pane,
|
|
377
|
-
// A pane of its own beside the run's, the gutter between them.
|
|
378
|
-
|
|
390
|
+
// A pane of its own beside the run's, the gutter between them (useSplit reads it as the inline-start margin).
|
|
391
|
+
marginInlineStart: gap, position: "relative", shrink: 0, minH: 0, style: split.asideStyle, children: [split.grip ? (_jsx(Gutter, { ...split.grip, label: say('Resize the side panel'), inset: pane.borderWidth })) : null, _jsx(SideProvider, { value: split.side, children: desk_(false) })] })) : null, _jsx(Dialog, { open: naming, onOpenChange: setNaming, children: _jsxs(DialogContent, { maxW: 480, children: [_jsx(DialogTitle, { children: say('Rename this run') }), _jsx(Input, { autoFocus: true, value: name, onChangeText: setName, "aria-label": say('The run’s name'), maxLength: 512, onKeyDown: (e) => {
|
|
379
392
|
if (e.key === 'Enter')
|
|
380
393
|
save();
|
|
381
|
-
} }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "outline", onPress: () => setNaming(false), children:
|
|
394
|
+
} }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "outline", onPress: () => setNaming(false), children: say('Cancel') }), _jsx(Button, { size: "sm", disabled: busy || !name.trim(), onPress: save, children: say('Save') })] })] }) })] }));
|
|
382
395
|
}
|
|
383
396
|
/** What a setup run does, said once per browser the first time one is watched. */
|
|
384
397
|
function Onboarding({ onDone }) {
|
|
398
|
+
const { t } = useI18n();
|
|
385
399
|
const steps = [
|
|
386
|
-
'Explore, install and check the codebase',
|
|
387
|
-
'Name the secrets it needs — you set their values',
|
|
388
|
-
'Propose the environment for you to review and save',
|
|
400
|
+
t('Explore, install and check the codebase'),
|
|
401
|
+
t('Name the secrets it needs — you set their values'),
|
|
402
|
+
t('Propose the environment for you to review and save'),
|
|
389
403
|
];
|
|
390
|
-
return (_jsxs(YStack, { gap: "$3", p: "$4", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", children: [_jsx(SizableText, { size: "$4", color: "$ink", children:
|
|
404
|
+
return (_jsxs(YStack, { gap: "$3", p: "$4", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", children: [_jsx(SizableText, { size: "$4", color: "$ink", children: t('Set up a cloud environment') }), _jsx(SizableText, { size: "$2", color: "$soft", children: [
|
|
405
|
+
t('An environment lets every later run install, start, test and check its changes the way an engineer does.'),
|
|
406
|
+
t('Setup is agent-led and takes several minutes.'),
|
|
407
|
+
t('The agent will:'),
|
|
408
|
+
].join(' ') }), _jsx(YStack, { gap: "$1.5", children: steps.map((s, i) => (_jsxs(XStack, { gap: "$3", children: [_jsx(SizableText, { size: "$2", color: "$soft", width: 12, children: String(i + 1) }), _jsx(SizableText, { size: "$2", color: "$ink", children: s })] }, s))) }), _jsx(SizableText, { size: "$2", color: "$soft", children: t('Interrupt anytime to steer the agent or ask it questions.') }), _jsx(Button, { size: "sm", onPress: onDone, children: t('Got it') })] }));
|
|
391
409
|
}
|
package/lib/say.d.ts
ADDED
package/lib/say.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `t` where no component is asking: the English, its `{name}` arguments filled.
|
|
3
|
+
* A helper that builds words outside a render (a rail's rows, a formatter)
|
|
4
|
+
* takes the caller's @hanzo/i18n `t` and falls back to this.
|
|
5
|
+
*/
|
|
6
|
+
import { translate } from '@hanzo/i18n';
|
|
7
|
+
export const english = ((source, values) => translate({}, source, values));
|
package/lib/scheduled.js
CHANGED
|
@@ -12,16 +12,21 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
12
12
|
* a read every half minute keeps them right when the stream is down. A new task
|
|
13
13
|
* is said in plain words — "every Monday at 9am" — and the platform says back
|
|
14
14
|
* what it understood before anything is created.
|
|
15
|
+
*
|
|
16
|
+
* Its words are the reader's language (@hanzo/i18n `t`); a task's name and
|
|
17
|
+
* prompt, a run's title, summary and error, and what the platform says back
|
|
18
|
+
* are drawn as given.
|
|
15
19
|
*/
|
|
16
20
|
import { SizableText, XStack, YStack } from '@hanzo/gui';
|
|
17
21
|
import { Clock, ExternalLink, MoreHorizontal, Pencil, Play, Plus, ScrollText, Trash2 } from '@hanzogui/lucide-icons-2';
|
|
22
|
+
import { useI18n } from '@hanzo/i18n/react';
|
|
18
23
|
import { Button, DropdownMenu, Input, Switch, Textarea } from '@hanzo/ui';
|
|
19
24
|
import { StatusDot } from '@hanzo/ui/chat';
|
|
20
25
|
import { ModelPicker } from '@hanzo/ui/models';
|
|
21
26
|
import { useLimits } from '@hanzo/ui/product/useLimits';
|
|
22
27
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
23
28
|
import { automations, create, cron, events, jobs, remove, save, start, words } from './api/auto.js';
|
|
24
|
-
import { Refusal } from './api/call.js';
|
|
29
|
+
import { Refusal, told } from './api/call.js';
|
|
25
30
|
import { ENSO, limits as readLimits, models } from './api/models.js';
|
|
26
31
|
import { Confirm, Field, Line, Sheet } from './customize/ui.js';
|
|
27
32
|
import { local, PLAN, shaped, tidy, unplanned, unready, When, zone } from './automations.js';
|
|
@@ -92,12 +97,13 @@ function useLive(on) {
|
|
|
92
97
|
return live;
|
|
93
98
|
}
|
|
94
99
|
export function Scheduled() {
|
|
100
|
+
const { t } = useI18n();
|
|
95
101
|
const host = useHost();
|
|
96
|
-
const
|
|
102
|
+
const target = useTarget();
|
|
97
103
|
const signed = Boolean(host.person);
|
|
98
104
|
const [tick, setTick] = useState(0);
|
|
99
|
-
const tasks = useRead(signed ? () => automations(
|
|
100
|
-
const runs = useRead(signed ? () => jobs(
|
|
105
|
+
const tasks = useRead(signed ? () => automations(target) : null, [], [target, signed, tick]);
|
|
106
|
+
const runs = useRead(signed ? () => jobs(target, { limit: 50 }) : null, [], [target, signed, tick]);
|
|
101
107
|
const [shownTasks, setTasks] = useState([]);
|
|
102
108
|
const [shownRuns, setRuns] = useState([]);
|
|
103
109
|
useEffect(() => setTasks(tasks.value), [tasks.value]);
|
|
@@ -134,58 +140,62 @@ export function Scheduled() {
|
|
|
134
140
|
await work();
|
|
135
141
|
}
|
|
136
142
|
catch (e) {
|
|
137
|
-
setProblem(unplanned(e) ? PLAN :
|
|
143
|
+
setProblem(unplanned(e) ? t(PLAN) : t('{name}: {error}', { name: what, error: told(e, t) }));
|
|
138
144
|
}
|
|
139
145
|
};
|
|
140
146
|
const flip = (a, on) => act(a.name, async () => {
|
|
141
|
-
const next = await save(
|
|
147
|
+
const next = await save(target, a.id, { enabled: on });
|
|
142
148
|
setTasks((was) => was.map((x) => (x.id === next.id ? next : x)));
|
|
143
149
|
});
|
|
144
150
|
const run = (a) => act(a.name, async () => {
|
|
145
|
-
const r = await start(
|
|
151
|
+
const r = await start(target, a.id);
|
|
146
152
|
if (r.status === 'skipped')
|
|
147
|
-
setProblem(
|
|
153
|
+
setProblem(t('{name}: not started, the previous run is still going.', { name: a.name }));
|
|
148
154
|
setTick((n) => n + 1);
|
|
149
155
|
});
|
|
150
156
|
const edit = (id) => host.go(path({ kind: 'automation', id }));
|
|
151
|
-
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, { justify: "space-between", items: "flex-start", gap: "$4", children: [_jsxs(YStack, { gap: "$1", flex: 1, minW: 0, children: [_jsx(SizableText, { render: "h1", size: "$7", fontWeight: "600", color: "$ink", children:
|
|
157
|
+
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, { justify: "space-between", items: "flex-start", gap: "$4", children: [_jsxs(YStack, { gap: "$1", flex: 1, minW: 0, children: [_jsx(SizableText, { render: "h1", size: "$7", fontWeight: "600", color: "$ink", children: t('Scheduled tasks') }), _jsx(SizableText, { size: "$2", color: "$soft", children: t('What runs without you watching: prompts on a schedule, background runs, Studio renders and workflows.') })] }), _jsxs(Button, { size: "sm", shrink: 0, disabled: !signed, onPress: () => setMaking(''), children: [_jsx(Plus, { size: 14 }), t('New task')] })] }), _jsx(Line, { children: problem }), _jsxs(YStack, { gap: "$2", children: [_jsx(SizableText, { render: "h2", size: "$3", color: "$ink", fontWeight: "500", children: t('Tasks') }), !signed ? (_jsx(Said, { says: t('Sign in to see your scheduled tasks.'), children: _jsx(Button, { size: "sm", onPress: () => host.signIn?.(), children: t('Sign in') }) })) : tasks.error && !shownTasks.length ? (_jsx(Said, { says: told(tasks.error, t) })) : tasks.loading && !shownTasks.length ? (_jsx(Said, { says: t('Reading your tasks…') })) : !shownTasks.length ? (_jsx(Said, { says: t('No scheduled tasks yet. Ask Chat or Dev to do something on a schedule, or start one here.'), children: _jsx(Button, { size: "sm", onPress: () => setMaking(''), children: t('New task') }) })) : (_jsx(YStack, { role: "list", "aria-label": t('Your tasks'), borderWidth: 1, borderColor: "$borderColor", rounded: "$4", overflow: "hidden", children: shownTasks.map((a, i) => (_jsx(Task, { a: a, first: i === 0, onOpen: () => edit(a.id), onSwitch: (on) => void flip(a, on), onRun: () => void run(a), onDelete: () => setDeleting(a) }, a.id))) }))] }), signed ? (_jsxs(YStack, { gap: "$2", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(SizableText, { render: "h2", size: "$3", color: "$ink", fontWeight: "500", flex: 1, children: t('Activity') }), _jsx(SizableText, { size: "$1", color: "$soft", children: live ? t('Live') : '' })] }), runs.error && !shownRuns.length ? (_jsx(Said, { says: told(runs.error, t) })) : runs.loading && !shownRuns.length ? (_jsx(Said, { says: t('Reading what ran…') })) : !shownRuns.length ? (_jsx(Said, { says: t("Nothing has run yet. A task's runs, Dev's background runs and Studio's renders show here as they go.") })) : (_jsx(YStack, { role: "list", "aria-label": t('Activity'), borderWidth: 1, borderColor: "$borderColor", rounded: "$4", overflow: "hidden", children: shownRuns.map((j, i) => (_jsx(Ran, { j: j, first: i === 0, now: now, onOpen: () => setLooking(j) }, j.id))) }))] })) : null] }), _jsx(Make, { prompt: making, onClose: () => setMaking(null), onMade: (a) => {
|
|
152
158
|
setTasks((was) => [a, ...was.filter((x) => x.id !== a.id)]);
|
|
153
|
-
setProblem(
|
|
154
|
-
} }), _jsx(Detail, { j: looking, onClose: () => setLooking(null), now: now }), deleting ? (_jsx(Confirm, { what: deleting.name, says:
|
|
155
|
-
await remove(
|
|
159
|
+
setProblem(t('Scheduled: {name}.', { name: a.name }));
|
|
160
|
+
} }), _jsx(Detail, { j: looking, onClose: () => setLooking(null), now: now }), deleting ? (_jsx(Confirm, { what: deleting.name, says: t('It stops running, and its schedule and its runs go with it. A run going now is stopped.'), open: true, onOpenChange: (o) => !o && setDeleting(null), onYes: async () => {
|
|
161
|
+
await remove(target, deleting.id);
|
|
156
162
|
setTasks((was) => was.filter((x) => x.id !== deleting.id));
|
|
157
163
|
setDeleting(null);
|
|
158
164
|
} })) : null] }));
|
|
159
165
|
}
|
|
160
166
|
/** One task: what it is and when it runs next, its switch, and its menu. */
|
|
161
167
|
function Task({ a, first, onOpen, onSwitch, onRun, onDelete }) {
|
|
168
|
+
const { t } = useI18n();
|
|
162
169
|
const when = a.draft
|
|
163
|
-
? 'Needs instructions'
|
|
164
|
-
: [words(a.schedule), a.enabled && a.next ?
|
|
165
|
-
return (_jsxs(XStack, { role: "listitem", items: "center", gap: "$3", px: "$3", py: "$3", borderTopWidth: first ? 0 : 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, children: [_jsxs(XStack, { render: "button", "aria-label":
|
|
166
|
-
...(a.draft ? [] : [{ key: 'run', label: 'Run now', icon: _jsx(Play, { size: 16 }), onSelect: onRun }]),
|
|
167
|
-
{ key: 'edit', label: 'Edit', icon: _jsx(Pencil, { size: 16 }), onSelect: onOpen },
|
|
168
|
-
{ key: 'delete', label: 'Delete', icon: _jsx(Trash2, { size: 16 }), onSelect: onDelete },
|
|
170
|
+
? t('Needs instructions')
|
|
171
|
+
: [words(a.schedule, t), a.enabled && a.next ? t('next {when}', { when: local(a.next) }) : a.schedule.kind !== 'manual' && !a.enabled ? t('paused') : ''].filter(Boolean).join(' · ');
|
|
172
|
+
return (_jsxs(XStack, { role: "listitem", items: "center", gap: "$3", px: "$3", py: "$3", borderTopWidth: first ? 0 : 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, children: [_jsxs(XStack, { render: "button", "aria-label": t('Open {name}', { name: a.name }), onPress: onOpen, flex: 1, minW: 0, items: "center", gap: "$3", cursor: "pointer", "$platform-web": { textAlign: 'start' }, children: [_jsx(YStack, { width: 32, height: 32, rounded: "$3", bg: "$raised", items: "center", justify: "center", shrink: 0, "aria-hidden": true, children: _jsx(Clock, { size: 16 }) }), _jsxs(YStack, { flex: 1, minW: 0, gap: "$0.5", children: [_jsxs(XStack, { items: "center", gap: "$2", minW: 0, children: [_jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, shrink: 1, children: a.name }), _jsx(Badge, { children: t(SOURCE[a.source]) }), a.draft ? _jsx(Badge, { children: t('Draft') }) : null] }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: when })] })] }), _jsx(Switch, { checked: a.enabled, disabled: a.draft, onCheckedChange: onSwitch, "aria-label": t('{name} on', { name: a.name }) }), _jsx(DropdownMenu, { trigger: _jsx(XStack, { render: "button", "aria-label": t('Actions for {name}', { name: a.name }), width: 30, height: 30, items: "center", justify: "center", rounded: "$2", hoverStyle: { bg: '$hover' }, cursor: "pointer", children: _jsx(MoreHorizontal, { size: 16 }) }), items: [
|
|
173
|
+
...(a.draft ? [] : [{ key: 'run', label: t('Run now'), icon: _jsx(Play, { size: 16 }), onSelect: onRun }]),
|
|
174
|
+
{ key: 'edit', label: t('Edit'), icon: _jsx(Pencil, { size: 16 }), onSelect: onOpen },
|
|
175
|
+
{ key: 'delete', label: t('Delete'), icon: _jsx(Trash2, { size: 16 }), onSelect: onDelete },
|
|
169
176
|
] })] }));
|
|
170
177
|
}
|
|
171
178
|
/** One run in Activity. */
|
|
172
179
|
function Ran({ j, first, now, onOpen }) {
|
|
173
|
-
|
|
180
|
+
const { t } = useI18n();
|
|
181
|
+
return (_jsx(XStack, { role: "listitem", borderTopWidth: first ? 0 : 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, children: _jsxs(XStack, { render: "button", "aria-label": t('{title}: {state}', { title: j.title, state: t(SAID[j.state]) }), onPress: onOpen, flex: 1, minW: 0, items: "center", gap: "$3", px: "$3", py: "$2.5", cursor: "pointer", "$platform-web": { textAlign: 'start' }, children: [_jsx(StatusDot, { status: DOTS[j.state] }), _jsxs(YStack, { flex: 1, minW: 0, gap: "$0.5", children: [_jsx(SizableText, { size: "$2", color: "$ink", numberOfLines: 1, children: j.title }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: about(j, now, t) })] }), _jsx(SizableText, { size: "$1", color: "$soft", shrink: 0, children: j.state === 'running' && j.progress !== null ? `${Math.round(j.progress * 100)}%` : t(SAID[j.state]) })] }) }));
|
|
174
182
|
}
|
|
175
183
|
/** One run, opened: how it went, its log, what it made and what it cost. */
|
|
176
184
|
function Detail({ j, onClose, now }) {
|
|
185
|
+
const { t } = useI18n();
|
|
177
186
|
const host = useHost();
|
|
178
|
-
return (_jsx(Sheet, { title: j?.title ?? 'Run', open: Boolean(j), onOpenChange: (o) => !o && onClose(), width: 520, children: j ? (_jsxs(YStack, { gap: "$3", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(StatusDot, { status: DOTS[j.state] }),
|
|
179
|
-
['From', SOURCE[j.product]],
|
|
180
|
-
['Started', j.started ? local(j.started) : '—'],
|
|
181
|
-
['Took', lasted(j.started, j.finished, now) || '—'],
|
|
182
|
-
['Cost', j.cost !== null ? dollars(j.cost) : 'Not known yet'],
|
|
183
|
-
] }), j.error ? (_jsx(SizableText, { size: "$2", color: "$soft", style: { whiteSpace: 'pre-wrap' }, children: j.error })) : null, j.summary ? (_jsx(SizableText, { size: "$2", color: "$ink", style: { whiteSpace: 'pre-wrap' }, children: j.summary })) : null, j.outputs.length ? (_jsxs(YStack, { gap: "$1", children: [_jsx(SizableText, { size: "$2", color: "$soft", children:
|
|
187
|
+
return (_jsx(Sheet, { title: j?.title ?? t('Run'), open: Boolean(j), onOpenChange: (o) => !o && onClose(), width: 520, children: j ? (_jsxs(YStack, { gap: "$3", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(StatusDot, { status: DOTS[j.state] }), _jsx(SizableText, { size: "$2", color: "$ink", children: j.status && j.status !== j.state ? t('{state} ({status})', { state: t(SAID[j.state]), status: j.status }) : t(SAID[j.state]) })] }), _jsx(Facts, { rows: [
|
|
188
|
+
[t('From'), t(SOURCE[j.product])],
|
|
189
|
+
[t('Started'), j.started ? local(j.started) : '—'],
|
|
190
|
+
[t('Took'), lasted(j.started, j.finished, now) || '—'],
|
|
191
|
+
[t('Cost'), j.cost !== null ? dollars(j.cost) : t('Not known yet')],
|
|
192
|
+
] }), j.error ? (_jsx(SizableText, { size: "$2", color: "$soft", style: { whiteSpace: 'pre-wrap' }, children: j.error })) : null, j.summary ? (_jsx(SizableText, { size: "$2", color: "$ink", style: { whiteSpace: 'pre-wrap' }, children: j.summary })) : null, j.outputs.length ? (_jsxs(YStack, { gap: "$1", children: [_jsx(SizableText, { size: "$2", color: "$soft", children: t('What it made') }), _jsx(Outputs, { ids: j.outputs })] })) : null, _jsxs(XStack, { gap: "$2", flexWrap: "wrap", rowGap: "$2", children: [j.session ? (_jsxs(Button, { size: "sm", variant: "outline", onPress: () => host.go(j.session), children: [_jsx(ScrollText, { size: 14 }), t('Open its log')] })) : null, j.origin ? (_jsxs(Button, { size: "sm", variant: "outline", onPress: () => host.open(j.origin), children: [_jsx(ExternalLink, { size: 14 }), t('Open where it runs')] })) : null, j.automation ? (_jsxs(Button, { size: "sm", variant: "ghost", onPress: () => host.go(path({ kind: 'automation', id: j.automation })), children: [_jsx(Pencil, { size: 14 }), t('Edit the task')] })) : null] })] })) : null }));
|
|
184
193
|
}
|
|
185
194
|
/** A new task: what it does, when in plain words, and the model it thinks with. */
|
|
186
195
|
function Make({ prompt, onClose, onMade }) {
|
|
196
|
+
const { t } = useI18n();
|
|
187
197
|
const host = useHost();
|
|
188
|
-
const
|
|
198
|
+
const target = useTarget();
|
|
189
199
|
const signed = Boolean(host.person);
|
|
190
200
|
const open = prompt !== null;
|
|
191
201
|
const [name, setName] = useState('');
|
|
@@ -193,14 +203,15 @@ function Make({ prompt, onClose, onMade }) {
|
|
|
193
203
|
const [said, setSaid] = useState('');
|
|
194
204
|
const [read, setRead] = useState(null);
|
|
195
205
|
const [reading, setReading] = useState(false);
|
|
196
|
-
|
|
206
|
+
// Why the platform could not read the words: its refusal, said when drawn.
|
|
207
|
+
const [unread, setUnread] = useState(null);
|
|
197
208
|
const [picking, setPicking] = useState(false);
|
|
198
209
|
const [picked, setPicked] = useState(() => shaped('daily', { kind: 'manual' }));
|
|
199
210
|
const [model, setModel] = useState(null);
|
|
200
211
|
const [busy, setBusy] = useState(false);
|
|
201
212
|
const [note, setNote] = useState('');
|
|
202
|
-
const catalog = useRead(signed && open ? () => models(
|
|
203
|
-
const { limits } = useLimits(signed && open ? (signal) => readLimits(
|
|
213
|
+
const catalog = useRead(signed && open ? () => models(target) : null, [], [target, signed, open]);
|
|
214
|
+
const { limits } = useLimits(signed && open ? (signal) => readLimits(target, signal) : null, target);
|
|
204
215
|
// Each opening starts from what it was handed.
|
|
205
216
|
useEffect(() => {
|
|
206
217
|
if (prompt === null)
|
|
@@ -209,7 +220,7 @@ function Make({ prompt, onClose, onMade }) {
|
|
|
209
220
|
setName(titled(prompt));
|
|
210
221
|
setSaid('');
|
|
211
222
|
setRead(null);
|
|
212
|
-
setUnread(
|
|
223
|
+
setUnread(null);
|
|
213
224
|
setPicking(false);
|
|
214
225
|
setNote('');
|
|
215
226
|
}, [prompt]);
|
|
@@ -218,22 +229,22 @@ function Make({ prompt, onClose, onMade }) {
|
|
|
218
229
|
const text = said.trim();
|
|
219
230
|
if (!open || !text || picking) {
|
|
220
231
|
setRead(null);
|
|
221
|
-
setUnread(
|
|
232
|
+
setUnread(null);
|
|
222
233
|
return;
|
|
223
234
|
}
|
|
224
235
|
const ctl = new AbortController();
|
|
225
236
|
const id = setTimeout(() => {
|
|
226
237
|
setReading(true);
|
|
227
|
-
cron(
|
|
238
|
+
cron(target, text, zone(), { signal: ctl.signal })
|
|
228
239
|
.then((p) => {
|
|
229
240
|
setRead(p);
|
|
230
|
-
setUnread(
|
|
241
|
+
setUnread(null);
|
|
231
242
|
})
|
|
232
243
|
.catch((e) => {
|
|
233
244
|
if (ctl.signal.aborted)
|
|
234
245
|
return;
|
|
235
246
|
setRead(null);
|
|
236
|
-
setUnread(e
|
|
247
|
+
setUnread(e);
|
|
237
248
|
})
|
|
238
249
|
.finally(() => !ctl.signal.aborted && setReading(false));
|
|
239
250
|
}, 450);
|
|
@@ -241,34 +252,34 @@ function Make({ prompt, onClose, onMade }) {
|
|
|
241
252
|
clearTimeout(id);
|
|
242
253
|
ctl.abort();
|
|
243
254
|
};
|
|
244
|
-
}, [said, open, picking,
|
|
255
|
+
}, [said, open, picking, target]);
|
|
245
256
|
const when = picking ? picked : (read?.schedule ?? null);
|
|
246
|
-
const wrong = when ? unready(when) : '';
|
|
257
|
+
const wrong = when ? t(unready(when)) : '';
|
|
247
258
|
const make = async () => {
|
|
248
259
|
if (!name.trim())
|
|
249
|
-
return setNote('Name it first.');
|
|
260
|
+
return setNote(t('Name it first.'));
|
|
250
261
|
if (!asks.trim())
|
|
251
|
-
return setNote('Say what it should do.');
|
|
262
|
+
return setNote(t('Say what it should do.'));
|
|
252
263
|
if (!when)
|
|
253
|
-
return setNote(said.trim() ? 'Say when in other words, or choose instead.' : 'Say when it runs.');
|
|
264
|
+
return setNote(said.trim() ? t('Say when in other words, or choose instead.') : t('Say when it runs.'));
|
|
254
265
|
if (wrong)
|
|
255
266
|
return setNote(wrong);
|
|
256
267
|
setBusy(true);
|
|
257
268
|
setNote('');
|
|
258
269
|
try {
|
|
259
270
|
const d = { name: name.trim(), instructions: asks.trim(), model, schedule: when, permissions: 'ask', notify: true, postTo: [], enabled: true, source: 'dev' };
|
|
260
|
-
onMade(await create(
|
|
271
|
+
onMade(await create(target, tidy(d)));
|
|
261
272
|
onClose();
|
|
262
273
|
}
|
|
263
274
|
catch (e) {
|
|
264
|
-
setNote(unplanned(e) ? PLAN : e
|
|
275
|
+
setNote(unplanned(e) ? t(PLAN) : told(e, t));
|
|
265
276
|
}
|
|
266
277
|
finally {
|
|
267
278
|
setBusy(false);
|
|
268
279
|
}
|
|
269
280
|
};
|
|
270
|
-
const heard = useMemo(() => (read ? [read.says || words(read.schedule), read.next ?
|
|
271
|
-
return (_jsx(Sheet, { title:
|
|
281
|
+
const heard = useMemo(() => (read ? [read.says || words(read.schedule, t), read.next ? t('next {when}', { when: local(read.next) }) : ''].filter(Boolean).join(' · ') : ''), [read, t]);
|
|
282
|
+
return (_jsx(Sheet, { title: t('New task'), open: open, onOpenChange: (o) => !o && !busy && onClose(), width: 560, children: _jsxs(YStack, { gap: "$4", children: [_jsx(Field, { label: t('Name'), children: _jsx(Input, { value: name, onChangeText: setName, placeholder: t('Monday summary'), "aria-label": t('Task name') }) }), _jsx(Field, { label: t('What it does'), hint: t('Each run is one Dev run in this organization, as you. It reads and proposes; it changes nothing on its own.'), children: _jsx(Textarea, { value: asks, onChangeText: setAsks, placeholder: t('Summarize what changed in my repositories this week, and what needs me.'), "aria-label": t('What it does'), rows: 5 }) }), _jsx(Field, { label: t('When'), hint: picking ? undefined : t('In your own words: every weekday at 8am, every 5 minutes, first of the month.'), children: picking ? (_jsxs(YStack, { gap: "$2", children: [_jsx(When, { s: picked, onChange: setPicked }), _jsx(XStack, { children: _jsx(Button, { size: "sm", variant: "ghost", onPress: () => setPicking(false), children: t('Say it instead') }) })] })) : (_jsxs(YStack, { gap: "$2", children: [_jsx(Input, { value: said, onChangeText: setSaid, placeholder: t('Every Monday at 9am'), "aria-label": t('When it runs') }), _jsx(SizableText, { size: "$1", color: unread ? '$soft' : '$ink', role: "status", minH: 18, children: reading ? t('Reading…') : unread ? `${told(unread, t)} ${t('Choose instead, or say it another way.')}` : heard }), _jsx(XStack, { children: _jsx(Button, { size: "sm", variant: "ghost", onPress: () => setPicking(true), children: t('Choose instead') }) })] })) }), _jsx(Field, { label: t('Model'), children: _jsx(XStack, { children: _jsx(ModelPicker, { size: "sm", name: t('Model'), models: catalog.value, scope: "chat", limits: limits, value: model ?? ENSO, onChange: (id) => setModel(id === ENSO ? null : id), loading: catalog.loading, error: catalog.error ? told(catalog.error, t) : null }) }) }), _jsx(Line, { children: note }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "ghost", disabled: busy, onPress: onClose, children: t('Cancel') }), _jsx(Button, { size: "sm", disabled: busy || !signed, onPress: () => void make(), children: busy ? t('Creating…') : t('Create') })] })] }) }));
|
|
272
283
|
}
|
|
273
284
|
function Badge({ children }) {
|
|
274
285
|
return (_jsx(SizableText, { size: "$1", color: "$soft", px: "$1.5", rounded: "$2", borderWidth: 1, borderColor: "$borderColor", shrink: 0, children: children }));
|
package/lib/section.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export declare function useSessions(host: Host): Read<Session[]>;
|
|
|
16
16
|
export declare function DevSection({ host, onPick, label, children, }: {
|
|
17
17
|
host: Host;
|
|
18
18
|
onPick?: () => void;
|
|
19
|
+
/** The runs' heading; absent, "Runs" in the reader's language. */
|
|
19
20
|
label?: string;
|
|
20
21
|
children?: ReactNode;
|
|
21
22
|
}): import("react").JSX.Element;
|