@hanzo/build 0.2.2 → 0.2.4

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.
@@ -5,7 +5,8 @@
5
5
  * GET /v1/agent/sessions/{id} + the 50 most recent events
6
6
  * POST /v1/agent/sessions/{id}/message {message} steer a running run
7
7
  * POST /v1/agent/sessions/{id}/stop {message} end it, work kept
8
- * GET /v1/agent/sessions/stream?root={id} SSE: `session` and `event` frames
8
+ * GET /v1/agent/sessions/stream?root={id} SSE: `session` and `event` frames,
9
+ * each wrapped: {"session":{…}}, {"event":{…}}
9
10
  *
10
11
  * The stream is best-effort by the platform's own statement: a subscriber that
11
12
  * falls 256 frames behind is dropped. So `watch` reconnects, and every
@@ -30,6 +31,8 @@ export interface Session {
30
31
  branch: string;
31
32
  /** 'sandbox', or the id of the org's machine it runs on. */
32
33
  environment: string;
34
+ /** 'build' or 'plan', as the coding service recorded it; '' for other runs. */
35
+ mode: string;
33
36
  /** The pull request the run proposed, or ''. */
34
37
  pr: string;
35
38
  events: number;
@@ -5,7 +5,8 @@
5
5
  * GET /v1/agent/sessions/{id} + the 50 most recent events
6
6
  * POST /v1/agent/sessions/{id}/message {message} steer a running run
7
7
  * POST /v1/agent/sessions/{id}/stop {message} end it, work kept
8
- * GET /v1/agent/sessions/stream?root={id} SSE: `session` and `event` frames
8
+ * GET /v1/agent/sessions/stream?root={id} SSE: `session` and `event` frames,
9
+ * each wrapped: {"session":{…}}, {"event":{…}}
9
10
  *
10
11
  * The stream is best-effort by the platform's own statement: a subscriber that
11
12
  * falls 256 frames behind is dropped. So `watch` reconnects, and every
@@ -31,6 +32,7 @@ export function session(raw) {
31
32
  base: str(s.base),
32
33
  branch: str(s.branch),
33
34
  environment: str(s.environment),
35
+ mode: str(s.mode),
34
36
  pr: str(s.pr),
35
37
  events: num(s.events),
36
38
  createdAt: str(s.createdAt),
@@ -99,9 +101,9 @@ export async function watch(t, root, on, signal) {
99
101
  return;
100
102
  }
101
103
  if (f.event === 'session')
102
- on.session?.(session(data));
104
+ on.session?.(session(obj(data).session));
103
105
  else if (f.event === 'event')
104
- on.event?.(event(data));
106
+ on.event?.(event(obj(data).event));
105
107
  }, signal);
106
108
  }
107
109
  catch (e) {
package/lib/api/turn.d.ts CHANGED
@@ -16,8 +16,11 @@
16
16
  import type { Event } from './sessions.ts';
17
17
  /** The payload as an object. A live frame may carry it as a JSON string. */
18
18
  export declare function decode(payload: unknown): Record<string, unknown> | string | null;
19
- /** One event's sentence, or '' when it says nothing a person needs. */
20
- export declare function said(e: Pick<Event, 'kind' | 'payload'>): string;
19
+ /**
20
+ * One event's sentence, or '' when it says nothing a person needs. `mode` is
21
+ * the run's, from its record: a plan run's final status says the plan.
22
+ */
23
+ export declare function said(e: Pick<Event, 'kind' | 'payload'>, mode?: string): string;
21
24
  export interface Outcome {
22
25
  /** The last lifecycle status the run narrated, or ''. */
23
26
  status: string;
package/lib/api/turn.js CHANGED
@@ -23,7 +23,7 @@ const base = (path) => {
23
23
  const cut = path.replace(/\/+$/, '');
24
24
  return cut.slice(cut.lastIndexOf('/') + 1);
25
25
  };
26
- function status(b) {
26
+ function status(b, mode) {
27
27
  const branch = str(b.branch);
28
28
  switch (str(b.status)) {
29
29
  case 'started':
@@ -32,7 +32,9 @@ function status(b) {
32
32
  return 'Sent to a machine';
33
33
  case 'done': {
34
34
  // A plan's answer IS its final status: the run read and wrote nothing.
35
- if (str(b.mode) === 'plan')
35
+ // Whether the run planned is the RECORD's word — any member of the org
36
+ // can append an event saying `mode: plan`, and its text is not an answer.
37
+ if (mode === 'plan')
36
38
  return str(b.plan) || 'Planned — the run answered with no plan';
37
39
  if (b.changed === false)
38
40
  return 'Done — nothing to change';
@@ -49,15 +51,18 @@ function status(b) {
49
51
  }
50
52
  return str(b.status);
51
53
  }
52
- /** One event's sentence, or '' when it says nothing a person needs. */
53
- export function said(e) {
54
+ /**
55
+ * One event's sentence, or '' when it says nothing a person needs. `mode` is
56
+ * the run's, from its record: a plan run's final status says the plan.
57
+ */
58
+ export function said(e, mode = '') {
54
59
  const body = decode(e.payload);
55
60
  if (typeof body === 'string')
56
61
  return body;
57
62
  if (!body)
58
63
  return '';
59
64
  if (e.kind === 'status')
60
- return status(body);
65
+ return status(body, mode);
61
66
  for (const key of ['message', 'text', 'content', 'result', 'command']) {
62
67
  const v = body[key];
63
68
  if (typeof v === 'string' && v)
package/lib/data.js CHANGED
@@ -172,6 +172,7 @@ export function useRun(t, id) {
172
172
  title: pushed.title || base.title,
173
173
  branch: pushed.branch || base.branch,
174
174
  pr: pushed.pr || base.pr,
175
+ mode: pushed.mode || base.mode,
175
176
  };
176
177
  }, [read, frame]);
177
178
  return { detail, record, events, status: record?.status ?? '', refused };
package/lib/project.js CHANGED
@@ -133,13 +133,13 @@ export function Project({ slug }) {
133
133
  .filter((e) => e.kind === 'log' || e.kind === 'tool-call' || e.kind === 'status')
134
134
  .map((e) => ({
135
135
  id: `r${e.seq || e.id}`,
136
- level: e.kind === 'status' && /error/.test(said(e).toLowerCase()) ? 'error' : e.kind === 'tool-call' ? 'info' : 'log',
137
- text: said(e),
136
+ level: e.kind === 'status' && /error/.test(said(e, run.record?.mode).toLowerCase()) ? 'error' : e.kind === 'tool-call' ? 'info' : 'log',
137
+ text: said(e, run.record?.mode),
138
138
  source: e.kind === 'status' ? 'run' : who(e.actor),
139
139
  }))
140
140
  .filter((l) => l.text),
141
141
  ...pageLines,
142
- ], [run.events, pageLines]);
142
+ ], [run.events, run.record?.mode, pageLines]);
143
143
  const onBridge = (e) => {
144
144
  if (e.type === 'preview:ready') {
145
145
  setBridge(true);
@@ -227,7 +227,7 @@ export function Project({ slug }) {
227
227
  }
228
228
  const chat = (_jsxs(YStack, { flex: 1, minH: 0, children: [_jsx(YStack, { flex: 1, minH: 0, overflow: "scroll", px: "$3", py: "$4", gap: "$4", children: ordered.length === 0 ? (_jsx(SizableText, { size: "$2", color: "$ink", px: "$2", children: runs.loading
229
229
  ? 'Reading this project’s runs…'
230
- : `${project?.name ?? slug} is loaded — it is in the preview, and every run on it is under the clock above. Say what to change and it gets built.` })) : (ordered.map((r) => (_jsxs(YStack, { gap: "$2", children: [_jsx(Turn, { run: r, open: r.id === current, onOpen: () => setChosen(r.id), project: slug }), r.id === current ? (_jsxs(YStack, { gap: "$1", pl: "$2", borderLeftWidth: 1, borderColor: "$borderColor", children: [fold(run.events.map((e) => ({ kind: e.kind, actor: who(e.actor), seq: e.seq, id: e.id, text: said(e) })).filter((b) => b.text)).map((b) => (_jsx(SizableText, { size: "$1", color: "$soft", children: b.text }, b.key))), pr.href ? (_jsxs(Out, { href: pr.href, label: `Open pull request ${pr.label}`, children: [_jsxs(SizableText, { size: "$1", color: "$ink", textDecorationLine: "underline", children: ["Pull request ", pr.label] }), _jsx(ExternalLink, { size: 11 })] })) : null] })) : null] }, r.id)))) }), _jsxs(YStack, { px: "$3", pb: "$3", gap: "$2", children: [!dismissed && !running ? _jsx(Suggestions, { items: SUGGESTIONS, onPick: (s) => void send(s), onDismiss: () => setDismissed(true) }) : null, note || unhonoured(mode) ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: note || unhonoured(mode) })) : null, picked.length ? _jsx(Attachments, { items: picked, onRemove: () => setPicked([]) }) : null, attached.length ? (_jsx(XStack, { gap: 6, flexWrap: "wrap", children: _jsx(Files, { files: attached, onFiles: setAttached }) })) : null, _jsxs(Composer, { value: draft, onChange: setDraft, onSend: () => void (running ? steer() : send()), onStop: current ? () => void stop(t, current).then(() => setNote('Stop requested — the run keeps its work on its branch')) : undefined, busy: busy || (running && !draft.trim()), rows: 3, placeholder: running ? 'Steer this run' : 'Ask Hanzo for edits', label: "Ask Hanzo for edits", children: [_jsx(Attach, { files: attached, onFiles: setAttached, onNote: setNote }), _jsx(XStack, { flex: 1 }), _jsx(ModeSelect, { modes: MODES, value: mode, onChange: (m) => setMode(m), self: "center" }), _jsx(Dictate, { onText: (said) => setDraft((d) => (d ? `${d} ${said}` : said)), onNote: setNote })] })] })] }));
230
+ : `${project?.name ?? slug} is loaded — it is in the preview, and every run on it is under the clock above. Say what to change and it gets built.` })) : (ordered.map((r) => (_jsxs(YStack, { gap: "$2", children: [_jsx(Turn, { run: r, open: r.id === current, onOpen: () => setChosen(r.id), project: slug }), r.id === current ? (_jsxs(YStack, { gap: "$1", pl: "$2", borderLeftWidth: 1, borderColor: "$borderColor", children: [fold(run.events.map((e) => ({ kind: e.kind, actor: who(e.actor), seq: e.seq, id: e.id, text: said(e, run.record?.mode) })).filter((b) => b.text)).map((b) => (_jsx(SizableText, { size: "$1", color: "$soft", children: b.text }, b.key))), pr.href ? (_jsxs(Out, { href: pr.href, label: `Open pull request ${pr.label}`, children: [_jsxs(SizableText, { size: "$1", color: "$ink", textDecorationLine: "underline", children: ["Pull request ", pr.label] }), _jsx(ExternalLink, { size: 11 })] })) : null] })) : null] }, r.id)))) }), _jsxs(YStack, { px: "$3", pb: "$3", gap: "$2", children: [!dismissed && !running ? _jsx(Suggestions, { items: SUGGESTIONS, onPick: (s) => void send(s), onDismiss: () => setDismissed(true) }) : null, note || unhonoured(mode) ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: note || unhonoured(mode) })) : null, picked.length ? _jsx(Attachments, { items: picked, onRemove: () => setPicked([]) }) : null, attached.length ? (_jsx(XStack, { gap: 6, flexWrap: "wrap", children: _jsx(Files, { files: attached, onFiles: setAttached }) })) : null, _jsxs(Composer, { value: draft, onChange: setDraft, onSend: () => void (running ? steer() : send()), onStop: current ? () => void stop(t, current).then(() => setNote('Stop requested — the run keeps its work on its branch')) : undefined, busy: busy || (running && !draft.trim()), rows: 3, placeholder: running ? 'Steer this run' : 'Ask Hanzo for edits', label: "Ask Hanzo for edits", children: [_jsx(Attach, { files: attached, onFiles: setAttached, onNote: setNote }), _jsx(XStack, { flex: 1 }), _jsx(ModeSelect, { modes: MODES, value: mode, onChange: (m) => setMode(m), self: "center" }), _jsx(Dictate, { onText: (said) => setDraft((d) => (d ? `${d} ${said}` : said)), onNote: setNote })] })] })] }));
231
231
  const body = view === 'preview' ? (_jsx(PreviewFrame, { ref: frame, src: src, title: `${project?.name ?? slug} preview`, device: device, onBridge: onBridge, empty: _jsxs(YStack, { items: "center", gap: "$2", p: "$6", children: [_jsx(SizableText, { size: "$3", color: "$ink", children: "Nothing deployed yet" }), _jsx(SizableText, { size: "$2", color: "$soft", text: "center", children: "Publish this project and its page appears here." })] }), toolbar: bridge ? (_jsx(XStack, { render: "button", "aria-pressed": editing, "aria-label": editing ? 'Stop picking elements' : 'Pick an element to edit', onPress: () => {
232
232
  const next = !editing;
233
233
  setEditing(next);
package/lib/run.js CHANGED
@@ -29,8 +29,8 @@ export function Run({ id }) {
29
29
  const [note, setNote] = useState('');
30
30
  const [busy, setBusy] = useState(false);
31
31
  const blocks = useMemo(() => fold(events
32
- .map((e) => ({ kind: e.kind, actor: who(e.actor), seq: e.seq, id: e.id, text: said(e) }))
33
- .filter((b) => b.text)), [events]);
32
+ .map((e) => ({ kind: e.kind, actor: who(e.actor), seq: e.seq, id: e.id, text: said(e, record?.mode) }))
33
+ .filter((b) => b.text)), [events, record?.mode]);
34
34
  const end = outcome(events);
35
35
  const state = status || end.status;
36
36
  const pr = pull(record?.pr ?? '', record?.repo ?? '');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/build",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
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 page (hanzo.build) and as a component (hanzo.ai/dev).",