@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.
- package/lib/api/coding.d.ts +40 -4
- package/lib/api/coding.js +59 -12
- package/lib/api/sandbox.d.ts +41 -8
- package/lib/api/sandbox.js +98 -9
- package/lib/api/turn.d.ts +47 -14
- package/lib/api/turn.js +129 -89
- package/lib/desk.d.ts +1 -1
- package/lib/desk.js +191 -19
- package/lib/door.d.ts +7 -3
- package/lib/door.js +18 -7
- package/lib/files.d.ts +4 -1
- package/lib/files.js +61 -13
- package/lib/out.d.ts +6 -0
- package/lib/out.js +20 -0
- package/lib/project.js +12 -7
- package/lib/run.js +19 -5
- package/lib/transcript.js +12 -24
- package/package.json +1 -1
- package/lib/api/harness.d.ts +0 -62
- package/lib/api/harness.js +0 -556
package/lib/api/coding.d.ts
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,
|
|
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
|
-
/**
|
|
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,
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
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
|
+
}
|
package/lib/api/sandbox.d.ts
CHANGED
|
@@ -1,20 +1,53 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* A run's sandbox
|
|
3
|
-
*
|
|
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
|
-
*
|
|
6
|
-
* POST /v1/sandbox/
|
|
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
|
-
*
|
|
9
|
-
*
|
|
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,
|
|
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;
|
package/lib/api/sandbox.js
CHANGED
|
@@ -1,26 +1,115 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* A run's sandbox
|
|
3
|
-
*
|
|
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
|
-
*
|
|
6
|
-
* POST /v1/sandbox/
|
|
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
|
-
*
|
|
9
|
-
*
|
|
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,
|
|
17
|
-
const r = await call(t, 'POST', `/v1/sandbox/${seg(
|
|
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
|
|
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}
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
* agent is the agent's
|
|
134
|
-
*
|
|
135
|
-
*
|
|
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 ''. */
|