@hanzo/build 0.2.10 → 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.
Files changed (136) hide show
  1. package/README.md +97 -25
  2. package/lib/api/agents.d.ts +80 -0
  3. package/lib/api/agents.js +148 -0
  4. package/lib/api/billing.d.ts +205 -0
  5. package/lib/api/billing.js +300 -0
  6. package/lib/api/call.d.ts +6 -0
  7. package/lib/api/call.js +11 -0
  8. package/lib/api/capabilities.d.ts +25 -0
  9. package/lib/api/capabilities.js +40 -0
  10. package/lib/api/coding.d.ts +31 -1
  11. package/lib/api/coding.js +50 -1
  12. package/lib/api/connectors.d.ts +90 -0
  13. package/lib/api/connectors.js +109 -0
  14. package/lib/api/consent.d.ts +22 -0
  15. package/lib/api/consent.js +28 -0
  16. package/lib/api/github.d.ts +13 -0
  17. package/lib/api/github.js +9 -0
  18. package/lib/api/harness.d.ts +44 -0
  19. package/lib/api/harness.js +348 -0
  20. package/lib/api/keys.d.ts +41 -0
  21. package/lib/api/keys.js +55 -0
  22. package/lib/api/machines.d.ts +56 -0
  23. package/lib/api/machines.js +84 -0
  24. package/lib/api/members.d.ts +47 -0
  25. package/lib/api/members.js +88 -0
  26. package/lib/api/memory.d.ts +25 -0
  27. package/lib/api/memory.js +34 -0
  28. package/lib/api/places.d.ts +2 -2
  29. package/lib/api/places.js +3 -26
  30. package/lib/api/plugins.d.ts +55 -0
  31. package/lib/api/plugins.js +62 -0
  32. package/lib/api/pref.d.ts +49 -0
  33. package/lib/api/pref.js +68 -0
  34. package/lib/api/profile.d.ts +21 -0
  35. package/lib/api/profile.js +40 -0
  36. package/lib/api/projects.d.ts +19 -2
  37. package/lib/api/projects.js +29 -4
  38. package/lib/api/provider.d.ts +42 -0
  39. package/lib/api/provider.js +58 -0
  40. package/lib/api/sessions.d.ts +32 -4
  41. package/lib/api/sessions.js +42 -6
  42. package/lib/api/skills.d.ts +70 -0
  43. package/lib/api/skills.js +90 -0
  44. package/lib/api/tools.d.ts +34 -0
  45. package/lib/api/tools.js +37 -0
  46. package/lib/api/turn.d.ts +84 -16
  47. package/lib/api/turn.js +221 -4
  48. package/lib/api/webhooks.d.ts +60 -0
  49. package/lib/api/webhooks.js +81 -0
  50. package/lib/ask.d.ts +18 -0
  51. package/lib/ask.js +69 -0
  52. package/lib/builder.js +39 -19
  53. package/lib/customize/agents.d.ts +2 -0
  54. package/lib/customize/agents.js +125 -0
  55. package/lib/customize/connectors.d.ts +2 -0
  56. package/lib/customize/connectors.js +197 -0
  57. package/lib/customize/index.d.ts +6 -0
  58. package/lib/customize/index.js +50 -0
  59. package/lib/customize/plugins.d.ts +2 -0
  60. package/lib/customize/plugins.js +93 -0
  61. package/lib/customize/skills.d.ts +2 -0
  62. package/lib/customize/skills.js +125 -0
  63. package/lib/customize/ui.d.ts +110 -0
  64. package/lib/customize/ui.js +99 -0
  65. package/lib/desk.d.ts +4 -1
  66. package/lib/desk.js +3 -16
  67. package/lib/find.d.ts +7 -0
  68. package/lib/find.js +77 -0
  69. package/lib/foot.d.ts +16 -0
  70. package/lib/foot.js +36 -0
  71. package/lib/forge.js +4 -4
  72. package/lib/git.d.ts +4 -0
  73. package/lib/git.js +6 -2
  74. package/lib/host.d.ts +5 -0
  75. package/lib/landing.d.ts +10 -0
  76. package/lib/landing.js +30 -14
  77. package/lib/markdown.d.ts +49 -0
  78. package/lib/markdown.js +123 -0
  79. package/lib/plans.d.ts +1 -0
  80. package/lib/plans.js +113 -0
  81. package/lib/prefs.d.ts +30 -0
  82. package/lib/prefs.js +83 -0
  83. package/lib/prose.d.ts +4 -0
  84. package/lib/prose.js +53 -0
  85. package/lib/route.d.ts +16 -1
  86. package/lib/route.js +32 -1
  87. package/lib/run.js +184 -32
  88. package/lib/section.js +2 -2
  89. package/lib/settings/account.d.ts +5 -0
  90. package/lib/settings/account.js +111 -0
  91. package/lib/settings/billing.d.ts +1 -0
  92. package/lib/settings/billing.js +104 -0
  93. package/lib/settings/capabilities.d.ts +1 -0
  94. package/lib/settings/capabilities.js +55 -0
  95. package/lib/settings/card.d.ts +31 -0
  96. package/lib/settings/card.js +105 -0
  97. package/lib/settings/code.d.ts +1 -0
  98. package/lib/settings/code.js +51 -0
  99. package/lib/settings/environments.d.ts +1 -0
  100. package/lib/settings/environments.js +53 -0
  101. package/lib/settings/general.d.ts +1 -0
  102. package/lib/settings/general.js +44 -0
  103. package/lib/settings/index.d.ts +4 -0
  104. package/lib/settings/index.js +22 -0
  105. package/lib/settings/integrations.d.ts +1 -0
  106. package/lib/settings/integrations.js +81 -0
  107. package/lib/settings/keys.d.ts +1 -0
  108. package/lib/settings/keys.js +102 -0
  109. package/lib/settings/machines.d.ts +1 -0
  110. package/lib/settings/machines.js +118 -0
  111. package/lib/settings/members.d.ts +1 -0
  112. package/lib/settings/members.js +67 -0
  113. package/lib/settings/memory.d.ts +1 -0
  114. package/lib/settings/memory.js +56 -0
  115. package/lib/settings/notifications.d.ts +1 -0
  116. package/lib/settings/notifications.js +97 -0
  117. package/lib/settings/privacy.d.ts +1 -0
  118. package/lib/settings/privacy.js +63 -0
  119. package/lib/settings/sections.d.ts +16 -0
  120. package/lib/settings/sections.js +32 -0
  121. package/lib/settings/ui.d.ts +49 -0
  122. package/lib/settings/ui.js +47 -0
  123. package/lib/settings/usage.d.ts +1 -0
  124. package/lib/settings/usage.js +114 -0
  125. package/lib/shelf.d.ts +6 -0
  126. package/lib/shelf.js +86 -16
  127. package/lib/switch.js +1 -2
  128. package/lib/transcript.d.ts +12 -0
  129. package/lib/transcript.js +56 -0
  130. package/lib/voice.d.ts +3 -1
  131. package/lib/voice.js +10 -4
  132. package/package.json +1 -1
  133. package/lib/account.d.ts +0 -14
  134. package/lib/account.js +0 -38
  135. package/lib/mcp.d.ts +0 -1
  136. package/lib/mcp.js +0 -28
@@ -0,0 +1,68 @@
1
+ /**
2
+ * The signed-in person's own settings, as the platform keeps them for every
3
+ * Hanzo surface.
4
+ *
5
+ * GET /v1/pref → {prefs: {…}, updatedAt} an empty document for someone who saved nothing
6
+ * PATCH /v1/pref {key: value | null} a shallow merge; null deletes the key
7
+ *
8
+ * The document is one object shared by every surface, keyed per person and per
9
+ * org. This reads only the keys the builder owns and writes only those, so a
10
+ * key another surface saved is left as it was. A value this version does not
11
+ * recognise reads as unset rather than as a choice.
12
+ */
13
+ import { call } from './call.js';
14
+ const THEMES = ['system', 'light', 'dark'];
15
+ const TEXTS = ['small', 'medium', 'large'];
16
+ const MOTIONS = ['system', 'reduced'];
17
+ const EFFORTS = ['low', 'medium', 'high'];
18
+ const MODES = ['build', 'plan'];
19
+ const obj = (v) => (v && typeof v === 'object' && !Array.isArray(v) ? v : {});
20
+ const text = (v) => (typeof v === 'string' && v.trim() ? v : undefined);
21
+ const one = (v, of) => (of.includes(v) ? v : undefined);
22
+ /** Drop the keys that say nothing, so a read and a merge compare cleanly. */
23
+ function defined(o) {
24
+ return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== undefined));
25
+ }
26
+ export function code(raw) {
27
+ const c = obj(raw);
28
+ const out = defined({
29
+ model: text(c.model),
30
+ effort: one(c.effort, EFFORTS),
31
+ mode: one(c.mode, MODES),
32
+ place: typeof c.place === 'string' ? c.place : undefined,
33
+ });
34
+ return Object.keys(out).length ? out : undefined;
35
+ }
36
+ /** The builder's keys from a whole preference document. */
37
+ export function prefs(raw) {
38
+ const p = obj(raw);
39
+ return defined({
40
+ theme: one(p.theme, THEMES),
41
+ text: one(p.text, TEXTS),
42
+ motion: one(p.motion, MOTIONS),
43
+ language: text(p.language),
44
+ callName: text(p.callName),
45
+ work: text(p.work),
46
+ instructions: text(p.instructions),
47
+ code: code(p.code),
48
+ });
49
+ }
50
+ /** What a document reads as once `patch` is merged into it — the platform's own rule. */
51
+ export function merge(base, patch) {
52
+ const out = { ...base };
53
+ for (const [k, v] of Object.entries(patch)) {
54
+ if (v === null)
55
+ delete out[k];
56
+ else if (v !== undefined)
57
+ out[k] = v;
58
+ }
59
+ return prefs(out);
60
+ }
61
+ export async function read(t) {
62
+ const r = await call(t, 'GET', '/v1/pref');
63
+ return prefs(r?.prefs);
64
+ }
65
+ export async function save(t, patch) {
66
+ const r = await call(t, 'PATCH', '/v1/pref', patch);
67
+ return prefs(r?.prefs);
68
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The signed-in person's own profile on Hanzo IAM: the name they are shown by
3
+ * and their photo.
4
+ *
5
+ * PUT /v1/iam/account {displayName} → {status, data: {owner, name, displayName, avatar, …}}
6
+ * POST /v1/account/avatar multipart `file` → {avatar} the photo, also written to IAM
7
+ *
8
+ * Both act on the caller alone: neither request names a person. The email is a
9
+ * sign-in identifier and changes only through IAM's verification, never here.
10
+ */
11
+ import { type Target } from './call.ts';
12
+ export interface Profile {
13
+ displayName: string;
14
+ avatar: string;
15
+ }
16
+ /** Save the name the person is shown by. An empty name is refused here. */
17
+ export declare function rename(t: Target, name: string): Promise<Profile>;
18
+ /** The formats the platform keeps a photo in; it decides by the bytes, this only filters the picker. */
19
+ export declare const PHOTO = "image/png,image/jpeg,image/gif,image/webp";
20
+ /** Store a new photo and answer its address. */
21
+ export declare function photo(t: Target, file: Blob): Promise<string>;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The signed-in person's own profile on Hanzo IAM: the name they are shown by
3
+ * and their photo.
4
+ *
5
+ * PUT /v1/iam/account {displayName} → {status, data: {owner, name, displayName, avatar, …}}
6
+ * POST /v1/account/avatar multipart `file` → {avatar} the photo, also written to IAM
7
+ *
8
+ * Both act on the caller alone: neither request names a person. The email is a
9
+ * sign-in identifier and changes only through IAM's verification, never here.
10
+ */
11
+ import { call, headers, reason, Refusal, unwrap } from './call.js';
12
+ const str = (v) => (typeof v === 'string' ? v : '');
13
+ const obj = (v) => (v && typeof v === 'object' ? v : {});
14
+ /** Save the name the person is shown by. An empty name is refused here. */
15
+ export async function rename(t, name) {
16
+ const displayName = name.trim();
17
+ if (!displayName)
18
+ throw new Error('A name cannot be empty');
19
+ const p = obj(unwrap(await call(t, 'PUT', '/v1/iam/account', { displayName })));
20
+ return { displayName: str(p.displayName), avatar: str(p.avatar) };
21
+ }
22
+ /** The formats the platform keeps a photo in; it decides by the bytes, this only filters the picker. */
23
+ export const PHOTO = 'image/png,image/jpeg,image/gif,image/webp';
24
+ const LIMIT = 8 << 20;
25
+ /** Store a new photo and answer its address. */
26
+ export async function photo(t, file) {
27
+ if (!file.size)
28
+ throw new Error('That file is empty');
29
+ if (file.size > LIMIT)
30
+ throw new Error('A photo is at most 8 MB');
31
+ const form = new FormData();
32
+ form.append('file', file);
33
+ const res = await fetch(`${t.api}/v1/account/avatar`, { method: 'POST', headers: headers(t), body: form, cache: 'no-store' });
34
+ if (!res.ok)
35
+ throw new Refusal(res.status, (await reason(res)) || `POST /v1/account/avatar answered ${res.status}`);
36
+ const url = str(obj(await res.json()).avatar);
37
+ if (!url.startsWith('https://'))
38
+ throw new Refusal(502, 'The photo was stored without an address this page can show');
39
+ return url;
40
+ }
@@ -1,10 +1,15 @@
1
1
  /**
2
2
  * The org's projects: what it has built, and where each one is served.
3
3
  *
4
- * GET /v1/projects → Project[]
4
+ * GET /v1/projects → Project[]
5
+ * PATCH /v1/projects/{slug} rename it, or make it public or private
6
+ * DELETE /v1/projects/{slug} delete it and take its site down
5
7
  *
6
8
  * `slug` is the one key every surface shares — the address under /dev, the
7
- * site's name, the `project` a run is tagged with.
9
+ * site's name, the `project` a run is tagged with. A project belongs to the org,
10
+ * not to a person: the record names no author, so any member lists, changes and
11
+ * deletes it. Private is paid, and an unfunded org is refused rather than left
12
+ * public without being told.
8
13
  */
9
14
  import { type Target } from './call.ts';
10
15
  export interface Project {
@@ -17,12 +22,24 @@ export interface Project {
17
22
  status: string;
18
23
  /** The deployed address, or '' when nothing has shipped. */
19
24
  live: string;
25
+ /** `public` or `private`, or '' when the platform did not say. */
26
+ visibility: '' | Visibility;
27
+ /** Unix seconds; 0 when unknown. A deploy does not move `updated`. */
28
+ created: number;
20
29
  updated: number;
21
30
  }
31
+ export type Visibility = 'public' | 'private';
22
32
  /** An address the preview may frame: https, or http on loopback when the builder is local too. */
23
33
  export declare function safe(url: string, local?: boolean): string;
24
34
  export declare function project(raw: unknown): Project;
25
35
  export declare function projects(t: Target): Promise<Project[]>;
36
+ /** Change a project's name or who can see it; what is not sent is left as it is. */
37
+ export declare function change(t: Target, slug: string, what: {
38
+ name?: string;
39
+ visibility?: Visibility;
40
+ }): Promise<Project>;
41
+ /** Delete a project. Its site stops answering and its slug is free again. */
42
+ export declare function remove(t: Target, slug: string): Promise<void>;
26
43
  /** The repository name a project's clone URL names — the last path segment, without `.git`. */
27
44
  export declare function name(clone: string): string;
28
45
  /** A starter from the public catalog. */
@@ -1,13 +1,19 @@
1
1
  /**
2
2
  * The org's projects: what it has built, and where each one is served.
3
3
  *
4
- * GET /v1/projects → Project[]
4
+ * GET /v1/projects → Project[]
5
+ * PATCH /v1/projects/{slug} rename it, or make it public or private
6
+ * DELETE /v1/projects/{slug} delete it and take its site down
5
7
  *
6
8
  * `slug` is the one key every surface shares — the address under /dev, the
7
- * site's name, the `project` a run is tagged with.
9
+ * site's name, the `project` a run is tagged with. A project belongs to the org,
10
+ * not to a person: the record names no author, so any member lists, changes and
11
+ * deletes it. Private is paid, and an unfunded org is refused rather than left
12
+ * public without being told.
8
13
  */
9
- import { call } from './call.js';
14
+ import { call, seg } from './call.js';
10
15
  const str = (v) => (typeof v === 'string' ? v : '');
16
+ const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : 0);
11
17
  const loopback = (host) => host === 'localhost' || host === '127.0.0.1';
12
18
  /** Whether this page itself is served from loopback — local work. */
13
19
  const here = () => typeof window !== 'undefined' && loopback(window.location.hostname);
@@ -37,7 +43,9 @@ export function project(raw) {
37
43
  branch: str(repo.branch),
38
44
  status: str(p.status),
39
45
  live: safe(str(p.liveUrl)),
40
- updated: typeof p.updatedAt === 'number' ? p.updatedAt : 0,
46
+ visibility: p.visibility === 'public' || p.visibility === 'private' ? p.visibility : '',
47
+ created: num(p.createdAt),
48
+ updated: num(p.updatedAt),
41
49
  };
42
50
  }
43
51
  export async function projects(t) {
@@ -48,6 +56,23 @@ export async function projects(t) {
48
56
  .filter((p) => p.slug)
49
57
  .sort((a, b) => b.updated - a.updated);
50
58
  }
59
+ /** Change a project's name or who can see it; what is not sent is left as it is. */
60
+ export async function change(t, slug, what) {
61
+ const body = {};
62
+ if (what.name !== undefined) {
63
+ const n = what.name.trim();
64
+ if (!n)
65
+ throw new Error('A project needs a name');
66
+ body.name = n;
67
+ }
68
+ if (what.visibility !== undefined)
69
+ body.visibility = what.visibility;
70
+ return project(await call(t, 'PATCH', `/v1/projects/${seg(slug)}`, body));
71
+ }
72
+ /** Delete a project. Its site stops answering and its slug is free again. */
73
+ export async function remove(t, slug) {
74
+ await call(t, 'DELETE', `/v1/projects/${seg(slug)}`);
75
+ }
51
76
  /** The repository name a project's clone URL names — the last path segment, without `.git`. */
52
77
  export function name(clone) {
53
78
  const cut = clone.replace(/\.git$/, '').replace(/\/+$/, '');
@@ -0,0 +1,42 @@
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 { type Target } from './call.ts';
14
+ export interface Connector {
15
+ id: string;
16
+ name: string;
17
+ /** Whether this deployment can connect it at all. */
18
+ available: boolean;
19
+ connected: boolean;
20
+ /** The connected account's own name: the Slack workspace, the GitHub login. */
21
+ account: string;
22
+ since: string;
23
+ /** Why it cannot be connected here, when it cannot. */
24
+ note: string;
25
+ }
26
+ export declare function connector(raw: unknown, id?: string): Connector;
27
+ export declare function read(t: Target, id: string): Promise<Connector>;
28
+ /** The provider's consent page for this org. */
29
+ export declare function authorize(t: Target, id: string): Promise<string>;
30
+ export declare function disconnect(t: Target, id: string): Promise<void>;
31
+ export interface Channel {
32
+ id: string;
33
+ name: string;
34
+ private: boolean;
35
+ /** Whether Hanzo has joined it. */
36
+ member: boolean;
37
+ }
38
+ export interface Channels {
39
+ channels: Channel[];
40
+ next: string;
41
+ }
42
+ export declare function channels(t: Target, cursor?: string): Promise<Channels>;
@@ -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
+ }
@@ -1,10 +1,13 @@
1
1
  /**
2
2
  * Runs, as the platform records them: one session per run.
3
3
  *
4
- * GET /v1/agent/sessions?kind=&project=&status=&limit= newest first
5
- * GET /v1/agent/sessions/{id} + the 50 most recent events
6
- * POST /v1/agent/sessions/{id}/message {message} steer a running run
7
- * POST /v1/agent/sessions/{id}/stop {message} end it, work kept
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
  *
@@ -37,6 +40,10 @@ export interface Session {
37
40
  pr: string;
38
41
  /** The sandbox the run leased, once it has one. It is gone when the run ends. */
39
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;
40
47
  events: number;
41
48
  createdAt: string;
42
49
  updatedAt: string;
@@ -64,13 +71,34 @@ export declare function took(runs: Session[], mode: string): [number, number] |
64
71
  export interface ListQuery {
65
72
  kind?: string;
66
73
  project?: string;
74
+ /** running, paused, done or error: the four the platform filters on. */
67
75
  status?: string;
68
76
  limit?: number;
77
+ /** The `next` of the page before. */
78
+ after?: string;
69
79
  }
80
+ export interface Page {
81
+ sessions: Session[];
82
+ /** The cursor for the page after this one, or '' on the last. */
83
+ next: string;
84
+ }
85
+ export declare function page(t: Target, q?: ListQuery): Promise<Page>;
70
86
  export declare function list(t: Target, q?: ListQuery): Promise<Session[]>;
71
87
  export declare function get(t: Target, id: string): Promise<Detail>;
72
88
  export declare function message(t: Target, id: string, text: string): Promise<void>;
73
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;
74
102
  export interface Watch {
75
103
  session?: (s: Session) => void;
76
104
  event?: (e: Event) => void;
@@ -1,10 +1,13 @@
1
1
  /**
2
2
  * Runs, as the platform records them: one session per run.
3
3
  *
4
- * GET /v1/agent/sessions?kind=&project=&status=&limit= newest first
5
- * GET /v1/agent/sessions/{id} + the 50 most recent events
6
- * POST /v1/agent/sessions/{id}/message {message} steer a running run
7
- * POST /v1/agent/sessions/{id}/stop {message} end it, work kept
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,8 @@ export function session(raw) {
35
38
  mode: str(s.mode),
36
39
  pr: str(s.pr),
37
40
  sandbox: str(s.sandbox),
41
+ org: str(s.org),
42
+ published: s.published === true,
38
43
  events: num(s.events),
39
44
  createdAt: str(s.createdAt),
40
45
  updatedAt: str(s.updatedAt),
@@ -68,9 +73,15 @@ export function took(runs, mode) {
68
73
  const at = (q) => Math.max(1, Math.round(spans[Math.min(spans.length - 1, Math.floor(q * spans.length))] / 60_000));
69
74
  return [at(0.5), at(0.9)];
70
75
  }
71
- export async function list(t, q = {}) {
76
+ export async function page(t, q = {}) {
72
77
  const raw = obj(await call(t, 'GET', `/v1/agent/sessions${query({ ...q })}`));
73
- return (Array.isArray(raw.sessions) ? raw.sessions : []).map(session).filter((s) => s.id);
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;
74
85
  }
75
86
  export async function get(t, id) {
76
87
  const raw = obj(await call(t, 'GET', `/v1/agent/sessions/${seg(id)}`));
@@ -88,6 +99,31 @@ export async function message(t, id, text) {
88
99
  export async function stop(t, id, why = 'Stopped from the builder') {
89
100
  await call(t, 'POST', `/v1/agent/sessions/${seg(id)}/stop`, { message: why });
90
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)}` : '';
91
127
  /**
92
128
  * Follow one run's tree until `signal` aborts. Reconnects with a capped backoff;
93
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
+ }