@hanzo/build 0.2.9 → 0.2.11
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 +126 -23
- package/lib/api/agents.d.ts +80 -0
- package/lib/api/agents.js +148 -0
- package/lib/api/billing.d.ts +205 -0
- package/lib/api/billing.js +300 -0
- package/lib/api/call.d.ts +6 -0
- package/lib/api/call.js +11 -0
- package/lib/api/capabilities.d.ts +25 -0
- package/lib/api/capabilities.js +40 -0
- package/lib/api/changes.d.ts +56 -0
- package/lib/api/changes.js +63 -0
- package/lib/api/coding.d.ts +42 -4
- package/lib/api/coding.js +62 -3
- package/lib/api/connectors.d.ts +90 -0
- package/lib/api/connectors.js +109 -0
- package/lib/api/consent.d.ts +22 -0
- package/lib/api/consent.js +28 -0
- package/lib/api/environment.d.ts +57 -0
- package/lib/api/environment.js +85 -0
- package/lib/api/git.d.ts +6 -2
- package/lib/api/git.js +13 -5
- package/lib/api/github.d.ts +13 -0
- package/lib/api/github.js +9 -0
- package/lib/api/harness.d.ts +44 -0
- package/lib/api/harness.js +348 -0
- package/lib/api/keys.d.ts +41 -0
- package/lib/api/keys.js +55 -0
- package/lib/api/machines.d.ts +56 -0
- package/lib/api/machines.js +84 -0
- package/lib/api/members.d.ts +47 -0
- package/lib/api/members.js +88 -0
- package/lib/api/memory.d.ts +25 -0
- package/lib/api/memory.js +34 -0
- package/lib/api/places.d.ts +2 -2
- package/lib/api/places.js +3 -26
- package/lib/api/plugins.d.ts +55 -0
- package/lib/api/plugins.js +62 -0
- package/lib/api/pref.d.ts +49 -0
- package/lib/api/pref.js +68 -0
- package/lib/api/profile.d.ts +21 -0
- package/lib/api/profile.js +40 -0
- package/lib/api/projects.d.ts +19 -2
- package/lib/api/projects.js +29 -4
- package/lib/api/provider.d.ts +42 -0
- package/lib/api/provider.js +58 -0
- package/lib/api/sandbox.d.ts +30 -0
- package/lib/api/sandbox.js +49 -0
- package/lib/api/sessions.d.ts +39 -4
- package/lib/api/sessions.js +58 -6
- package/lib/api/skills.d.ts +70 -0
- package/lib/api/skills.js +90 -0
- package/lib/api/tools.d.ts +34 -0
- package/lib/api/tools.js +37 -0
- package/lib/api/turn.d.ts +84 -16
- package/lib/api/turn.js +224 -4
- package/lib/api/webhooks.d.ts +60 -0
- package/lib/api/webhooks.js +81 -0
- package/lib/ask.d.ts +18 -0
- package/lib/ask.js +69 -0
- package/lib/builder.js +39 -19
- package/lib/customize/agents.d.ts +2 -0
- package/lib/customize/agents.js +125 -0
- package/lib/customize/connectors.d.ts +2 -0
- package/lib/customize/connectors.js +197 -0
- package/lib/customize/index.d.ts +6 -0
- package/lib/customize/index.js +50 -0
- package/lib/customize/plugins.d.ts +2 -0
- package/lib/customize/plugins.js +93 -0
- package/lib/customize/skills.d.ts +2 -0
- package/lib/customize/skills.js +125 -0
- package/lib/customize/ui.d.ts +110 -0
- package/lib/customize/ui.js +99 -0
- package/lib/desk.d.ts +8 -1
- package/lib/desk.js +29 -35
- package/lib/door.d.ts +7 -0
- package/lib/door.js +89 -0
- package/lib/environment.d.ts +15 -0
- package/lib/environment.js +132 -0
- package/lib/files.d.ts +14 -0
- package/lib/files.js +111 -0
- package/lib/find.d.ts +7 -0
- package/lib/find.js +77 -0
- package/lib/foot.d.ts +16 -0
- package/lib/foot.js +36 -0
- package/lib/forge.js +8 -4
- package/lib/git.d.ts +9 -0
- package/lib/git.js +74 -0
- package/lib/host.d.ts +5 -0
- package/lib/landing.d.ts +10 -0
- package/lib/landing.js +47 -14
- package/lib/markdown.d.ts +49 -0
- package/lib/markdown.js +123 -0
- package/lib/plans.d.ts +1 -0
- package/lib/plans.js +113 -0
- package/lib/prefs.d.ts +30 -0
- package/lib/prefs.js +83 -0
- package/lib/prose.d.ts +4 -0
- package/lib/prose.js +53 -0
- package/lib/route.d.ts +16 -1
- package/lib/route.js +32 -1
- package/lib/run.js +207 -28
- package/lib/section.js +2 -2
- package/lib/settings/account.d.ts +5 -0
- package/lib/settings/account.js +111 -0
- package/lib/settings/billing.d.ts +1 -0
- package/lib/settings/billing.js +104 -0
- package/lib/settings/capabilities.d.ts +1 -0
- package/lib/settings/capabilities.js +55 -0
- package/lib/settings/card.d.ts +31 -0
- package/lib/settings/card.js +105 -0
- package/lib/settings/code.d.ts +1 -0
- package/lib/settings/code.js +51 -0
- package/lib/settings/environments.d.ts +1 -0
- package/lib/settings/environments.js +53 -0
- package/lib/settings/general.d.ts +1 -0
- package/lib/settings/general.js +44 -0
- package/lib/settings/index.d.ts +4 -0
- package/lib/settings/index.js +22 -0
- package/lib/settings/integrations.d.ts +1 -0
- package/lib/settings/integrations.js +81 -0
- package/lib/settings/keys.d.ts +1 -0
- package/lib/settings/keys.js +102 -0
- package/lib/settings/machines.d.ts +1 -0
- package/lib/settings/machines.js +118 -0
- package/lib/settings/members.d.ts +1 -0
- package/lib/settings/members.js +67 -0
- package/lib/settings/memory.d.ts +1 -0
- package/lib/settings/memory.js +56 -0
- package/lib/settings/notifications.d.ts +1 -0
- package/lib/settings/notifications.js +97 -0
- package/lib/settings/privacy.d.ts +1 -0
- package/lib/settings/privacy.js +63 -0
- package/lib/settings/sections.d.ts +16 -0
- package/lib/settings/sections.js +32 -0
- package/lib/settings/ui.d.ts +49 -0
- package/lib/settings/ui.js +47 -0
- package/lib/settings/usage.d.ts +1 -0
- package/lib/settings/usage.js +114 -0
- package/lib/shelf.d.ts +6 -0
- package/lib/shelf.js +86 -16
- package/lib/switch.js +1 -2
- package/lib/transcript.d.ts +12 -0
- package/lib/transcript.js +56 -0
- package/lib/voice.d.ts +3 -1
- package/lib/voice.js +10 -4
- package/package.json +1 -1
- package/lib/account.d.ts +0 -14
- package/lib/account.js +0 -38
- package/lib/e2e/site.spec.d.ts +0 -1
- package/lib/e2e/site.spec.js +0 -125
- package/lib/mcp.d.ts +0 -1
- package/lib/mcp.js +0 -28
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The organization's connectors: a Slack workspace, the GitHub accounts the
|
|
3
|
+
* platform's GitHub App is installed on.
|
|
4
|
+
*
|
|
5
|
+
* GET /v1/provider/{id} one connector and this org's connection to it
|
|
6
|
+
* POST /v1/provider/{id}/connect {} → {authorizeUrl}: the provider's own consent page (org admin)
|
|
7
|
+
* POST /v1/provider/{id}/disconnect forget the org's connection (org admin)
|
|
8
|
+
* GET /v1/provider/slack/channels the connected workspace's channels, a page at a time
|
|
9
|
+
*
|
|
10
|
+
* Connecting leaves this page once, for the provider's consent screen, and only
|
|
11
|
+
* for that provider's own host.
|
|
12
|
+
*/
|
|
13
|
+
import { call, query, seg } from './call.js';
|
|
14
|
+
const str = (v) => (typeof v === 'string' ? v : '');
|
|
15
|
+
const obj = (v) => (v && typeof v === 'object' ? v : {});
|
|
16
|
+
const arr = (v) => (Array.isArray(v) ? v : []);
|
|
17
|
+
export function connector(raw, id = '') {
|
|
18
|
+
const p = obj(raw);
|
|
19
|
+
const c = obj(p.connection);
|
|
20
|
+
return {
|
|
21
|
+
id: str(p.id) || id,
|
|
22
|
+
name: str(p.name) || id,
|
|
23
|
+
available: p.available === true,
|
|
24
|
+
connected: p.connected === true,
|
|
25
|
+
account: str(c.account),
|
|
26
|
+
since: str(c.connectedAt),
|
|
27
|
+
note: str(p.note),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export async function read(t, id) {
|
|
31
|
+
return connector(await call(t, 'GET', `/v1/provider/${seg(id)}`), id);
|
|
32
|
+
}
|
|
33
|
+
/** Where each provider's consent lives. An authorize address anywhere else is refused. */
|
|
34
|
+
const CONSENT = {
|
|
35
|
+
slack: /^https:\/\/slack\.com\//,
|
|
36
|
+
github: /^https:\/\/github\.com\//,
|
|
37
|
+
};
|
|
38
|
+
/** The provider's consent page for this org. */
|
|
39
|
+
export async function authorize(t, id) {
|
|
40
|
+
const url = str(obj(await call(t, 'POST', `/v1/provider/${seg(id)}/connect`, {})).authorizeUrl);
|
|
41
|
+
const host = CONSENT[id];
|
|
42
|
+
if (!host || !host.test(url))
|
|
43
|
+
throw new Error(`The platform did not name a ${id} address to connect at`);
|
|
44
|
+
return url;
|
|
45
|
+
}
|
|
46
|
+
export async function disconnect(t, id) {
|
|
47
|
+
await call(t, 'POST', `/v1/provider/${seg(id)}/disconnect`, {});
|
|
48
|
+
}
|
|
49
|
+
export async function channels(t, cursor = '') {
|
|
50
|
+
const raw = obj(await call(t, 'GET', `/v1/provider/slack/channels${query({ cursor })}`));
|
|
51
|
+
return {
|
|
52
|
+
channels: arr(raw.channels)
|
|
53
|
+
.map(obj)
|
|
54
|
+
.map((c) => ({ id: str(c.id), name: str(c.name), private: c.is_private === true, member: c.is_member === true }))
|
|
55
|
+
.filter((c) => c.id),
|
|
56
|
+
next: str(raw.next_cursor),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
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.
|
|
4
|
+
*
|
|
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
|
+
*
|
|
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.
|
|
11
|
+
*/
|
|
12
|
+
import { type Target } from './call.ts';
|
|
13
|
+
export type Door = 'screen' | 'terminal';
|
|
14
|
+
/** A shell's tmux session name: the sandbox reattaches to it when the door opens again. */
|
|
15
|
+
export declare const NAME: RegExp;
|
|
16
|
+
/** 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>;
|
|
18
|
+
export interface Node {
|
|
19
|
+
/** The resolved path inside the sandbox. */
|
|
20
|
+
path: string;
|
|
21
|
+
dir: boolean;
|
|
22
|
+
/** A directory's entries, bare names, sorted. */
|
|
23
|
+
names: string[];
|
|
24
|
+
/** A file's text, or '' when it is binary or past the view cap. */
|
|
25
|
+
text: string;
|
|
26
|
+
binary: boolean;
|
|
27
|
+
truncated: boolean;
|
|
28
|
+
}
|
|
29
|
+
/** One path in the sandbox, relative to its working directory. */
|
|
30
|
+
export declare function read(t: Target, sandbox: string, path: string): Promise<Node>;
|
|
@@ -0,0 +1,49 @@
|
|
|
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.
|
|
4
|
+
*
|
|
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
|
+
*
|
|
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.
|
|
11
|
+
*/
|
|
12
|
+
import { call, Refusal, seg } from './call.js';
|
|
13
|
+
/** A shell's tmux session name: the sandbox reattaches to it when the door opens again. */
|
|
14
|
+
export const NAME = /^[A-Za-z0-9_][A-Za-z0-9_-]{0,63}$/;
|
|
15
|
+
/** 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`);
|
|
18
|
+
const path = typeof r?.url === 'string' ? r.url : '';
|
|
19
|
+
if (!path.startsWith('/v1/'))
|
|
20
|
+
throw new Refusal(502, 'The sandbox answered with no address to open');
|
|
21
|
+
const arg = which === 'terminal' && NAME.test(session) ? `&arg=${session}` : '';
|
|
22
|
+
return `${t.api}${path}${arg}`;
|
|
23
|
+
}
|
|
24
|
+
/** What a file shows at most. */
|
|
25
|
+
const CAP = 1 << 20;
|
|
26
|
+
/** One path in the sandbox, relative to its working directory. */
|
|
27
|
+
export async function read(t, sandbox, path) {
|
|
28
|
+
const o = (await call(t, 'POST', '/v1/sandbox/read', { id: sandbox, path })) ?? {};
|
|
29
|
+
const dir = o.dir === true;
|
|
30
|
+
const names = Array.isArray(o.entries) ? o.entries.filter((n) => typeof n === 'string').sort() : [];
|
|
31
|
+
const bytes = !dir && typeof o.data === 'string' ? decode(o.data) : new Uint8Array();
|
|
32
|
+
const binary = bytes.includes(0);
|
|
33
|
+
const truncated = bytes.length > CAP;
|
|
34
|
+
return {
|
|
35
|
+
path: typeof o.path === 'string' ? o.path : path,
|
|
36
|
+
dir,
|
|
37
|
+
names,
|
|
38
|
+
text: binary || truncated ? '' : new TextDecoder().decode(bytes),
|
|
39
|
+
binary,
|
|
40
|
+
truncated,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function decode(b64) {
|
|
44
|
+
const bin = atob(b64);
|
|
45
|
+
const out = new Uint8Array(bin.length);
|
|
46
|
+
for (let i = 0; i < bin.length; i++)
|
|
47
|
+
out[i] = bin.charCodeAt(i);
|
|
48
|
+
return out;
|
|
49
|
+
}
|
package/lib/api/sessions.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Runs, as the platform records them: one session per run.
|
|
3
3
|
*
|
|
4
|
-
* GET
|
|
5
|
-
* GET
|
|
6
|
-
*
|
|
7
|
-
* POST
|
|
4
|
+
* GET /v1/agent/sessions?kind=&project=&status=&limit=&after= newest first, {sessions, next}
|
|
5
|
+
* GET /v1/agent/sessions/{id} + the 50 most recent events
|
|
6
|
+
* PATCH /v1/agent/sessions/{id} {title}|{published} rename it; open its story to the public build route
|
|
7
|
+
* POST /v1/agent/sessions/{id}/message {message} steer a running run
|
|
8
|
+
* POST /v1/agent/sessions/{id}/pause pause it; a sandbox run keeps its work on its branch
|
|
9
|
+
* POST /v1/agent/sessions/{id}/resume ask a paused run to go on
|
|
10
|
+
* POST /v1/agent/sessions/{id}/stop {message} end it, work kept
|
|
8
11
|
* GET /v1/agent/sessions/stream?root={id} SSE: `session` and `event` frames,
|
|
9
12
|
* each wrapped: {"session":{…}}, {"event":{…}}
|
|
10
13
|
*
|
|
@@ -35,6 +38,12 @@ export interface Session {
|
|
|
35
38
|
mode: string;
|
|
36
39
|
/** The pull request the run proposed, or ''. */
|
|
37
40
|
pr: string;
|
|
41
|
+
/** The sandbox the run leased, once it has one. It is gone when the run ends. */
|
|
42
|
+
sandbox: string;
|
|
43
|
+
/** The org the run is in, which the public build route is addressed by. */
|
|
44
|
+
org: string;
|
|
45
|
+
/** Whether its story is open to the public build route. Only a run that names a project can be. */
|
|
46
|
+
published: boolean;
|
|
38
47
|
events: number;
|
|
39
48
|
createdAt: string;
|
|
40
49
|
updatedAt: string;
|
|
@@ -54,16 +63,42 @@ export interface Detail extends Session {
|
|
|
54
63
|
}
|
|
55
64
|
export declare function session(raw: unknown): Session;
|
|
56
65
|
export declare function event(raw: unknown): Event;
|
|
66
|
+
/**
|
|
67
|
+
* How long runs in one mode took, in whole minutes: the median and the ninetieth
|
|
68
|
+
* percentile of the finished ones. Null under three, which is too few to say.
|
|
69
|
+
*/
|
|
70
|
+
export declare function took(runs: Session[], mode: string): [number, number] | null;
|
|
57
71
|
export interface ListQuery {
|
|
58
72
|
kind?: string;
|
|
59
73
|
project?: string;
|
|
74
|
+
/** running, paused, done or error: the four the platform filters on. */
|
|
60
75
|
status?: string;
|
|
61
76
|
limit?: number;
|
|
77
|
+
/** The `next` of the page before. */
|
|
78
|
+
after?: string;
|
|
79
|
+
}
|
|
80
|
+
export interface Page {
|
|
81
|
+
sessions: Session[];
|
|
82
|
+
/** The cursor for the page after this one, or '' on the last. */
|
|
83
|
+
next: string;
|
|
62
84
|
}
|
|
85
|
+
export declare function page(t: Target, q?: ListQuery): Promise<Page>;
|
|
63
86
|
export declare function list(t: Target, q?: ListQuery): Promise<Session[]>;
|
|
64
87
|
export declare function get(t: Target, id: string): Promise<Detail>;
|
|
65
88
|
export declare function message(t: Target, id: string, text: string): Promise<void>;
|
|
66
89
|
export declare function stop(t: Target, id: string, why?: string): Promise<void>;
|
|
90
|
+
export declare function pause(t: Target, id: string): Promise<void>;
|
|
91
|
+
export declare function resume(t: Target, id: string): Promise<void>;
|
|
92
|
+
/** A new title, up to the platform's 512 characters. */
|
|
93
|
+
export declare function rename(t: Target, id: string, title: string): Promise<Session>;
|
|
94
|
+
/**
|
|
95
|
+
* Open the run's story to the public build route, or close it. The platform
|
|
96
|
+
* refuses to open one that names no project, because that route is keyed on
|
|
97
|
+
* the org and the project.
|
|
98
|
+
*/
|
|
99
|
+
export declare function publish(t: Target, id: string, on: boolean): Promise<Session>;
|
|
100
|
+
/** Where anyone can read a published run's story: the public build route, by org and project. */
|
|
101
|
+
export declare const story: (t: Target, org: string, project: string) => string;
|
|
67
102
|
export interface Watch {
|
|
68
103
|
session?: (s: Session) => void;
|
|
69
104
|
event?: (e: Event) => void;
|
package/lib/api/sessions.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Runs, as the platform records them: one session per run.
|
|
3
3
|
*
|
|
4
|
-
* GET
|
|
5
|
-
* GET
|
|
6
|
-
*
|
|
7
|
-
* POST
|
|
4
|
+
* GET /v1/agent/sessions?kind=&project=&status=&limit=&after= newest first, {sessions, next}
|
|
5
|
+
* GET /v1/agent/sessions/{id} + the 50 most recent events
|
|
6
|
+
* PATCH /v1/agent/sessions/{id} {title}|{published} rename it; open its story to the public build route
|
|
7
|
+
* POST /v1/agent/sessions/{id}/message {message} steer a running run
|
|
8
|
+
* POST /v1/agent/sessions/{id}/pause pause it; a sandbox run keeps its work on its branch
|
|
9
|
+
* POST /v1/agent/sessions/{id}/resume ask a paused run to go on
|
|
10
|
+
* POST /v1/agent/sessions/{id}/stop {message} end it, work kept
|
|
8
11
|
* GET /v1/agent/sessions/stream?root={id} SSE: `session` and `event` frames,
|
|
9
12
|
* each wrapped: {"session":{…}}, {"event":{…}}
|
|
10
13
|
*
|
|
@@ -34,6 +37,9 @@ export function session(raw) {
|
|
|
34
37
|
environment: str(s.environment),
|
|
35
38
|
mode: str(s.mode),
|
|
36
39
|
pr: str(s.pr),
|
|
40
|
+
sandbox: str(s.sandbox),
|
|
41
|
+
org: str(s.org),
|
|
42
|
+
published: s.published === true,
|
|
37
43
|
events: num(s.events),
|
|
38
44
|
createdAt: str(s.createdAt),
|
|
39
45
|
updatedAt: str(s.updatedAt),
|
|
@@ -52,9 +58,30 @@ export function event(raw) {
|
|
|
52
58
|
createdAt: str(e.createdAt),
|
|
53
59
|
};
|
|
54
60
|
}
|
|
55
|
-
|
|
61
|
+
/**
|
|
62
|
+
* How long runs in one mode took, in whole minutes: the median and the ninetieth
|
|
63
|
+
* percentile of the finished ones. Null under three, which is too few to say.
|
|
64
|
+
*/
|
|
65
|
+
export function took(runs, mode) {
|
|
66
|
+
const spans = runs
|
|
67
|
+
.filter((r) => r.mode === mode && r.status === 'done' && r.endedAt)
|
|
68
|
+
.map((r) => Date.parse(r.endedAt) - Date.parse(r.createdAt))
|
|
69
|
+
.filter((ms) => Number.isFinite(ms) && ms > 0)
|
|
70
|
+
.sort((a, b) => a - b);
|
|
71
|
+
if (spans.length < 3)
|
|
72
|
+
return null;
|
|
73
|
+
const at = (q) => Math.max(1, Math.round(spans[Math.min(spans.length - 1, Math.floor(q * spans.length))] / 60_000));
|
|
74
|
+
return [at(0.5), at(0.9)];
|
|
75
|
+
}
|
|
76
|
+
export async function page(t, q = {}) {
|
|
56
77
|
const raw = obj(await call(t, 'GET', `/v1/agent/sessions${query({ ...q })}`));
|
|
57
|
-
return
|
|
78
|
+
return {
|
|
79
|
+
sessions: (Array.isArray(raw.sessions) ? raw.sessions : []).map(session).filter((s) => s.id),
|
|
80
|
+
next: str(raw.next),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export async function list(t, q = {}) {
|
|
84
|
+
return (await page(t, q)).sessions;
|
|
58
85
|
}
|
|
59
86
|
export async function get(t, id) {
|
|
60
87
|
const raw = obj(await call(t, 'GET', `/v1/agent/sessions/${seg(id)}`));
|
|
@@ -72,6 +99,31 @@ export async function message(t, id, text) {
|
|
|
72
99
|
export async function stop(t, id, why = 'Stopped from the builder') {
|
|
73
100
|
await call(t, 'POST', `/v1/agent/sessions/${seg(id)}/stop`, { message: why });
|
|
74
101
|
}
|
|
102
|
+
export async function pause(t, id) {
|
|
103
|
+
await call(t, 'POST', `/v1/agent/sessions/${seg(id)}/pause`, {});
|
|
104
|
+
}
|
|
105
|
+
export async function resume(t, id) {
|
|
106
|
+
await call(t, 'POST', `/v1/agent/sessions/${seg(id)}/resume`, {});
|
|
107
|
+
}
|
|
108
|
+
/** A new title, up to the platform's 512 characters. */
|
|
109
|
+
export async function rename(t, id, title) {
|
|
110
|
+
const name = title.trim();
|
|
111
|
+
if (!name)
|
|
112
|
+
throw new Refusal(400, 'Give the run a name');
|
|
113
|
+
if (name.length > 512)
|
|
114
|
+
throw new Refusal(400, 'A run’s name is at most 512 characters');
|
|
115
|
+
return session(await call(t, 'PATCH', `/v1/agent/sessions/${seg(id)}`, { title: name }));
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Open the run's story to the public build route, or close it. The platform
|
|
119
|
+
* refuses to open one that names no project, because that route is keyed on
|
|
120
|
+
* the org and the project.
|
|
121
|
+
*/
|
|
122
|
+
export async function publish(t, id, on) {
|
|
123
|
+
return session(await call(t, 'PATCH', `/v1/agent/sessions/${seg(id)}`, { published: on }));
|
|
124
|
+
}
|
|
125
|
+
/** Where anyone can read a published run's story: the public build route, by org and project. */
|
|
126
|
+
export const story = (t, org, project) => org && project ? `${t.api}/v1/agent/builds/${seg(org)}/${seg(project)}` : '';
|
|
75
127
|
/**
|
|
76
128
|
* Follow one run's tree until `signal` aborts. Reconnects with a capped backoff;
|
|
77
129
|
* a refusal (401/403) ends it, because retrying a no is not recovery.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skills: what an agent knows how to do, as a SKILL.md it reads.
|
|
3
|
+
*
|
|
4
|
+
* GET /v1/tool/skills?activated=true {tools: [Tool]} the org's skills that are on, brand and own
|
|
5
|
+
* GET /v1/tool/skills/authored {skills: [skill]} the org's own, with their SKILL.md
|
|
6
|
+
* POST /v1/tool/skills {name, description, content} → 201 {skill}; the same name revises it
|
|
7
|
+
* DELETE /v1/tool/skills/{id} {deleted}
|
|
8
|
+
* GET /.well-known/agent-skills/index.json the brand's catalogue, public
|
|
9
|
+
* GET /.well-known/agent-skills/{name}/SKILL.md one of its documents, public
|
|
10
|
+
*
|
|
11
|
+
* A skill is on when its tool name, `skill_<name>`, is activated (tools.ts):
|
|
12
|
+
* that is how a catalogue skill is added to an org and how an org's own skill is
|
|
13
|
+
* switched off. The brand's skill wins a name an org's own skill also takes.
|
|
14
|
+
*/
|
|
15
|
+
import { type Target } from './call.ts';
|
|
16
|
+
import { type Tool } from './tools.ts';
|
|
17
|
+
/** An org's own skill. */
|
|
18
|
+
export interface Skill {
|
|
19
|
+
/** Derived from the name, and what a delete addresses. */
|
|
20
|
+
id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
description: string;
|
|
23
|
+
/** The SKILL.md body. */
|
|
24
|
+
content: string;
|
|
25
|
+
/** When it was last written, Unix seconds. */
|
|
26
|
+
created: number;
|
|
27
|
+
/** The repository it was read from, or '' for one written here. */
|
|
28
|
+
source: string;
|
|
29
|
+
}
|
|
30
|
+
/** One skill of the brand's catalogue. */
|
|
31
|
+
export interface Entry {
|
|
32
|
+
name: string;
|
|
33
|
+
description: string;
|
|
34
|
+
/** The product it belongs to. */
|
|
35
|
+
product: string;
|
|
36
|
+
}
|
|
37
|
+
export interface Catalogue {
|
|
38
|
+
skills: Entry[];
|
|
39
|
+
products: {
|
|
40
|
+
name: string;
|
|
41
|
+
count: number;
|
|
42
|
+
}[];
|
|
43
|
+
}
|
|
44
|
+
/** A skill's name: one lowercase path segment, as the handler takes it. */
|
|
45
|
+
export declare const NAME: RegExp;
|
|
46
|
+
/** The most SKILL.md the handler keeps. */
|
|
47
|
+
export declare const MAX: number;
|
|
48
|
+
/** The tool name a skill is switched on by. */
|
|
49
|
+
export declare const tool: (name: string) => string;
|
|
50
|
+
/** The skill a tool name switches, or '' when it is not a skill's. */
|
|
51
|
+
export declare const nameOf: (tool: string) => string;
|
|
52
|
+
export declare function skill(raw: unknown): Skill;
|
|
53
|
+
export declare function catalogue(raw: unknown): Catalogue;
|
|
54
|
+
/** The org's skills that are on, brand and own. */
|
|
55
|
+
export declare function active(t: Target): Promise<Tool[]>;
|
|
56
|
+
export declare function authored(t: Target): Promise<Skill[]>;
|
|
57
|
+
/** Why a skill cannot be written as it stands, or '' when it can. */
|
|
58
|
+
export declare function refuse(s: {
|
|
59
|
+
name: string;
|
|
60
|
+
content: string;
|
|
61
|
+
}): string;
|
|
62
|
+
export declare function write(t: Target, s: {
|
|
63
|
+
name: string;
|
|
64
|
+
description: string;
|
|
65
|
+
content: string;
|
|
66
|
+
}): Promise<Skill>;
|
|
67
|
+
export declare function remove(t: Target, id: string): Promise<void>;
|
|
68
|
+
export declare function brand(t: Target): Promise<Catalogue>;
|
|
69
|
+
/** One catalogue skill's SKILL.md. */
|
|
70
|
+
export declare function document(t: Target, name: string): Promise<string>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skills: what an agent knows how to do, as a SKILL.md it reads.
|
|
3
|
+
*
|
|
4
|
+
* GET /v1/tool/skills?activated=true {tools: [Tool]} the org's skills that are on, brand and own
|
|
5
|
+
* GET /v1/tool/skills/authored {skills: [skill]} the org's own, with their SKILL.md
|
|
6
|
+
* POST /v1/tool/skills {name, description, content} → 201 {skill}; the same name revises it
|
|
7
|
+
* DELETE /v1/tool/skills/{id} {deleted}
|
|
8
|
+
* GET /.well-known/agent-skills/index.json the brand's catalogue, public
|
|
9
|
+
* GET /.well-known/agent-skills/{name}/SKILL.md one of its documents, public
|
|
10
|
+
*
|
|
11
|
+
* A skill is on when its tool name, `skill_<name>`, is activated (tools.ts):
|
|
12
|
+
* that is how a catalogue skill is added to an org and how an org's own skill is
|
|
13
|
+
* switched off. The brand's skill wins a name an org's own skill also takes.
|
|
14
|
+
*/
|
|
15
|
+
import { call, reason, Refusal, seg } from './call.js';
|
|
16
|
+
import { toolsOf } from './tools.js';
|
|
17
|
+
/** A skill's name: one lowercase path segment, as the handler takes it. */
|
|
18
|
+
export const NAME = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
19
|
+
/** The most SKILL.md the handler keeps. */
|
|
20
|
+
export const MAX = 256 << 10;
|
|
21
|
+
/** The tool name a skill is switched on by. */
|
|
22
|
+
export const tool = (name) => `skill_${name}`;
|
|
23
|
+
/** The skill a tool name switches, or '' when it is not a skill's. */
|
|
24
|
+
export const nameOf = (tool) => (tool.startsWith('skill_') ? tool.slice('skill_'.length) : '');
|
|
25
|
+
const str = (v) => (typeof v === 'string' ? v : '');
|
|
26
|
+
const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : 0);
|
|
27
|
+
const obj = (v) => (v && typeof v === 'object' ? v : {});
|
|
28
|
+
const rows = (v) => (Array.isArray(v) ? v : []);
|
|
29
|
+
export function skill(raw) {
|
|
30
|
+
const o = obj(raw);
|
|
31
|
+
const name = str(o.name);
|
|
32
|
+
return { id: str(o.id) || name, name, description: str(o.description), content: str(o.content), created: num(o.createdAt), source: str(o.source) };
|
|
33
|
+
}
|
|
34
|
+
export function catalogue(raw) {
|
|
35
|
+
const o = obj(raw);
|
|
36
|
+
return {
|
|
37
|
+
skills: rows(o.skills)
|
|
38
|
+
.map((r) => {
|
|
39
|
+
const e = obj(r);
|
|
40
|
+
return { name: str(e.name), description: str(e.description), product: str(e.service) };
|
|
41
|
+
})
|
|
42
|
+
.filter((e) => e.name),
|
|
43
|
+
products: rows(o.products)
|
|
44
|
+
.map((r) => {
|
|
45
|
+
const p = obj(r);
|
|
46
|
+
return { name: str(p.name), count: num(p.skill_count) };
|
|
47
|
+
})
|
|
48
|
+
.filter((p) => p.name),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** The org's skills that are on, brand and own. */
|
|
52
|
+
export async function active(t) {
|
|
53
|
+
return toolsOf(await call(t, 'GET', '/v1/tool/skills?activated=true'));
|
|
54
|
+
}
|
|
55
|
+
export async function authored(t) {
|
|
56
|
+
return rows(obj(await call(t, 'GET', '/v1/tool/skills/authored')).skills).map(skill).filter((s) => s.name);
|
|
57
|
+
}
|
|
58
|
+
/** Why a skill cannot be written as it stands, or '' when it can. */
|
|
59
|
+
export function refuse(s) {
|
|
60
|
+
if (!NAME.test(s.name))
|
|
61
|
+
return 'A name is one lowercase word: letters, digits, _ or -';
|
|
62
|
+
if (!s.content.trim())
|
|
63
|
+
return 'A skill needs its SKILL.md';
|
|
64
|
+
if (new TextEncoder().encode(s.content).length > MAX)
|
|
65
|
+
return 'A SKILL.md is at most 256 KB';
|
|
66
|
+
return '';
|
|
67
|
+
}
|
|
68
|
+
export async function write(t, s) {
|
|
69
|
+
const why = refuse(s);
|
|
70
|
+
if (why)
|
|
71
|
+
throw new Error(why);
|
|
72
|
+
return skill(obj(await call(t, 'POST', '/v1/tool/skills', { name: s.name, description: s.description, content: s.content })).skill);
|
|
73
|
+
}
|
|
74
|
+
export async function remove(t, id) {
|
|
75
|
+
await call(t, 'DELETE', `/v1/tool/skills/${seg(id)}`);
|
|
76
|
+
}
|
|
77
|
+
/** A public document of the brand's catalogue. It needs no bearer, so it sends none. */
|
|
78
|
+
async function open(t, path) {
|
|
79
|
+
const res = await fetch(`${t.api}/.well-known/agent-skills/${path}`);
|
|
80
|
+
if (!res.ok)
|
|
81
|
+
throw new Refusal(res.status, (await reason(res)) || `The catalogue answered ${res.status}`);
|
|
82
|
+
return res;
|
|
83
|
+
}
|
|
84
|
+
export async function brand(t) {
|
|
85
|
+
return catalogue(await (await open(t, 'index.json')).json());
|
|
86
|
+
}
|
|
87
|
+
/** One catalogue skill's SKILL.md. */
|
|
88
|
+
export async function document(t, name) {
|
|
89
|
+
return (await open(t, `${seg(name)}/SKILL.md`)).text();
|
|
90
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The org's tool plane: every tool it can reach, and which of them are on.
|
|
3
|
+
*
|
|
4
|
+
* GET /v1/tool?source=&activated=true {tools: [{name, source, description, activated, dispatchable}]}
|
|
5
|
+
* GET /v1/tool/activation {enabled: [name]}
|
|
6
|
+
* PUT /v1/tool/activation {activate: [name], deactivate: [name]} → {enabled: [name]}
|
|
7
|
+
*
|
|
8
|
+
* A tool is on per org. On is what puts a skill's SKILL.md in an agent's prompt
|
|
9
|
+
* and what lets an MCP server's tool be called; a tool that is off is listed and
|
|
10
|
+
* refused at call time. Any member of the org may switch one — the handler asks
|
|
11
|
+
* for the org and nothing more.
|
|
12
|
+
*/
|
|
13
|
+
import { type Target } from './call.ts';
|
|
14
|
+
export interface Tool {
|
|
15
|
+
/** The flat, fleet-wide name: `skill_<name>`, `<server id>_<tool>`, `agent_<name>`… */
|
|
16
|
+
name: string;
|
|
17
|
+
/** connector, function, zap-service, agent, skill or mcp. */
|
|
18
|
+
source: string;
|
|
19
|
+
description: string;
|
|
20
|
+
activated: boolean;
|
|
21
|
+
}
|
|
22
|
+
export declare const names: (v: unknown) => string[];
|
|
23
|
+
export declare function tool(raw: unknown): Tool;
|
|
24
|
+
/** The tools in one listing, whichever of the plane's two envelopes it came in. */
|
|
25
|
+
export declare function toolsOf(raw: unknown): Tool[];
|
|
26
|
+
/** Every tool the org reaches, narrowed to one source and to the ones that are on when asked. */
|
|
27
|
+
export declare function tools(t: Target, q?: {
|
|
28
|
+
source?: string;
|
|
29
|
+
activated?: boolean;
|
|
30
|
+
}): Promise<Tool[]>;
|
|
31
|
+
/** The names that are on. */
|
|
32
|
+
export declare function enabled(t: Target): Promise<string[]>;
|
|
33
|
+
/** Switch names on and off; answers every name that is on afterwards. */
|
|
34
|
+
export declare function toggle(t: Target, on: string[], off?: string[]): Promise<string[]>;
|
package/lib/api/tools.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The org's tool plane: every tool it can reach, and which of them are on.
|
|
3
|
+
*
|
|
4
|
+
* GET /v1/tool?source=&activated=true {tools: [{name, source, description, activated, dispatchable}]}
|
|
5
|
+
* GET /v1/tool/activation {enabled: [name]}
|
|
6
|
+
* PUT /v1/tool/activation {activate: [name], deactivate: [name]} → {enabled: [name]}
|
|
7
|
+
*
|
|
8
|
+
* A tool is on per org. On is what puts a skill's SKILL.md in an agent's prompt
|
|
9
|
+
* and what lets an MCP server's tool be called; a tool that is off is listed and
|
|
10
|
+
* refused at call time. Any member of the org may switch one — the handler asks
|
|
11
|
+
* for the org and nothing more.
|
|
12
|
+
*/
|
|
13
|
+
import { call, query } from './call.js';
|
|
14
|
+
const str = (v) => (typeof v === 'string' ? v : '');
|
|
15
|
+
const obj = (v) => (v && typeof v === 'object' ? v : {});
|
|
16
|
+
export const names = (v) => (Array.isArray(v) ? v.filter((n) => typeof n === 'string' && n !== '') : []);
|
|
17
|
+
export function tool(raw) {
|
|
18
|
+
const o = obj(raw);
|
|
19
|
+
return { name: str(o.name), source: str(o.source), description: str(o.description), activated: o.activated === true };
|
|
20
|
+
}
|
|
21
|
+
/** The tools in one listing, whichever of the plane's two envelopes it came in. */
|
|
22
|
+
export function toolsOf(raw) {
|
|
23
|
+
const list = obj(raw).tools;
|
|
24
|
+
return (Array.isArray(list) ? list : []).map(tool).filter((x) => x.name);
|
|
25
|
+
}
|
|
26
|
+
/** Every tool the org reaches, narrowed to one source and to the ones that are on when asked. */
|
|
27
|
+
export async function tools(t, q = {}) {
|
|
28
|
+
return toolsOf(await call(t, 'GET', `/v1/tool${query({ source: q.source, activated: q.activated ? 'true' : undefined })}`));
|
|
29
|
+
}
|
|
30
|
+
/** The names that are on. */
|
|
31
|
+
export async function enabled(t) {
|
|
32
|
+
return names(obj(await call(t, 'GET', '/v1/tool/activation')).enabled);
|
|
33
|
+
}
|
|
34
|
+
/** Switch names on and off; answers every name that is on afterwards. */
|
|
35
|
+
export async function toggle(t, on, off = []) {
|
|
36
|
+
return names(obj(await call(t, 'PUT', '/v1/tool/activation', { activate: on, deactivate: off })).enabled);
|
|
37
|
+
}
|
package/lib/api/turn.d.ts
CHANGED
|
@@ -1,18 +1,3 @@
|
|
|
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,
|
|
14
|
-
* `log` for a free line. A steering message a person sent is a `message`.
|
|
15
|
-
*/
|
|
16
1
|
import type { Event } from './sessions.ts';
|
|
17
2
|
/** The payload as an object. A live frame may carry it as a JSON string. */
|
|
18
3
|
export declare function decode(payload: unknown): Record<string, unknown> | string | null;
|
|
@@ -38,7 +23,12 @@ export interface StepLine {
|
|
|
38
23
|
name: string;
|
|
39
24
|
done: boolean;
|
|
40
25
|
}
|
|
41
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Tool calls, in the order they were first named. A later status for the same
|
|
28
|
+
* step wins, and a run's steps follow one another, so a step another has
|
|
29
|
+
* followed is over. `exit` is not a step: it is how the sandbox says one command
|
|
30
|
+
* in it ended (apps/sandbox work.go).
|
|
31
|
+
*/
|
|
42
32
|
export declare function steps(events: Pick<Event, 'kind' | 'payload' | 'seq'>[]): StepLine[];
|
|
43
33
|
export interface Outcome {
|
|
44
34
|
/** The last lifecycle status the run narrated, or ''. */
|
|
@@ -71,3 +61,81 @@ export declare function pull(url: string, repo: string): {
|
|
|
71
61
|
href: string;
|
|
72
62
|
label: string;
|
|
73
63
|
};
|
|
64
|
+
/** How a card's work went. One still running once the run has ended was cut off. */
|
|
65
|
+
export type Ran = 'running' | 'done' | 'error' | 'cancelled';
|
|
66
|
+
/**
|
|
67
|
+
* One piece of the transcript, drawn by what it is.
|
|
68
|
+
*
|
|
69
|
+
* said prose: the agent's messages (markdown), or a person's steering words
|
|
70
|
+
* shell a command the agent ran, and what it printed
|
|
71
|
+
* edit files the agent changed, with the diff when the harness printed one
|
|
72
|
+
* read a file the agent read, drawn as a chip
|
|
73
|
+
* step a step of the run itself (lease, clone, install, push), or a tool the agent called
|
|
74
|
+
* note a lifecycle line: started, pushed, stopped
|
|
75
|
+
* plan a plan run's answer, which can be approved into a build
|
|
76
|
+
*/
|
|
77
|
+
export type Card = {
|
|
78
|
+
kind: 'said';
|
|
79
|
+
key: string;
|
|
80
|
+
who: 'person' | 'agent';
|
|
81
|
+
text: string;
|
|
82
|
+
} | {
|
|
83
|
+
kind: 'shell';
|
|
84
|
+
key: string;
|
|
85
|
+
command: string;
|
|
86
|
+
output: string;
|
|
87
|
+
ran: Ran;
|
|
88
|
+
} | {
|
|
89
|
+
kind: 'edit';
|
|
90
|
+
key: string;
|
|
91
|
+
files: string[];
|
|
92
|
+
patch: string;
|
|
93
|
+
ran: Ran;
|
|
94
|
+
} | {
|
|
95
|
+
kind: 'read';
|
|
96
|
+
key: string;
|
|
97
|
+
file: string;
|
|
98
|
+
ran: Ran;
|
|
99
|
+
} | {
|
|
100
|
+
kind: 'step';
|
|
101
|
+
key: string;
|
|
102
|
+
name: string;
|
|
103
|
+
detail: string;
|
|
104
|
+
output: string;
|
|
105
|
+
ran: Ran;
|
|
106
|
+
} | {
|
|
107
|
+
kind: 'note';
|
|
108
|
+
key: string;
|
|
109
|
+
text: string;
|
|
110
|
+
} | {
|
|
111
|
+
kind: 'plan';
|
|
112
|
+
key: string;
|
|
113
|
+
text: string;
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* A run's events, as the cards its transcript draws. `mode` is the run's, from
|
|
117
|
+
* its record: a plan's final status is its plan, and nothing else draws a plan.
|
|
118
|
+
*
|
|
119
|
+
* The run's own steps arrive as `tool-call` {step, message, status}; each
|
|
120
|
+
* command in the sandbox narrates its output as `log` {message} chunks and ends
|
|
121
|
+
* with `tool-call` {step: "exit"}. The output after the step that starts the
|
|
122
|
+
* agent is the agent's, read by its harness's grammar (harness.ts); after any
|
|
123
|
+
* other step it is that step's. Once the agent has exited, the commands the run
|
|
124
|
+
* runs before it pushes commit its work.
|
|
125
|
+
*/
|
|
126
|
+
export declare function cards(events: Pick<Event, 'kind' | 'payload' | 'seq'>[], mode?: string): Card[];
|
|
127
|
+
/** The plan a plan run answered with, from its final status, or ''. */
|
|
128
|
+
export declare function answer(events: Pick<Event, 'kind' | 'payload' | 'seq'>[]): string;
|
|
129
|
+
export interface Settled {
|
|
130
|
+
/** The run has said how it ended, or that it paused: its work is where it will stay. */
|
|
131
|
+
settled: boolean;
|
|
132
|
+
/** That work is on the run's own branch, which a follow-up can start from. */
|
|
133
|
+
pushed: boolean;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Whether the run has kept its work yet, and whether that work is on its
|
|
137
|
+
* branch. A stop or a pause commits and pushes AFTER it is asked, and says so
|
|
138
|
+
* in its status (apps/coding coding.go interrupted): until then a follow-up
|
|
139
|
+
* would clone a branch the push has not reached.
|
|
140
|
+
*/
|
|
141
|
+
export declare function settled(events: Pick<Event, 'kind' | 'payload' | 'seq'>[]): Settled;
|