@hanzo/build 0.2.30 → 0.2.32
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/access.d.ts +4 -0
- package/lib/access.js +16 -0
- package/lib/api/coding.d.ts +29 -3
- package/lib/api/coding.js +42 -10
- package/lib/api/models.d.ts +5 -8
- package/lib/api/models.js +15 -1
- package/lib/api/sandbox.d.ts +41 -8
- package/lib/api/sandbox.js +98 -9
- package/lib/api/turn.d.ts +45 -14
- package/lib/api/turn.js +113 -88
- package/lib/customize/agents.js +3 -2
- package/lib/desk.d.ts +1 -1
- package/lib/desk.js +187 -16
- 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/landing.js +2 -1
- package/lib/out.d.ts +6 -0
- package/lib/out.js +20 -0
- package/lib/project.js +12 -7
- package/lib/run.js +16 -6
- package/lib/settings/code.js +2 -1
- package/lib/transcript.js +11 -23
- package/package.json +3 -3
- package/lib/api/harness.d.ts +0 -62
- package/lib/api/harness.js +0 -556
package/lib/access.d.ts
ADDED
package/lib/access.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Under a model list: the models it lists and cannot choose, and where to ask
|
|
4
|
+
* for them. Nothing while every model listed answers.
|
|
5
|
+
*
|
|
6
|
+
* A link cannot sit inside a listbox option, so it sits here, under the list,
|
|
7
|
+
* where the keyboard reaches it from the search field.
|
|
8
|
+
*/
|
|
9
|
+
import { SizableText, XStack } from '@hanzo/gui';
|
|
10
|
+
import { Out } from './out.js';
|
|
11
|
+
export function Access({ items }) {
|
|
12
|
+
const locked = items.filter((m) => m.disabled && m.request);
|
|
13
|
+
if (!locked.length)
|
|
14
|
+
return null;
|
|
15
|
+
return (_jsxs(XStack, { items: "center", gap: "$2", flexWrap: "wrap", children: [_jsxs(SizableText, { size: "$1", color: "$soft", children: [locked.map((m) => m.label).join(', '), " ", locked.length > 1 ? 'are' : 'is', " in research preview."] }), _jsx(Out, { href: locked[0].request, children: _jsx(SizableText, { size: "$1", color: "$ink", textDecorationLine: "underline", children: "Request access" }) })] }));
|
|
16
|
+
}
|
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
|
}
|
|
@@ -99,3 +103,25 @@ export declare function approve(e: Earlier, plan: string): Ask;
|
|
|
99
103
|
* with none recorded, its title's.
|
|
100
104
|
*/
|
|
101
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,7 +63,6 @@ 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
|
};
|
|
@@ -77,11 +81,11 @@ export function headline(title, repo) {
|
|
|
77
81
|
return t;
|
|
78
82
|
}
|
|
79
83
|
/**
|
|
80
|
-
* The same codebase, place and project as the earlier run
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* 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.
|
|
85
89
|
*/
|
|
86
90
|
function from(e) {
|
|
87
91
|
const place = e.environment && e.environment !== 'sandbox' ? e.environment : undefined;
|
|
@@ -89,7 +93,7 @@ function from(e) {
|
|
|
89
93
|
repo: e.repo || undefined,
|
|
90
94
|
project: e.project || undefined,
|
|
91
95
|
targetId: place,
|
|
92
|
-
...(e.pushed ? { after: e.id } : { base: e.base || undefined }),
|
|
96
|
+
...(!place || e.pushed ? { after: e.id } : { base: e.base || undefined }),
|
|
93
97
|
};
|
|
94
98
|
}
|
|
95
99
|
const modeOf = (m) => (m === 'plan' || m === 'setup' ? m : 'build');
|
|
@@ -127,3 +131,31 @@ export function approve(e, plan) {
|
|
|
127
131
|
export function retry(e, ask) {
|
|
128
132
|
return { ...from(e), prompt: ask.trim() || headline(e.title, e.repo), mode: modeOf(e.mode) };
|
|
129
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/models.d.ts
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The models a run can be routed through, from the platform's own catalog.
|
|
3
|
-
*
|
|
4
|
-
* GET /v1/models → {data: [{id, …}]}
|
|
5
|
-
*
|
|
6
|
-
* `enso` is the router: it picks a model per step, which is why it is the
|
|
7
|
-
* default and why it is not a model a person has to understand to use.
|
|
8
|
-
*/
|
|
9
1
|
import { type Target } from './call.ts';
|
|
10
2
|
export interface Model {
|
|
11
3
|
id: string;
|
|
12
4
|
label: string;
|
|
5
|
+
/** Listed and never chosen. */
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
hint?: string;
|
|
8
|
+
/** Where a person asks for access to a model they cannot call. */
|
|
9
|
+
request?: string;
|
|
13
10
|
}
|
|
14
11
|
export declare const ENSO = "enso";
|
|
15
12
|
/** A readable name for an id: `anthropic/claude-opus-5.5` → `Claude Opus 5.5`, `zen5-coder` → `Zen5 Coder`. */
|
package/lib/api/models.js
CHANGED
|
@@ -5,7 +5,12 @@
|
|
|
5
5
|
*
|
|
6
6
|
* `enso` is the router: it picks a model per step, which is why it is the
|
|
7
7
|
* default and why it is not a model a person has to understand to use.
|
|
8
|
+
*
|
|
9
|
+
* After what the gateway serves come the models nobody can call yet, from
|
|
10
|
+
* @hanzo/ui's `RESEARCH`: listed so a person knows they exist, disabled so no
|
|
11
|
+
* run is ever started on one, with where to ask for access.
|
|
8
12
|
*/
|
|
13
|
+
import { RESEARCH } from '@hanzo/ui/models/catalog';
|
|
9
14
|
import { call } from './call.js';
|
|
10
15
|
export const ENSO = 'enso';
|
|
11
16
|
/** A readable name for an id: `anthropic/claude-opus-5.5` → `Claude Opus 5.5`, `zen5-coder` → `Zen5 Coder`. */
|
|
@@ -24,5 +29,14 @@ export async function models(t) {
|
|
|
24
29
|
.map((r) => (r && typeof r === 'object' && typeof r.id === 'string' ? r.id : ''))
|
|
25
30
|
.filter((id) => id && !id.endsWith(':batch'));
|
|
26
31
|
const out = [...new Set([ENSO, ...ids])];
|
|
27
|
-
return
|
|
32
|
+
return [
|
|
33
|
+
...out.map((id) => ({ id, label: label(id) })),
|
|
34
|
+
...RESEARCH.filter((m) => !out.includes(m.id)).map((m) => ({
|
|
35
|
+
id: m.id,
|
|
36
|
+
label: m.label ?? label(m.id),
|
|
37
|
+
disabled: true,
|
|
38
|
+
hint: 'Research preview',
|
|
39
|
+
request: m.request,
|
|
40
|
+
})),
|
|
41
|
+
];
|
|
28
42
|
}
|
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;
|
|
@@ -80,10 +103,11 @@ export type Ran = 'running' | 'done' | 'error' | 'cancelled';
|
|
|
80
103
|
* One piece of the transcript, drawn by what it is.
|
|
81
104
|
*
|
|
82
105
|
* said prose: the agent's messages (markdown), or a person's steering words
|
|
106
|
+
* think the agent's reasoning, folded until opened
|
|
83
107
|
* shell a command the agent ran, and what it printed
|
|
84
|
-
* edit files the agent changed
|
|
85
|
-
* read a file the agent read, drawn as a chip
|
|
108
|
+
* edit files the agent changed; the diff is the run's Git tab
|
|
86
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
|
|
87
111
|
* note a lifecycle line: started, pushed, stopped
|
|
88
112
|
* plan a plan run's answer, which can be approved into a build
|
|
89
113
|
*/
|
|
@@ -92,6 +116,10 @@ export type Card = {
|
|
|
92
116
|
key: string;
|
|
93
117
|
who: 'person' | 'agent';
|
|
94
118
|
text: string;
|
|
119
|
+
} | {
|
|
120
|
+
kind: 'think';
|
|
121
|
+
key: string;
|
|
122
|
+
text: string;
|
|
95
123
|
} | {
|
|
96
124
|
kind: 'shell';
|
|
97
125
|
key: string;
|
|
@@ -102,12 +130,6 @@ export type Card = {
|
|
|
102
130
|
kind: 'edit';
|
|
103
131
|
key: string;
|
|
104
132
|
files: string[];
|
|
105
|
-
patch: string;
|
|
106
|
-
ran: Ran;
|
|
107
|
-
} | {
|
|
108
|
-
kind: 'read';
|
|
109
|
-
key: string;
|
|
110
|
-
file: string;
|
|
111
133
|
ran: Ran;
|
|
112
134
|
} | {
|
|
113
135
|
kind: 'step';
|
|
@@ -116,6 +138,13 @@ export type Card = {
|
|
|
116
138
|
detail: string;
|
|
117
139
|
output: string;
|
|
118
140
|
ran: Ran;
|
|
141
|
+
} | {
|
|
142
|
+
kind: 'todo';
|
|
143
|
+
key: string;
|
|
144
|
+
items: {
|
|
145
|
+
text: string;
|
|
146
|
+
done: boolean;
|
|
147
|
+
}[];
|
|
119
148
|
} | {
|
|
120
149
|
kind: 'note';
|
|
121
150
|
key: string;
|
|
@@ -129,12 +158,14 @@ export type Card = {
|
|
|
129
158
|
* A run's events, as the cards its transcript draws. `mode` is the run's, from
|
|
130
159
|
* its record: a plan's final status is its plan, and nothing else draws a plan.
|
|
131
160
|
*
|
|
132
|
-
* The run's own steps arrive as `tool-call` {step, message, status}
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
* agent is the agent's
|
|
136
|
-
*
|
|
137
|
-
*
|
|
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.
|
|
138
169
|
*/
|
|
139
170
|
export declare function cards(events: Pick<Event, 'kind' | 'payload' | 'seq'>[], mode?: string): Card[];
|
|
140
171
|
/** The plan a plan run answered with, from its final status, or ''. */
|