@hanzo/build 0.2.29 → 0.2.31

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.
@@ -1,7 +1,9 @@
1
1
  /**
2
- * Starting a coding run.
2
+ * Starting a coding run, and what it left.
3
3
  *
4
- * POST /v1/agent/coding → 202 {sessionId, repo, branch, project, routed, targetId}
4
+ * POST /v1/agent/coding → 202 {sessionId, repo, branch, routed, targetId}
5
+ * GET /v1/agent/coding/{session}/artifacts {session, saved, artifacts}
6
+ * GET /v1/agent/coding/{session}/artifacts/{name} one stored artifact's bytes
5
7
  *
6
8
  * 202 is ADMITTED, not finished: the answer is the session the run narrates
7
9
  * itself in, and the session stream is how it is watched.
@@ -22,6 +24,9 @@
22
24
  * `setup` is a plan's shape put to one question: how this codebase is installed,
23
25
  * started and checked. Its agent may install and run things in its sandbox, and
24
26
  * its answer is kept as the codebase's proposed environment (environment.ts).
27
+ *
28
+ * A sandbox run keeps its sandbox after it ends, so `after` continues the earlier
29
+ * run in that same sandbox and working tree, resumed if it parked.
25
30
  */
26
31
  import { type Target } from './call.ts';
27
32
  export type Mode = 'build' | 'plan' | 'setup';
@@ -52,7 +57,6 @@ export interface Run {
52
57
  session: string;
53
58
  repo: string;
54
59
  branch: string;
55
- project: string;
56
60
  routed: boolean;
57
61
  target: string;
58
62
  }
@@ -78,7 +82,11 @@ export interface Earlier {
78
82
  /** Its work is on its own branch (turn.ts `settled`). */
79
83
  pushed: boolean;
80
84
  }
81
- /** The earlier run's ask, without the codebase its title leads with (`universe: add the widget`). */
85
+ /**
86
+ * The earlier run's ask, without the codebase its title leads with (`universe:
87
+ * add the widget`), or the `new project:` the platform titles a run that made its
88
+ * codebase with (apps/coding start.go).
89
+ */
82
90
  export declare function headline(title: string, repo: string): string;
83
91
  /**
84
92
  * A new run continuing an earlier one in its mode: what the person said, and
@@ -89,3 +97,31 @@ export declare function headline(title: string, repo: string): string;
89
97
  export declare function followUp(e: Earlier, said: string): Ask;
90
98
  /** A build of what a plan run answered: its ask as the title, and the plan to carry out. */
91
99
  export declare function approve(e: Earlier, plan: string): Ask;
100
+ /**
101
+ * The same ask again, where the earlier run worked and in its mode: what a failed
102
+ * run's Try again starts. `ask` is the person's words as the run recorded them;
103
+ * with none recorded, its title's.
104
+ */
105
+ export declare function retry(e: Earlier, ask: string): Ask;
106
+ /** What a run left: a changed file, the whole change as a patch, or a link. */
107
+ export type Kind = 'file' | 'patch' | 'preview' | 'pull' | 'deploy';
108
+ export interface Artifact {
109
+ /** A changed file's path in the workspace, `changes.patch`, or a link's name. */
110
+ name: string;
111
+ kind: Kind;
112
+ /** A stored artifact's length in bytes; 0 for a link. */
113
+ size: number;
114
+ /** Where a link points: a preview's host, a pull request, a deployment. */
115
+ url: string;
116
+ /** A preview's port in the run's sandbox. */
117
+ port: number;
118
+ }
119
+ export interface Left {
120
+ /** When the artifacts were saved, or '' while the run has saved none. */
121
+ saved: string;
122
+ artifacts: Artifact[];
123
+ }
124
+ /** What a run left, kept after its sandbox is gone. */
125
+ export declare function artifacts(t: Target, session: string): Promise<Left>;
126
+ /** One stored artifact's bytes. */
127
+ export declare function artifact(t: Target, session: string, name: string): Promise<Blob>;
package/lib/api/coding.js CHANGED
@@ -1,7 +1,9 @@
1
1
  /**
2
- * Starting a coding run.
2
+ * Starting a coding run, and what it left.
3
3
  *
4
- * POST /v1/agent/coding → 202 {sessionId, repo, branch, project, routed, targetId}
4
+ * POST /v1/agent/coding → 202 {sessionId, repo, branch, routed, targetId}
5
+ * GET /v1/agent/coding/{session}/artifacts {session, saved, artifacts}
6
+ * GET /v1/agent/coding/{session}/artifacts/{name} one stored artifact's bytes
5
7
  *
6
8
  * 202 is ADMITTED, not finished: the answer is the session the run narrates
7
9
  * itself in, and the session stream is how it is watched.
@@ -22,8 +24,11 @@
22
24
  * `setup` is a plan's shape put to one question: how this codebase is installed,
23
25
  * started and checked. Its agent may install and run things in its sandbox, and
24
26
  * its answer is kept as the codebase's proposed environment (environment.ts).
27
+ *
28
+ * A sandbox run keeps its sandbox after it ends, so `after` continues the earlier
29
+ * run in that same sandbox and working tree, resumed if it parked.
25
30
  */
26
- import { call, Refusal } from './call.js';
31
+ import { call, headers, reason, Refusal, seg } from './call.js';
27
32
  /** Why this mode cannot run where it is sent, or '' when it can. */
28
33
  export const unhonoured = (mode, target) => (mode === 'plan' || mode === 'setup') && target?.trim()
29
34
  ? `A ${mode} runs in the Hanzo sandbox: a machine clones and pushes with its own credential. Choose Cloud, or switch to Build.`
@@ -58,23 +63,29 @@ export async function start(t, ask) {
58
63
  session: s('sessionId'),
59
64
  repo: s('repo'),
60
65
  branch: s('branch'),
61
- project: s('project'),
62
66
  routed: r?.routed === true,
63
67
  target: s('targetId'),
64
68
  };
65
69
  }
66
- /** The earlier run's ask, without the codebase its title leads with (`universe: add the widget`). */
70
+ /**
71
+ * The earlier run's ask, without the codebase its title leads with (`universe:
72
+ * add the widget`), or the `new project:` the platform titles a run that made its
73
+ * codebase with (apps/coding start.go).
74
+ */
67
75
  export function headline(title, repo) {
68
76
  const name = repo.split('/').filter(Boolean).pop() ?? '';
69
77
  const t = title.trim();
70
- return name && t.startsWith(`${name}: `) ? t.slice(name.length + 2).trim() : t;
78
+ for (const lead of [name, 'new project'])
79
+ if (lead && t.startsWith(`${lead}: `))
80
+ return t.slice(lead.length + 2).trim();
81
+ return t;
71
82
  }
72
83
  /**
73
- * The same codebase, place and project as the earlier run, starting from its
74
- * branch when it pushed one. `after` IS that branch as the base (BaseOf,
75
- * apps/coding coding.go), so it is named only then: a plan, a setup, or a build
76
- * that changed nothing pushed no branch to clone, and its follow-up starts where
77
- * it did.
84
+ * The same codebase, place and project as the earlier run. In the sandbox it goes
85
+ * on in the earlier run's own sandbox and working tree (`after`), whatever that
86
+ * run pushed. A machine keeps no sandbox: there `after` is the branch the earlier
87
+ * run pushed, named only when it pushed one, and otherwise the follow-up starts
88
+ * where it did.
78
89
  */
79
90
  function from(e) {
80
91
  const place = e.environment && e.environment !== 'sandbox' ? e.environment : undefined;
@@ -82,7 +93,7 @@ function from(e) {
82
93
  repo: e.repo || undefined,
83
94
  project: e.project || undefined,
84
95
  targetId: place,
85
- ...(e.pushed ? { after: e.id } : { base: e.base || undefined }),
96
+ ...(!place || e.pushed ? { after: e.id } : { base: e.base || undefined }),
86
97
  };
87
98
  }
88
99
  const modeOf = (m) => (m === 'plan' || m === 'setup' ? m : 'build');
@@ -112,3 +123,39 @@ export function approve(e, plan) {
112
123
  const ask = `Carry out this plan:\n\n${plan.trim()}`;
113
124
  return { ...from(e), prompt: head ? `${head}\n\n${ask}` : ask, mode: 'build' };
114
125
  }
126
+ /**
127
+ * The same ask again, where the earlier run worked and in its mode: what a failed
128
+ * run's Try again starts. `ask` is the person's words as the run recorded them;
129
+ * with none recorded, its title's.
130
+ */
131
+ export function retry(e, ask) {
132
+ return { ...from(e), prompt: ask.trim() || headline(e.title, e.repo), mode: modeOf(e.mode) };
133
+ }
134
+ const KINDS = ['file', 'patch', 'preview', 'pull', 'deploy'];
135
+ /** What a run left, kept after its sandbox is gone. */
136
+ export async function artifacts(t, session) {
137
+ const r = (await call(t, 'GET', `/v1/agent/coding/${seg(session)}/artifacts`)) ?? {};
138
+ const list = Array.isArray(r.artifacts) ? r.artifacts : [];
139
+ return {
140
+ saved: typeof r.saved === 'string' ? r.saved : '',
141
+ artifacts: list.flatMap((raw) => {
142
+ const a = raw && typeof raw === 'object' ? raw : {};
143
+ const kind = KINDS.find((k) => k === a.kind);
144
+ const name = typeof a.name === 'string' ? a.name : '';
145
+ if (!kind || !name)
146
+ return [];
147
+ return [{ name, kind, size: typeof a.size === 'number' ? a.size : 0, url: typeof a.url === 'string' ? a.url : '', port: typeof a.port === 'number' ? a.port : 0 }];
148
+ }),
149
+ };
150
+ }
151
+ /** One stored artifact's bytes. */
152
+ export async function artifact(t, session, name) {
153
+ const parts = name.split('/');
154
+ if (parts.some((p) => p === '' || p === '.' || p === '..'))
155
+ throw new Refusal(400, 'That artifact has no name to fetch it by');
156
+ const res = await fetch(`${t.api}/v1/agent/coding/${seg(session)}/artifacts/${parts.map(seg).join('/')}`, { headers: headers(t), cache: 'no-store' });
157
+ if (!res.ok)
158
+ throw new Refusal(res.status, (await reason(res)) || `The artifact answered ${res.status}`);
159
+ // Bytes, whatever type the store named: a page is saved, never opened here.
160
+ return new Blob([await res.arrayBuffer()], { type: 'application/octet-stream' });
161
+ }
@@ -1,20 +1,53 @@
1
1
  /**
2
- * A run's sandbox while the run holds it: a door into its screen or its shell,
3
- * and its working tree as it is right now.
2
+ * A run's sandbox. It outlives the run: kept after the run ends, parked (its pod
3
+ * stopped, its disk and working tree kept) while nobody watches, and retired a
4
+ * day after it parks; a free plan's sandbox is not parked, and ends within the
5
+ * hour. Resume gives a parked one a pod again under the same id and tree.
4
6
  *
5
- * POST /v1/sandbox/{id}/{screen|terminal}/ticket one ticket, spent when the page's socket opens (30 s)
6
- * POST /v1/sandbox/read {id, path} a directory's names, or a file's bytes
7
+ * GET /v1/sandbox/{id} its status and when its lease ends
8
+ * POST /v1/sandbox/{id}/pause park it: stop the pod, keep the disk
9
+ * POST /v1/sandbox/{id}/resume a pod again, same id and /work
10
+ * POST /v1/sandbox/{id}/{screen|terminal}/ticket one ticket, spent when the page's socket opens (30 s)
11
+ * GET /v1/sandbox/{id}/ports what listens in it, each with its preview host
12
+ * POST /v1/sandbox/{id}/preview {port} an address that opens that port in a browser
13
+ * POST /v1/sandbox/read {id, path} a directory's names, or a file's bytes
7
14
  *
8
- * The door is cloud's own page — noVNC for the screen, xterm for the shell — so
9
- * the builder frames it and never speaks the socket itself. The sandbox is gone
10
- * when the run ends, and every call here then answers 404.
15
+ * A door or a preview asked of a sandbox that stopped as it was asked is 409: it
16
+ * is resumed, and asked again once.
11
17
  */
12
18
  import { type Target } from './call.ts';
13
19
  export type Door = 'screen' | 'terminal';
20
+ /** Where a sandbox is in its life. */
21
+ export type State = 'pending' | 'running' | 'parked' | 'error' | 'gone';
22
+ export interface Box {
23
+ id: string;
24
+ state: State;
25
+ /** When its lease ends, or a parked sandbox is retired; 0 when unknown. Unix ms. */
26
+ ends: number;
27
+ /** Why it could not come up, with state `error`. */
28
+ error: string;
29
+ }
30
+ /** The sandbox as it stands. One the caller no longer holds is gone. */
31
+ export declare function sandbox(t: Target, id: string): Promise<Box>;
32
+ /** Park it: its pod stops, its disk and working tree stay. */
33
+ export declare function park(t: Target, id: string): Promise<Box>;
34
+ /** Give it a pod again, under the same id and tree. */
35
+ export declare function wake(t: Target, id: string): Promise<Box>;
14
36
  /** A shell's tmux session name: the sandbox reattaches to it when the door opens again. */
15
37
  export declare const NAME: RegExp;
16
38
  /** Where to frame one door, with a fresh ticket in its address. */
17
- export declare function door(t: Target, sandbox: string, which: Door, session?: string): Promise<string>;
39
+ export declare function door(t: Target, id: string, which: Door, session?: string): Promise<string>;
40
+ export interface Port {
41
+ port: number;
42
+ host: string;
43
+ }
44
+ /** What listens in the sandbox, ascending. */
45
+ export declare function ports(t: Target, id: string): Promise<Port[]>;
46
+ /**
47
+ * An address that opens one port in a browser: its own origin, with a single-use
48
+ * ticket that sets the preview's cookie and lands on its root.
49
+ */
50
+ export declare function preview(t: Target, id: string, port: number): Promise<string>;
18
51
  export interface Node {
19
52
  /** The resolved path inside the sandbox. */
20
53
  path: string;
@@ -1,26 +1,115 @@
1
1
  /**
2
- * A run's sandbox while the run holds it: a door into its screen or its shell,
3
- * and its working tree as it is right now.
2
+ * A run's sandbox. It outlives the run: kept after the run ends, parked (its pod
3
+ * stopped, its disk and working tree kept) while nobody watches, and retired a
4
+ * day after it parks; a free plan's sandbox is not parked, and ends within the
5
+ * hour. Resume gives a parked one a pod again under the same id and tree.
4
6
  *
5
- * POST /v1/sandbox/{id}/{screen|terminal}/ticket one ticket, spent when the page's socket opens (30 s)
6
- * POST /v1/sandbox/read {id, path} a directory's names, or a file's bytes
7
+ * GET /v1/sandbox/{id} its status and when its lease ends
8
+ * POST /v1/sandbox/{id}/pause park it: stop the pod, keep the disk
9
+ * POST /v1/sandbox/{id}/resume a pod again, same id and /work
10
+ * POST /v1/sandbox/{id}/{screen|terminal}/ticket one ticket, spent when the page's socket opens (30 s)
11
+ * GET /v1/sandbox/{id}/ports what listens in it, each with its preview host
12
+ * POST /v1/sandbox/{id}/preview {port} an address that opens that port in a browser
13
+ * POST /v1/sandbox/read {id, path} a directory's names, or a file's bytes
7
14
  *
8
- * The door is cloud's own page — noVNC for the screen, xterm for the shell — so
9
- * the builder frames it and never speaks the socket itself. The sandbox is gone
10
- * when the run ends, and every call here then answers 404.
15
+ * A door or a preview asked of a sandbox that stopped as it was asked is 409: it
16
+ * is resumed, and asked again once.
11
17
  */
12
18
  import { call, Refusal, seg } from './call.js';
19
+ const STATES = ['pending', 'running', 'parked', 'error'];
20
+ function box(raw, id) {
21
+ const o = raw && typeof raw === 'object' ? raw : {};
22
+ // A parked sandbox is also said to be paused; a status this does not know is not one to open.
23
+ const state = STATES.find((s) => s === (o.status === 'paused' ? 'parked' : o.status)) ?? 'error';
24
+ const ends = typeof o.expiresAt === 'number' && o.expiresAt > 0 ? o.expiresAt * 1000 : 0;
25
+ return { id, state, ends, error: typeof o.error === 'string' ? o.error : '' };
26
+ }
27
+ /** The sandbox as it stands. One the caller no longer holds is gone. */
28
+ export async function sandbox(t, id) {
29
+ try {
30
+ return box(await call(t, 'GET', `/v1/sandbox/${seg(id)}`), id);
31
+ }
32
+ catch (e) {
33
+ if (e instanceof Refusal && e.status === 404)
34
+ return { id, state: 'gone', ends: 0, error: '' };
35
+ throw e;
36
+ }
37
+ }
38
+ /** Park it: its pod stops, its disk and working tree stay. */
39
+ export async function park(t, id) {
40
+ return box(await call(t, 'POST', `/v1/sandbox/${seg(id)}/pause`), id);
41
+ }
42
+ /** Give it a pod again, under the same id and tree. */
43
+ export async function wake(t, id) {
44
+ return box(await call(t, 'POST', `/v1/sandbox/${seg(id)}/resume`), id);
45
+ }
46
+ /** How long a resumed sandbox is waited on to run. */
47
+ const UP = 90_000;
48
+ /** Resume it and wait until it runs. */
49
+ async function up(t, id) {
50
+ let b = await wake(t, id);
51
+ const until = Date.now() + UP;
52
+ while (b.state === 'pending' && Date.now() < until) {
53
+ await new Promise((r) => setTimeout(r, 1500));
54
+ b = await sandbox(t, id);
55
+ }
56
+ if (b.state !== 'running')
57
+ throw new Refusal(503, b.error || 'The sandbox did not start again. Try again.');
58
+ }
59
+ /** Asks what needs a running sandbox; a parked one is resumed first. */
60
+ async function running(t, id, ask) {
61
+ try {
62
+ return await ask();
63
+ }
64
+ catch (e) {
65
+ if (!(e instanceof Refusal) || e.status !== 409)
66
+ throw e;
67
+ await up(t, id);
68
+ return ask();
69
+ }
70
+ }
13
71
  /** A shell's tmux session name: the sandbox reattaches to it when the door opens again. */
14
72
  export const NAME = /^[A-Za-z0-9_][A-Za-z0-9_-]{0,63}$/;
15
73
  /** Where to frame one door, with a fresh ticket in its address. */
16
- export async function door(t, sandbox, which, session = '') {
17
- const r = await call(t, 'POST', `/v1/sandbox/${seg(sandbox)}/${which}/ticket`);
74
+ export async function door(t, id, which, session = '') {
75
+ const r = await running(t, id, () => call(t, 'POST', `/v1/sandbox/${seg(id)}/${which}/ticket`));
18
76
  const path = typeof r?.url === 'string' ? r.url : '';
19
77
  if (!path.startsWith('/v1/'))
20
78
  throw new Refusal(502, 'The sandbox answered with no address to open');
21
79
  const arg = which === 'terminal' && NAME.test(session) ? `&arg=${session}` : '';
22
80
  return `${t.api}${path}${arg}`;
23
81
  }
82
+ /** What listens in the sandbox, ascending. */
83
+ export async function ports(t, id) {
84
+ const r = await call(t, 'GET', `/v1/sandbox/${seg(id)}/ports`);
85
+ const list = Array.isArray(r?.ports) ? r.ports : [];
86
+ return list
87
+ .map((p) => (p && typeof p === 'object' ? p : {}))
88
+ .filter((p) => typeof p.port === 'number' && p.port > 0 && p.port < 65536)
89
+ .map((p) => ({
90
+ port: p.port,
91
+ host: typeof p.host === 'string' ? p.host : '',
92
+ }));
93
+ }
94
+ /**
95
+ * An address that opens one port in a browser: its own origin, with a single-use
96
+ * ticket that sets the preview's cookie and lands on its root.
97
+ */
98
+ export async function preview(t, id, port) {
99
+ const r = await running(t, id, () => call(t, 'POST', `/v1/sandbox/${seg(id)}/preview`, {
100
+ port,
101
+ }));
102
+ let url;
103
+ try {
104
+ url = new URL(typeof r?.url === 'string' ? r.url : '');
105
+ }
106
+ catch {
107
+ throw new Refusal(502, 'The sandbox answered with no address to open');
108
+ }
109
+ if (url.protocol !== 'https:')
110
+ throw new Refusal(502, 'The sandbox answered with no address to open');
111
+ return url.href;
112
+ }
24
113
  /** What a file shows at most. */
25
114
  const CAP = 1 << 20;
26
115
  /** One path in the sandbox, relative to its working directory. */
package/lib/api/turn.d.ts CHANGED
@@ -1,3 +1,26 @@
1
+ /**
2
+ * What a run's events SAY, as text a person reads.
3
+ *
4
+ * A curated projection, never a dump: an event carries the machinery a run
5
+ * happened on — hosts, paths, whole file bodies — and printing it would put
6
+ * someone's filesystem in front of anyone who can read the run. Each kind gets a
7
+ * sentence built from the few fields that describe the outcome; the rest is
8
+ * dropped by omission, because a projection cannot leak a field it never reads.
9
+ *
10
+ * Every string here is rendered as TEXT by the transcript. Nothing is HTML.
11
+ *
12
+ * The coding plane's vocabulary (apps/coding): `status` for a lifecycle move
13
+ * (started, routed, done, error, stopped, paused), `tool-call` for a step of the
14
+ * run itself, `event` for one line of the agent's own structured narration (the
15
+ * harness's JSON stream: its messages, reasoning, commands, edits, tool calls and
16
+ * plan), and `log` for raw output, which only the Terminal's log shows. A
17
+ * person's steering is a `control` {command, message}. A run on a project with a
18
+ * site ends `done` with `live` when it published what it pushed, or `unpublished`
19
+ * saying why it did not.
20
+ *
21
+ * `cards` is the transcript as it is drawn: the run's steps and the agent's
22
+ * items, never its log.
23
+ */
1
24
  import type { Event } from './sessions.ts';
2
25
  /** The payload as an object. A live frame may carry it as a JSON string. */
3
26
  export declare function decode(payload: unknown): Record<string, unknown> | string | null;
@@ -56,6 +79,8 @@ export interface Outcome {
56
79
  * the branch and the pull request are read from the run's record (`pull`).
57
80
  */
58
81
  export declare function outcome(events: Pick<Event, 'kind' | 'payload' | 'seq'>[]): Outcome;
82
+ /** Why the run failed, as its last status says it, or '' when that status is not a failure. */
83
+ export declare function failure(events: Pick<Event, 'kind' | 'payload' | 'seq'>[]): string;
59
84
  /** The turns, ordered and deduped: a recorded read and the live feed overlap. */
60
85
  export declare function merge<T extends Pick<Event, 'id' | 'seq'>>(...lists: T[][]): T[];
61
86
  /** The actor, short enough to label a block: `hanzo/2d4d67ab`, not the whole subject. */
@@ -78,10 +103,11 @@ export type Ran = 'running' | 'done' | 'error' | 'cancelled';
78
103
  * One piece of the transcript, drawn by what it is.
79
104
  *
80
105
  * said prose: the agent's messages (markdown), or a person's steering words
106
+ * think the agent's reasoning, folded until opened
81
107
  * shell a command the agent ran, and what it printed
82
- * edit files the agent changed, with the diff when the harness printed one
83
- * read a file the agent read, drawn as a chip
108
+ * edit files the agent changed; the diff is the run's Git tab
84
109
  * step a step of the run itself (lease, clone, install, push), or a tool the agent called
110
+ * todo the agent's plan, as a checklist it ticks
85
111
  * note a lifecycle line: started, pushed, stopped
86
112
  * plan a plan run's answer, which can be approved into a build
87
113
  */
@@ -90,6 +116,10 @@ export type Card = {
90
116
  key: string;
91
117
  who: 'person' | 'agent';
92
118
  text: string;
119
+ } | {
120
+ kind: 'think';
121
+ key: string;
122
+ text: string;
93
123
  } | {
94
124
  kind: 'shell';
95
125
  key: string;
@@ -100,12 +130,6 @@ export type Card = {
100
130
  kind: 'edit';
101
131
  key: string;
102
132
  files: string[];
103
- patch: string;
104
- ran: Ran;
105
- } | {
106
- kind: 'read';
107
- key: string;
108
- file: string;
109
133
  ran: Ran;
110
134
  } | {
111
135
  kind: 'step';
@@ -114,6 +138,13 @@ export type Card = {
114
138
  detail: string;
115
139
  output: string;
116
140
  ran: Ran;
141
+ } | {
142
+ kind: 'todo';
143
+ key: string;
144
+ items: {
145
+ text: string;
146
+ done: boolean;
147
+ }[];
117
148
  } | {
118
149
  kind: 'note';
119
150
  key: string;
@@ -127,12 +158,14 @@ export type Card = {
127
158
  * A run's events, as the cards its transcript draws. `mode` is the run's, from
128
159
  * its record: a plan's final status is its plan, and nothing else draws a plan.
129
160
  *
130
- * The run's own steps arrive as `tool-call` {step, message, status}; each
131
- * command in the sandbox narrates its output as `log` {message} chunks and ends
132
- * with `tool-call` {step: "exit"}. The output after the step that starts the
133
- * agent is the agent's, read by its harness's grammar (harness.ts); after any
134
- * other step it is that step's. Once the agent has exited, the commands the run
135
- * runs before it pushes commit its work.
161
+ * The run's own steps arrive as `tool-call` {step, message, status}. The agent
162
+ * narrates itself as `event` lines: an item is started, updated and completed
163
+ * under one id, and its card is drawn once and kept current. Ids count from zero
164
+ * in each agent process, so an id is the agent's within the thread it started. A
165
+ * turn that fails is one plain line with the detail folded beneath it, drawn once
166
+ * however many times it is said; an `error` on its own is the agent retrying and
167
+ * is drawn only if the turn then fails. The final status carries the agent's
168
+ * answer, drawn once. `log` is never drawn here.
136
169
  */
137
170
  export declare function cards(events: Pick<Event, 'kind' | 'payload' | 'seq'>[], mode?: string): Card[];
138
171
  /** The plan a plan run answered with, from its final status, or ''. */