@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.
- package/README.md +97 -25
- 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/coding.d.ts +31 -1
- package/lib/api/coding.js +50 -1
- 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/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/sessions.d.ts +32 -4
- package/lib/api/sessions.js +42 -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 +221 -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 +4 -1
- package/lib/desk.js +3 -16
- 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 +4 -4
- package/lib/git.d.ts +4 -0
- package/lib/git.js +6 -2
- package/lib/host.d.ts +5 -0
- package/lib/landing.d.ts +10 -0
- package/lib/landing.js +30 -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 +184 -32
- 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/mcp.d.ts +0 -1
- package/lib/mcp.js +0 -28
|
@@ -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;
|
package/lib/api/turn.js
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
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 person's steering is a `control` {command, message}.
|
|
15
|
+
*
|
|
16
|
+
* `cards` is the transcript as it is drawn: the run's steps, and the agent's own
|
|
17
|
+
* output read by what each part is (harness.ts). The output a card shows is what
|
|
18
|
+
* the run's Terminal log already prints; a command's working directory, which is
|
|
19
|
+
* the sandbox's layout, is dropped.
|
|
20
|
+
*/
|
|
21
|
+
import { harness } from './harness.js';
|
|
1
22
|
const str = (v) => (typeof v === 'string' ? v : '');
|
|
2
23
|
/** The payload as an object. A live frame may carry it as a JSON string. */
|
|
3
24
|
export function decode(payload) {
|
|
@@ -50,7 +71,10 @@ function status(b, mode) {
|
|
|
50
71
|
case 'stopped':
|
|
51
72
|
return branch && b.changed ? `Stopped — work kept on ${branch}` : 'Stopped';
|
|
52
73
|
case 'paused':
|
|
53
|
-
return 'Paused';
|
|
74
|
+
return branch && b.changed ? `Paused — work kept on ${branch}` : 'Paused';
|
|
75
|
+
case 'followed':
|
|
76
|
+
// A run redirected from a chat thread is carried on by a follow-up (apps/coding steer.go).
|
|
77
|
+
return str(b.next) ? 'Continued in a follow-up run' : 'The follow-up run could not start';
|
|
54
78
|
}
|
|
55
79
|
return str(b.status);
|
|
56
80
|
}
|
|
@@ -123,7 +147,12 @@ export function shell(events) {
|
|
|
123
147
|
}
|
|
124
148
|
return out.slice(-400);
|
|
125
149
|
}
|
|
126
|
-
/**
|
|
150
|
+
/**
|
|
151
|
+
* Tool calls, in the order they were first named. A later status for the same
|
|
152
|
+
* step wins, and a run's steps follow one another, so a step another has
|
|
153
|
+
* followed is over. `exit` is not a step: it is how the sandbox says one command
|
|
154
|
+
* in it ended (apps/sandbox work.go).
|
|
155
|
+
*/
|
|
127
156
|
export function steps(events) {
|
|
128
157
|
const order = [];
|
|
129
158
|
const done = new Map();
|
|
@@ -134,14 +163,14 @@ export function steps(events) {
|
|
|
134
163
|
if (!body || typeof body === 'string')
|
|
135
164
|
continue;
|
|
136
165
|
const name = str(body.step);
|
|
137
|
-
if (!name)
|
|
166
|
+
if (!name || name === 'exit')
|
|
138
167
|
continue;
|
|
139
168
|
if (!done.has(name))
|
|
140
169
|
order.push(name);
|
|
141
170
|
const status = str(body.status);
|
|
142
171
|
done.set(name, status === 'ok' || status === 'done' || status === 'error');
|
|
143
172
|
}
|
|
144
|
-
return order.map((name) => ({ name, done: done.get(name) === true }));
|
|
173
|
+
return order.map((name, i) => ({ name, done: i < order.length - 1 || done.get(name) === true }));
|
|
145
174
|
}
|
|
146
175
|
/**
|
|
147
176
|
* What the run narrated about how it ended, latest winning. Status text only:
|
|
@@ -190,3 +219,191 @@ export function pull(url, repo) {
|
|
|
190
219
|
return { href: '', label: '' };
|
|
191
220
|
return { href: url, label: `#${m[3]}` };
|
|
192
221
|
}
|
|
222
|
+
/** The platform's own words for the step that starts the agent (apps/coding sandboxrunner.go). */
|
|
223
|
+
const TASK = 'running the task';
|
|
224
|
+
/** What the run's own steps are called on a card. */
|
|
225
|
+
const NAMES = {
|
|
226
|
+
lease: 'Sandbox',
|
|
227
|
+
clone: 'Clone',
|
|
228
|
+
secrets: 'Secrets',
|
|
229
|
+
install: 'Install',
|
|
230
|
+
start: 'Start',
|
|
231
|
+
push: 'Push',
|
|
232
|
+
keep: 'Keep the work',
|
|
233
|
+
};
|
|
234
|
+
/** Steps another card already says: `leased` finishes the lease, `done` is the final status, `ended` is housekeeping. */
|
|
235
|
+
const QUIET = new Set(['leased', 'done', 'ended']);
|
|
236
|
+
const ranOf = (status) => (status === 'ok' || status === 'done' ? 'done' : status === 'error' ? 'error' : 'running');
|
|
237
|
+
/**
|
|
238
|
+
* A run's events, as the cards its transcript draws. `mode` is the run's, from
|
|
239
|
+
* its record: a plan's final status is its plan, and nothing else draws a plan.
|
|
240
|
+
*
|
|
241
|
+
* The run's own steps arrive as `tool-call` {step, message, status}; each
|
|
242
|
+
* command in the sandbox narrates its output as `log` {message} chunks and ends
|
|
243
|
+
* with `tool-call` {step: "exit"}. The output after the step that starts the
|
|
244
|
+
* agent is the agent's, read by its harness's grammar (harness.ts); after any
|
|
245
|
+
* other step it is that step's. Once the agent has exited, the commands the run
|
|
246
|
+
* runs before it pushes commit its work.
|
|
247
|
+
*/
|
|
248
|
+
export function cards(events, mode = '') {
|
|
249
|
+
const out = [];
|
|
250
|
+
let seq = 0;
|
|
251
|
+
let n = 0;
|
|
252
|
+
const mint = () => `${seq}.${n++}`;
|
|
253
|
+
let phase = null;
|
|
254
|
+
let agent = null;
|
|
255
|
+
// The agent has run and exited: output with no step of its own is the run's.
|
|
256
|
+
let exited = false;
|
|
257
|
+
const settle = () => {
|
|
258
|
+
agent?.end();
|
|
259
|
+
agent = null;
|
|
260
|
+
phase = null;
|
|
261
|
+
};
|
|
262
|
+
const answers = new Set();
|
|
263
|
+
for (const e of [...events].sort((a, b) => a.seq - b.seq)) {
|
|
264
|
+
seq = e.seq;
|
|
265
|
+
n = 0;
|
|
266
|
+
const body = decode(e.payload);
|
|
267
|
+
const b = body && typeof body === 'object' ? body : null;
|
|
268
|
+
switch (e.kind) {
|
|
269
|
+
case 'log': {
|
|
270
|
+
const text = typeof body === 'string' ? body : str(b?.message) || str(b?.text);
|
|
271
|
+
if (!text)
|
|
272
|
+
break;
|
|
273
|
+
if (!agent && !phase && exited) {
|
|
274
|
+
phase = { kind: 'step', key: mint(), name: 'Commit', detail: 'committing the work', output: '', ran: 'running' };
|
|
275
|
+
out.push(phase);
|
|
276
|
+
}
|
|
277
|
+
if (!agent && !phase)
|
|
278
|
+
agent = harness(out, mint);
|
|
279
|
+
if (agent)
|
|
280
|
+
agent.feed(text);
|
|
281
|
+
else if (phase)
|
|
282
|
+
phase.output += text;
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
case 'tool-call': {
|
|
286
|
+
if (!b)
|
|
287
|
+
break;
|
|
288
|
+
const step = str(b.step);
|
|
289
|
+
const message = str(b.message);
|
|
290
|
+
if (step === 'exit') {
|
|
291
|
+
// One command in the sandbox ended: `exit 0`, `exit 1`, or `ended: …`.
|
|
292
|
+
if (agent) {
|
|
293
|
+
settle();
|
|
294
|
+
exited = true;
|
|
295
|
+
}
|
|
296
|
+
else if (phase && phase.ran === 'running')
|
|
297
|
+
phase.ran = message === 'exit 0' ? 'done' : 'error';
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
if (message === TASK) {
|
|
301
|
+
settle();
|
|
302
|
+
exited = false;
|
|
303
|
+
agent = harness(out, mint);
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
if (step === 'leased') {
|
|
307
|
+
const lease = out.find((c) => c.kind === 'step' && c.name === NAMES.lease);
|
|
308
|
+
if (lease)
|
|
309
|
+
lease.ran = 'done';
|
|
310
|
+
}
|
|
311
|
+
if (!step || QUIET.has(step))
|
|
312
|
+
break;
|
|
313
|
+
const name = NAMES[step] ?? step.charAt(0).toUpperCase() + step.slice(1);
|
|
314
|
+
if (phase && phase.name === name) {
|
|
315
|
+
phase.ran = ranOf(str(b.status));
|
|
316
|
+
if (message)
|
|
317
|
+
phase.detail = message;
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
settle();
|
|
321
|
+
phase = { kind: 'step', key: mint(), name, detail: message, output: '', ran: ranOf(str(b.status)) };
|
|
322
|
+
out.push(phase);
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
case 'status': {
|
|
326
|
+
settle();
|
|
327
|
+
if (!b)
|
|
328
|
+
break;
|
|
329
|
+
const text = str(b.plan);
|
|
330
|
+
if (str(b.status) === 'done' && (mode === 'plan' || mode === 'setup') && text) {
|
|
331
|
+
answers.add(text.trim());
|
|
332
|
+
out.push(mode === 'plan' ? { kind: 'plan', key: mint(), text } : { kind: 'said', key: mint(), who: 'agent', text });
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
const line = status(b, mode);
|
|
336
|
+
if (line)
|
|
337
|
+
out.push({ kind: 'note', key: mint(), text: line });
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
case 'control': {
|
|
341
|
+
if (!b)
|
|
342
|
+
break;
|
|
343
|
+
const command = str(b.command);
|
|
344
|
+
const said = str(b.message);
|
|
345
|
+
if (command === 'message' && said)
|
|
346
|
+
out.push({ kind: 'said', key: mint(), who: 'person', text: said });
|
|
347
|
+
else if (command === 'pause')
|
|
348
|
+
out.push({ kind: 'note', key: mint(), text: 'Pause asked' });
|
|
349
|
+
else if (command === 'resume')
|
|
350
|
+
out.push({ kind: 'note', key: mint(), text: 'Resume asked' });
|
|
351
|
+
else if (command === 'stop')
|
|
352
|
+
out.push({ kind: 'note', key: mint(), text: 'Stop asked' });
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
case 'message': {
|
|
356
|
+
const text = typeof body === 'string' ? body : str(b?.text) || str(b?.content) || str(b?.message);
|
|
357
|
+
if (text)
|
|
358
|
+
out.push({ kind: 'said', key: mint(), who: str(b?.role) === 'user' ? 'person' : 'agent', text });
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
settle();
|
|
364
|
+
// The answer the final status carries is also the agent's last message: draw it once, as the answer.
|
|
365
|
+
let kept = false;
|
|
366
|
+
for (let i = out.length - 1; i >= 0; i--) {
|
|
367
|
+
const c = out[i];
|
|
368
|
+
if (!((c.kind === 'said' && c.who === 'agent') || c.kind === 'plan') || !answers.has(c.text.trim()))
|
|
369
|
+
continue;
|
|
370
|
+
if (kept)
|
|
371
|
+
out.splice(i, 1);
|
|
372
|
+
kept = true;
|
|
373
|
+
}
|
|
374
|
+
return out;
|
|
375
|
+
}
|
|
376
|
+
/** The plan a plan run answered with, from its final status, or ''. */
|
|
377
|
+
export function answer(events) {
|
|
378
|
+
let out = '';
|
|
379
|
+
for (const e of [...events].sort((a, b) => a.seq - b.seq)) {
|
|
380
|
+
if (e.kind !== 'status')
|
|
381
|
+
continue;
|
|
382
|
+
const b = decode(e.payload);
|
|
383
|
+
if (b && typeof b === 'object' && str(b.status) === 'done')
|
|
384
|
+
out = str(b.plan);
|
|
385
|
+
}
|
|
386
|
+
return out;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Whether the run has kept its work yet, and whether that work is on its
|
|
390
|
+
* branch. A stop or a pause commits and pushes AFTER it is asked, and says so
|
|
391
|
+
* in its status (apps/coding coding.go interrupted): until then a follow-up
|
|
392
|
+
* would clone a branch the push has not reached.
|
|
393
|
+
*/
|
|
394
|
+
export function settled(events) {
|
|
395
|
+
const out = { settled: false, pushed: false };
|
|
396
|
+
for (const e of [...events].sort((a, b) => a.seq - b.seq)) {
|
|
397
|
+
if (e.kind !== 'status')
|
|
398
|
+
continue;
|
|
399
|
+
const b = decode(e.payload);
|
|
400
|
+
if (!b || typeof b === 'string')
|
|
401
|
+
continue;
|
|
402
|
+
const s = str(b.status);
|
|
403
|
+
if (s === 'done' || s === 'error' || s === 'stopped' || s === 'paused') {
|
|
404
|
+
out.settled = true;
|
|
405
|
+
out.pushed = b.changed === true;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return out;
|
|
409
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The organization's webhooks: https endpoints that each matching platform event
|
|
3
|
+
* is POSTed to, signed.
|
|
4
|
+
*
|
|
5
|
+
* GET /v1/webhook {data: [endpoint]}, newest first, secrets withheld
|
|
6
|
+
* POST /v1/webhook {url, events, description} → 201, the secret shown this once
|
|
7
|
+
* DELETE /v1/webhook/{id} 204
|
|
8
|
+
* POST /v1/webhook/{id}/test one signed test event, sent now → {delivered, httpStatus, durationMs, error}
|
|
9
|
+
* GET /v1/webhook/{id}/deliveries {data: [attempt]}, newest first
|
|
10
|
+
*
|
|
11
|
+
* The org is the caller's; an endpoint another org owns reads as not found.
|
|
12
|
+
* Events are subject patterns (`commerce.order.>`); none means every event.
|
|
13
|
+
*/
|
|
14
|
+
import { type Target } from './call.ts';
|
|
15
|
+
export interface Endpoint {
|
|
16
|
+
id: string;
|
|
17
|
+
url: string;
|
|
18
|
+
/** Subject patterns; empty is every event. */
|
|
19
|
+
events: string[];
|
|
20
|
+
description: string;
|
|
21
|
+
/** active | disabled */
|
|
22
|
+
status: string;
|
|
23
|
+
/** The signing secret: present only in the answer to a create. */
|
|
24
|
+
secret: string;
|
|
25
|
+
created: string;
|
|
26
|
+
/** Settled in the last seven days, and how many of those failed. */
|
|
27
|
+
deliveries: number;
|
|
28
|
+
failures: number;
|
|
29
|
+
}
|
|
30
|
+
export declare function endpoint(raw: unknown): Endpoint;
|
|
31
|
+
export declare function endpoints(t: Target): Promise<Endpoint[]>;
|
|
32
|
+
/** The patterns in what a person typed: comma or space separated. */
|
|
33
|
+
export declare function patterns(typed: string): string[];
|
|
34
|
+
export interface Draft {
|
|
35
|
+
url: string;
|
|
36
|
+
events: string[];
|
|
37
|
+
description: string;
|
|
38
|
+
}
|
|
39
|
+
/** Why a draft cannot be sent, or ''. The platform refuses the same things. */
|
|
40
|
+
export declare function refuse(d: Draft): string;
|
|
41
|
+
export declare function add(t: Target, d: Draft): Promise<Endpoint>;
|
|
42
|
+
export declare function remove(t: Target, id: string): Promise<void>;
|
|
43
|
+
export interface Trial {
|
|
44
|
+
delivered: boolean;
|
|
45
|
+
/** 0 when the endpoint never answered. */
|
|
46
|
+
status: number;
|
|
47
|
+
ms: number;
|
|
48
|
+
error: string;
|
|
49
|
+
}
|
|
50
|
+
export declare function test(t: Target, id: string): Promise<Trial>;
|
|
51
|
+
export interface Attempt {
|
|
52
|
+
subject: string;
|
|
53
|
+
/** ok | retrying | failed */
|
|
54
|
+
status: string;
|
|
55
|
+
code: number;
|
|
56
|
+
attempt: number;
|
|
57
|
+
error: string;
|
|
58
|
+
at: string;
|
|
59
|
+
}
|
|
60
|
+
export declare function deliveries(t: Target, id: string, limit?: number): Promise<Attempt[]>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The organization's webhooks: https endpoints that each matching platform event
|
|
3
|
+
* is POSTed to, signed.
|
|
4
|
+
*
|
|
5
|
+
* GET /v1/webhook {data: [endpoint]}, newest first, secrets withheld
|
|
6
|
+
* POST /v1/webhook {url, events, description} → 201, the secret shown this once
|
|
7
|
+
* DELETE /v1/webhook/{id} 204
|
|
8
|
+
* POST /v1/webhook/{id}/test one signed test event, sent now → {delivered, httpStatus, durationMs, error}
|
|
9
|
+
* GET /v1/webhook/{id}/deliveries {data: [attempt]}, newest first
|
|
10
|
+
*
|
|
11
|
+
* The org is the caller's; an endpoint another org owns reads as not found.
|
|
12
|
+
* Events are subject patterns (`commerce.order.>`); none means every event.
|
|
13
|
+
*/
|
|
14
|
+
import { call, query, seg } from './call.js';
|
|
15
|
+
const str = (v) => (typeof v === 'string' ? v : '');
|
|
16
|
+
const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : 0);
|
|
17
|
+
const obj = (v) => (v && typeof v === 'object' ? v : {});
|
|
18
|
+
const arr = (v) => (Array.isArray(v) ? v : []);
|
|
19
|
+
export function endpoint(raw) {
|
|
20
|
+
const e = obj(raw);
|
|
21
|
+
return {
|
|
22
|
+
id: str(e.id),
|
|
23
|
+
url: str(e.url),
|
|
24
|
+
events: arr(e.events).filter((s) => typeof s === 'string' && s !== ''),
|
|
25
|
+
description: str(e.description),
|
|
26
|
+
status: str(e.status) || 'active',
|
|
27
|
+
secret: str(e.secret),
|
|
28
|
+
created: str(e.created),
|
|
29
|
+
deliveries: num(e.deliveries7d),
|
|
30
|
+
failures: num(e.failures7d),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export async function endpoints(t) {
|
|
34
|
+
const raw = obj(await call(t, 'GET', '/v1/webhook'));
|
|
35
|
+
return arr(raw.data).map(endpoint).filter((e) => e.id);
|
|
36
|
+
}
|
|
37
|
+
/** The patterns in what a person typed: comma or space separated. */
|
|
38
|
+
export function patterns(typed) {
|
|
39
|
+
return [...new Set(typed.split(/[\s,]+/).map((s) => s.trim()).filter(Boolean))];
|
|
40
|
+
}
|
|
41
|
+
/** Why a draft cannot be sent, or ''. The platform refuses the same things. */
|
|
42
|
+
export function refuse(d) {
|
|
43
|
+
let u;
|
|
44
|
+
try {
|
|
45
|
+
u = new URL(d.url.trim());
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return 'Enter the https address to deliver to';
|
|
49
|
+
}
|
|
50
|
+
if (u.protocol !== 'https:')
|
|
51
|
+
return 'A webhook is delivered over https only';
|
|
52
|
+
if (d.events.length > 64)
|
|
53
|
+
return 'At most 64 event patterns';
|
|
54
|
+
return '';
|
|
55
|
+
}
|
|
56
|
+
export async function add(t, d) {
|
|
57
|
+
const why = refuse(d);
|
|
58
|
+
if (why)
|
|
59
|
+
throw new Error(why);
|
|
60
|
+
return endpoint(await call(t, 'POST', '/v1/webhook', { url: d.url.trim(), events: d.events, description: d.description.trim() }));
|
|
61
|
+
}
|
|
62
|
+
export async function remove(t, id) {
|
|
63
|
+
await call(t, 'DELETE', `/v1/webhook/${seg(id)}`);
|
|
64
|
+
}
|
|
65
|
+
export async function test(t, id) {
|
|
66
|
+
const r = obj(await call(t, 'POST', `/v1/webhook/${seg(id)}/test`, {}));
|
|
67
|
+
return { delivered: r.delivered === true, status: num(r.httpStatus), ms: num(r.durationMs), error: str(r.error) };
|
|
68
|
+
}
|
|
69
|
+
export async function deliveries(t, id, limit = 10) {
|
|
70
|
+
const raw = obj(await call(t, 'GET', `/v1/webhook/${seg(id)}/deliveries${query({ limit })}`));
|
|
71
|
+
return arr(raw.data)
|
|
72
|
+
.map(obj)
|
|
73
|
+
.map((a) => ({
|
|
74
|
+
subject: str(a.subject),
|
|
75
|
+
status: str(a.status),
|
|
76
|
+
code: num(a.httpStatus),
|
|
77
|
+
attempt: num(a.attempt),
|
|
78
|
+
error: str(a.error),
|
|
79
|
+
at: str(a.created),
|
|
80
|
+
}));
|
|
81
|
+
}
|
package/lib/ask.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare function Confirm({ open, onOpenChange, title, says, act, run, }: {
|
|
2
|
+
open: boolean;
|
|
3
|
+
onOpenChange: (o: boolean) => void;
|
|
4
|
+
title: string;
|
|
5
|
+
/** The whole consequence, stated plainly. */
|
|
6
|
+
says: string;
|
|
7
|
+
/** The button's word: Delete, Remove, Forget. */
|
|
8
|
+
act: string;
|
|
9
|
+
run: () => Promise<void>;
|
|
10
|
+
}): import("react").JSX.Element;
|
|
11
|
+
export declare function Rename({ open, onOpenChange, title, name, run, }: {
|
|
12
|
+
open: boolean;
|
|
13
|
+
onOpenChange: (o: boolean) => void;
|
|
14
|
+
title: string;
|
|
15
|
+
/** The name it has now. */
|
|
16
|
+
name: string;
|
|
17
|
+
run: (next: string) => Promise<void>;
|
|
18
|
+
}): import("react").JSX.Element;
|
package/lib/ask.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Asking one thing before an act: to confirm one that cannot be undone, or the
|
|
4
|
+
* new name for something. A refusal is said in the dialog, which stays open, so
|
|
5
|
+
* the reason is read where the act was asked for.
|
|
6
|
+
*/
|
|
7
|
+
import { SizableText, XStack } from '@hanzo/gui';
|
|
8
|
+
import { Button, Dialog, DialogContent, DialogTitle, Input } from '@hanzo/ui';
|
|
9
|
+
import { useEffect, useState } from 'react';
|
|
10
|
+
/** One act in flight, and what refused it. */
|
|
11
|
+
function useAct(onDone) {
|
|
12
|
+
const [working, setWorking] = useState(false);
|
|
13
|
+
const [note, setNote] = useState('');
|
|
14
|
+
const go = async (run) => {
|
|
15
|
+
setWorking(true);
|
|
16
|
+
setNote('');
|
|
17
|
+
try {
|
|
18
|
+
await run();
|
|
19
|
+
setWorking(false);
|
|
20
|
+
onDone();
|
|
21
|
+
}
|
|
22
|
+
catch (e) {
|
|
23
|
+
setNote(e instanceof Error ? e.message : 'That did not work');
|
|
24
|
+
setWorking(false);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
return { working, note, setNote, go };
|
|
28
|
+
}
|
|
29
|
+
function Said({ note }) {
|
|
30
|
+
if (!note)
|
|
31
|
+
return null;
|
|
32
|
+
return (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: note }));
|
|
33
|
+
}
|
|
34
|
+
export function Confirm({ open, onOpenChange, title, says, act, run, }) {
|
|
35
|
+
const { working, note, setNote, go } = useAct(() => onOpenChange(false));
|
|
36
|
+
const close = (o) => {
|
|
37
|
+
if (working)
|
|
38
|
+
return;
|
|
39
|
+
setNote('');
|
|
40
|
+
onOpenChange(o);
|
|
41
|
+
};
|
|
42
|
+
return (_jsx(Dialog, { open: open, onOpenChange: close, children: _jsxs(DialogContent, { maxW: 420, showCloseButton: false, children: [_jsx(DialogTitle, { children: title }), _jsx(SizableText, { size: "$2", color: "$soft", children: says }), _jsx(Said, { note: note }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "ghost", disabled: working, onPress: () => close(false), children: "Cancel" }), _jsx(Button, { size: "sm", variant: "destructive", disabled: working, onPress: () => void go(run), children: act })] })] }) }));
|
|
43
|
+
}
|
|
44
|
+
export function Rename({ open, onOpenChange, title, name, run, }) {
|
|
45
|
+
const [value, setValue] = useState(name);
|
|
46
|
+
const { working, note, setNote, go } = useAct(() => onOpenChange(false));
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (open)
|
|
49
|
+
setValue(name);
|
|
50
|
+
}, [open, name]);
|
|
51
|
+
const close = (o) => {
|
|
52
|
+
if (working)
|
|
53
|
+
return;
|
|
54
|
+
setNote('');
|
|
55
|
+
onOpenChange(o);
|
|
56
|
+
};
|
|
57
|
+
const save = () => {
|
|
58
|
+
const next = value.trim();
|
|
59
|
+
if (!next)
|
|
60
|
+
return setNote('It needs a name');
|
|
61
|
+
if (next === name)
|
|
62
|
+
return close(false);
|
|
63
|
+
void go(() => run(next));
|
|
64
|
+
};
|
|
65
|
+
return (_jsx(Dialog, { open: open, onOpenChange: close, children: _jsxs(DialogContent, { maxW: 420, showCloseButton: false, children: [_jsx(DialogTitle, { children: title }), _jsx(Input, { autoFocus: true, value: value, onChangeText: setValue, "aria-label": "Name", onKeyDown: (e) => {
|
|
66
|
+
if ((e.key ?? e.nativeEvent?.key) === 'Enter')
|
|
67
|
+
save();
|
|
68
|
+
} }), _jsx(Said, { note: note }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "ghost", disabled: working, onPress: () => close(false), children: "Cancel" }), _jsx(Button, { size: "sm", disabled: working, onPress: save, children: "Save" })] })] }) }));
|
|
69
|
+
}
|