@byokit/herdr 0.1.0-next.dbe7142 → 0.1.0
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/CHANGELOG.md +72 -0
- package/README.md +184 -14
- package/dist/agents.js +48 -7
- package/dist/binary.d.ts +29 -0
- package/dist/binary.js +132 -0
- package/dist/constants.d.ts +7 -0
- package/dist/constants.js +4 -0
- package/dist/device.d.ts +34 -8
- package/dist/device.js +152 -49
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/kit.d.ts +28 -2
- package/dist/kit.js +256 -50
- package/dist/link.js +118 -2
- package/dist/supervise.js +11 -2
- package/dist/testing/contract.d.ts +13 -3
- package/dist/testing/contract.js +95 -7
- package/dist/testing/fake-herdr/server.d.ts +7 -0
- package/dist/testing/fake-herdr/server.js +89 -11
- package/dist/testing/fake-herdr/world.d.ts +4 -0
- package/dist/testing/fake-herdr/world.js +3 -0
- package/dist/testing/index.d.ts +2 -2
- package/dist/types.d.ts +56 -21
- package/package.json +5 -1
package/dist/device.d.ts
CHANGED
|
@@ -1,15 +1,44 @@
|
|
|
1
1
|
import type { DeviceLink } from '@byokit/link';
|
|
2
|
-
import type { AgentRef, BlockedAgent, HerdrSnapshot, HerdrState, PromptReceipt, StartAgent } from './types.ts';
|
|
2
|
+
import type { AgentRef, AgentStatus, BlockedAgent, HerdrEventName, HerdrEventOf, HerdrMethod, HerdrParams, HerdrResult, HerdrSnapshot, HerdrState, HerdrSubscription, PromptReceipt, StartAgent } from './types.ts';
|
|
3
|
+
export type { HerdrEventName, HerdrEventOf, HerdrEvents, HerdrMethod, HerdrMethods, HerdrParams, HerdrResult, HerdrSubscription, } from './types.ts';
|
|
4
|
+
/** One `hd.events` frame (7.2); `raw` carries a line that was not JSON. */
|
|
5
|
+
export type HerdrLinkEvent = {
|
|
6
|
+
type: 'snapshot';
|
|
7
|
+
snapshot: HerdrSnapshot;
|
|
8
|
+
} | {
|
|
9
|
+
type: 'blocked';
|
|
10
|
+
change: 'added' | 'resolved';
|
|
11
|
+
blocked: BlockedAgent;
|
|
12
|
+
} | {
|
|
13
|
+
type: 'raw';
|
|
14
|
+
line: string;
|
|
15
|
+
};
|
|
16
|
+
/** A terminal over the link (7.2): Herdr's NDJSON frames both ways. */
|
|
17
|
+
export type DeviceTerminal = {
|
|
18
|
+
ready: Promise<void>;
|
|
19
|
+
exited: Promise<{
|
|
20
|
+
reason: string | null;
|
|
21
|
+
}>;
|
|
22
|
+
onFrame(fn: (line: string) => void): () => void;
|
|
23
|
+
send(line: string): void;
|
|
24
|
+
close(): void;
|
|
25
|
+
};
|
|
3
26
|
export { openNotice } from './notices.ts';
|
|
27
|
+
export { agentWords, stateWords, words, WORDS, type WordKey } from './words.ts';
|
|
4
28
|
export declare function herdrDevice(link: DeviceLink): {
|
|
5
29
|
state(): Promise<{
|
|
6
30
|
state: HerdrState;
|
|
7
31
|
words: string;
|
|
8
32
|
}>;
|
|
9
33
|
tree(): Promise<HerdrSnapshot>;
|
|
34
|
+
agentKinds(): Promise<string[]>;
|
|
10
35
|
startAgent(o: StartAgent): Promise<AgentRef>;
|
|
11
36
|
prompt(paneId: string, text: string): Promise<PromptReceipt>;
|
|
12
37
|
keys(paneId: string, keys: string[]): Promise<void>;
|
|
38
|
+
wait(paneId: string, o: {
|
|
39
|
+
until?: AgentStatus[];
|
|
40
|
+
timeoutMs: number;
|
|
41
|
+
}): Promise<AgentStatus>;
|
|
13
42
|
read(paneId: string, o?: {
|
|
14
43
|
source?: 'visible' | 'recent' | 'recent_unwrapped' | 'detection';
|
|
15
44
|
lines?: number;
|
|
@@ -24,17 +53,14 @@ export declare function herdrDevice(link: DeviceLink): {
|
|
|
24
53
|
tab?: string;
|
|
25
54
|
workspace?: string;
|
|
26
55
|
}): Promise<void>;
|
|
27
|
-
events(): AsyncIterable<
|
|
56
|
+
events(): AsyncIterable<HerdrLinkEvent>;
|
|
57
|
+
subscribe<E extends HerdrEventName>(subs: HerdrSubscription<E>[], on: (e: HerdrEventOf<E>) => void, onError?: (message: string) => void): () => void;
|
|
28
58
|
registerNotices(seed: Uint8Array): Promise<void>;
|
|
29
59
|
openNotice(data: Record<string, unknown>, seed: Uint8Array): BlockedAgent | null;
|
|
30
|
-
call(method:
|
|
60
|
+
call<M extends HerdrMethod>(method: M, params: HerdrParams<M>): Promise<HerdrResult<M>>;
|
|
31
61
|
terminal(paneId: string, o: {
|
|
32
62
|
mode: 'control' | 'observe';
|
|
33
63
|
cols: number;
|
|
34
64
|
rows: number;
|
|
35
|
-
}):
|
|
36
|
-
onFrame(fn: (line: string) => void): () => void;
|
|
37
|
-
send(line: string): void;
|
|
38
|
-
close(): void;
|
|
39
|
-
};
|
|
65
|
+
}): DeviceTerminal;
|
|
40
66
|
};
|
package/dist/device.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { openNotice } from "./notices.js";
|
|
2
|
+
export { agentWords, stateWords, words, WORDS } from "./words.js";
|
|
2
3
|
import { boxPublicKeyB64, openNotice } from "./notices.js";
|
|
3
4
|
const decodeLines = () => {
|
|
4
5
|
const text = new TextDecoder();
|
|
@@ -18,42 +19,73 @@ const decodeLines = () => {
|
|
|
18
19
|
},
|
|
19
20
|
};
|
|
20
21
|
};
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
22
|
+
/**
|
|
23
|
+
* A link stream as newline-delimited JSON frames; unparsed lines surface as `{ type: 'raw', line }`. A plain
|
|
24
|
+
* iterator rather than an async generator: `return()` (a `break`, or a view that stops watching) ends the link
|
|
25
|
+
* stream even while a `next()` waits for the next frame, which a generator parked at an await cannot do.
|
|
26
|
+
*/
|
|
27
|
+
function streamFrames(link, op, args) {
|
|
28
|
+
return {
|
|
29
|
+
[Symbol.asyncIterator]() {
|
|
30
|
+
const ready = [];
|
|
31
|
+
const lines = decodeLines();
|
|
32
|
+
let s;
|
|
33
|
+
let opening;
|
|
34
|
+
let failed;
|
|
35
|
+
let ended = false;
|
|
36
|
+
let closed = false;
|
|
37
|
+
let wake;
|
|
38
|
+
const rouse = () => { wake?.(); wake = undefined; };
|
|
39
|
+
const emit = (frame) => { ready.push(frame); rouse(); };
|
|
40
|
+
const open = () => opening ??= link.stream(op, args).then((opened) => {
|
|
41
|
+
if (closed) {
|
|
42
|
+
opened.end();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
s = opened;
|
|
46
|
+
opened.onData = (chunk) => {
|
|
47
|
+
for (const line of lines.push(chunk)) {
|
|
48
|
+
try {
|
|
49
|
+
emit(JSON.parse(line));
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
emit({ type: 'raw', line });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
opened.onEnd = () => {
|
|
57
|
+
for (const line of lines.flush())
|
|
58
|
+
emit({ type: 'raw', line });
|
|
59
|
+
ended = true;
|
|
60
|
+
rouse();
|
|
61
|
+
};
|
|
62
|
+
}, (error) => { failed = { error }; rouse(); });
|
|
63
|
+
return {
|
|
64
|
+
async next() {
|
|
65
|
+
void open();
|
|
66
|
+
for (;;) {
|
|
67
|
+
if (closed)
|
|
68
|
+
return { value: undefined, done: true };
|
|
69
|
+
if (ready.length > 0)
|
|
70
|
+
return { value: ready.shift(), done: false };
|
|
71
|
+
if (failed)
|
|
72
|
+
throw failed.error;
|
|
73
|
+
if (ended)
|
|
74
|
+
return { value: undefined, done: true };
|
|
75
|
+
await new Promise((resolve) => { wake = resolve; });
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
async return() {
|
|
79
|
+
if (!closed) {
|
|
80
|
+
closed = true;
|
|
81
|
+
s?.end();
|
|
82
|
+
rouse();
|
|
83
|
+
}
|
|
84
|
+
return { value: undefined, done: true };
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
},
|
|
49
88
|
};
|
|
50
|
-
for (;;) {
|
|
51
|
-
while (ready.length > 0)
|
|
52
|
-
yield ready.shift();
|
|
53
|
-
if (ended)
|
|
54
|
-
return;
|
|
55
|
-
await new Promise((resolve) => { wake = resolve; });
|
|
56
|
-
}
|
|
57
89
|
}
|
|
58
90
|
const terminalHandle = (opening) => {
|
|
59
91
|
const listeners = new Set();
|
|
@@ -61,8 +93,29 @@ const terminalHandle = (opening) => {
|
|
|
61
93
|
let s;
|
|
62
94
|
const queued = [];
|
|
63
95
|
let dead = false;
|
|
64
|
-
|
|
65
|
-
|
|
96
|
+
let framed = false;
|
|
97
|
+
let markReady;
|
|
98
|
+
let failReady;
|
|
99
|
+
let markExited;
|
|
100
|
+
const ready = new Promise((resolve, reject) => { markReady = resolve; failReady = reject; });
|
|
101
|
+
const exited = new Promise((resolve) => { markExited = resolve; });
|
|
102
|
+
ready.catch(() => { }); // an app that never awaits ready must not see an unhandled rejection
|
|
103
|
+
const deliver = (line) => {
|
|
104
|
+
if (!framed) {
|
|
105
|
+
framed = true;
|
|
106
|
+
markReady();
|
|
107
|
+
}
|
|
108
|
+
for (const fn of listeners)
|
|
109
|
+
fn(line);
|
|
110
|
+
};
|
|
111
|
+
const finish = (reason) => {
|
|
112
|
+
dead = true;
|
|
113
|
+
if (!framed)
|
|
114
|
+
failReady(new Error(reason ?? 'herdr: the terminal ended before its first frame'));
|
|
115
|
+
markExited({ reason });
|
|
116
|
+
};
|
|
117
|
+
// The handle returns synchronously; the stream opens underneath and sends made before it opens
|
|
118
|
+
// queue until then (frames cannot arrive before the open anyway).
|
|
66
119
|
opening.then((opened) => {
|
|
67
120
|
if (dead) {
|
|
68
121
|
opened.end();
|
|
@@ -70,32 +123,79 @@ const terminalHandle = (opening) => {
|
|
|
70
123
|
}
|
|
71
124
|
s = opened;
|
|
72
125
|
opened.onData = (chunk) => {
|
|
73
|
-
for (const line of lines.push(chunk))
|
|
74
|
-
|
|
75
|
-
fn(line);
|
|
76
|
-
}
|
|
126
|
+
for (const line of lines.push(chunk))
|
|
127
|
+
deliver(line);
|
|
77
128
|
};
|
|
78
|
-
opened.onEnd = () => {
|
|
79
|
-
for (const line of lines.flush())
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
129
|
+
opened.onEnd = (error) => {
|
|
130
|
+
for (const line of lines.flush())
|
|
131
|
+
deliver(line);
|
|
132
|
+
finish(error ?? null);
|
|
83
133
|
};
|
|
84
134
|
for (const line of queued.splice(0))
|
|
85
135
|
void opened.write(`${line}\n`).catch(() => { });
|
|
86
|
-
}, () => {
|
|
136
|
+
}, (e) => { finish(e instanceof Error ? e.message : 'failed'); });
|
|
87
137
|
return {
|
|
138
|
+
ready,
|
|
139
|
+
exited,
|
|
88
140
|
onFrame: (fn) => {
|
|
89
141
|
listeners.add(fn);
|
|
90
142
|
return () => { listeners.delete(fn); };
|
|
91
143
|
},
|
|
92
144
|
send: (line) => {
|
|
93
|
-
if (s !== undefined)
|
|
145
|
+
if (s !== undefined && !dead)
|
|
94
146
|
void s.write(`${line}\n`).catch(() => { });
|
|
95
147
|
else if (!dead)
|
|
96
148
|
queued.push(line);
|
|
97
149
|
},
|
|
98
|
-
close: () => {
|
|
150
|
+
close: () => {
|
|
151
|
+
if (dead)
|
|
152
|
+
return;
|
|
153
|
+
s?.end();
|
|
154
|
+
finish(null);
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
};
|
|
158
|
+
/** `hd.subscribe`: one parsed frame per Herdr event; a host end with words reaches `onError`. */
|
|
159
|
+
const subscription = (opening, on, onError) => {
|
|
160
|
+
const lines = decodeLines();
|
|
161
|
+
let s;
|
|
162
|
+
let stopped = false;
|
|
163
|
+
const deliver = (line) => {
|
|
164
|
+
let frame;
|
|
165
|
+
try {
|
|
166
|
+
frame = JSON.parse(line);
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (!stopped)
|
|
172
|
+
on(frame);
|
|
173
|
+
};
|
|
174
|
+
opening.then((opened) => {
|
|
175
|
+
if (stopped) {
|
|
176
|
+
opened.end();
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
s = opened;
|
|
180
|
+
opened.onData = (chunk) => { for (const line of lines.push(chunk))
|
|
181
|
+
deliver(line); };
|
|
182
|
+
opened.onEnd = (error) => {
|
|
183
|
+
for (const line of lines.flush())
|
|
184
|
+
deliver(line);
|
|
185
|
+
if (!stopped && error !== undefined)
|
|
186
|
+
onError?.(error);
|
|
187
|
+
stopped = true;
|
|
188
|
+
};
|
|
189
|
+
}, (e) => {
|
|
190
|
+
if (!stopped)
|
|
191
|
+
onError?.(e instanceof Error ? e.message : 'failed');
|
|
192
|
+
stopped = true;
|
|
193
|
+
});
|
|
194
|
+
return () => {
|
|
195
|
+
if (stopped)
|
|
196
|
+
return;
|
|
197
|
+
stopped = true;
|
|
198
|
+
s?.end();
|
|
99
199
|
};
|
|
100
200
|
};
|
|
101
201
|
export function herdrDevice(link) {
|
|
@@ -104,11 +204,13 @@ export function herdrDevice(link) {
|
|
|
104
204
|
return {
|
|
105
205
|
state: () => link.request('hd.state', {}),
|
|
106
206
|
tree: () => link.request('hd.tree', {}),
|
|
207
|
+
agentKinds: () => link.request('hd.kinds', {}),
|
|
107
208
|
startAgent: (o) => link.request('hd.agent.start', o),
|
|
108
209
|
prompt: (paneId, text) => link.request('hd.prompt', { paneId, text }),
|
|
109
210
|
keys: async (paneId, keys) => {
|
|
110
211
|
await link.request('hd.keys', { paneId, keys });
|
|
111
212
|
},
|
|
213
|
+
wait: (paneId, o) => link.request('hd.wait', { paneId, ...o }),
|
|
112
214
|
read: (paneId, o) => link.request('hd.read', { paneId, ...o }),
|
|
113
215
|
blocked: () => link.request('hd.blocked', {}),
|
|
114
216
|
answer: async (paneId, keys, revision) => {
|
|
@@ -118,6 +220,7 @@ export function herdrDevice(link) {
|
|
|
118
220
|
await link.request('hd.close', o);
|
|
119
221
|
},
|
|
120
222
|
events: () => streamFrames(link, 'hd.events', {}),
|
|
223
|
+
subscribe: (subs, on, onError) => subscription(link.stream('hd.subscribe', { subs }), on, onError),
|
|
121
224
|
registerNotices: async (seed) => {
|
|
122
225
|
await link.request('hd.notices.register', { boxPublicKey: boxPublicKeyB64(seed) });
|
|
123
226
|
},
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { HERDR_VERSION, HERDR_PROTOCOL } from './constants.ts';
|
|
2
2
|
export { HerdrKit } from './kit.ts';
|
|
3
|
-
export
|
|
3
|
+
export { agentWords, stateWords, words, WORDS, type WordKey } from './words.ts';
|
|
4
|
+
export type { AgentRef, AgentStatus, BlockedAgent, HerdrEvent, HerdrEventName, HerdrEventOf, HerdrKitOptions, HerdrMethod, HerdrMethods, HerdrParams, HerdrProtocolRange, HerdrResult, HerdrSnapshot, HerdrState, HerdrSubscription, HerdrTransport, PromptReceipt, StartAgent, TerminalSession, } from './types.ts';
|
package/dist/index.js
CHANGED
|
@@ -2,3 +2,4 @@
|
|
|
2
2
|
// host-side link adapter in `./link`; the fake Herdr and contract suite arrive with H6 in `./testing`.
|
|
3
3
|
export { HERDR_VERSION, HERDR_PROTOCOL } from "./constants.js";
|
|
4
4
|
export { HerdrKit } from "./kit.js";
|
|
5
|
+
export { agentWords, stateWords, words, WORDS } from "./words.js";
|
package/dist/kit.d.ts
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
import type { AgentRef, AgentStatus, BlockedAgent, HerdrEventName, HerdrEventOf, HerdrKitOptions, HerdrMethod, HerdrParams, HerdrResult, HerdrSnapshot, HerdrState, HerdrSubscription, PromptReceipt, StartAgent, TerminalSession } from './types.ts';
|
|
1
|
+
import type { AgentRef, AgentStatus, BlockedAgent, HerdrEvent, HerdrEventName, HerdrEventOf, HerdrKitOptions, HerdrMethod, HerdrParams, HerdrResult, HerdrSnapshot, HerdrState, HerdrSubscription, PromptReceipt, StartAgent, TerminalSession } from './types.ts';
|
|
2
2
|
export declare class HerdrKit {
|
|
3
3
|
private readonly supervisor;
|
|
4
4
|
private transport?;
|
|
5
5
|
private current;
|
|
6
6
|
private tree;
|
|
7
7
|
private listeners;
|
|
8
|
+
private rawListeners;
|
|
9
|
+
private statusReadyPromise;
|
|
10
|
+
private statusReadyResolve?;
|
|
8
11
|
private stops;
|
|
9
12
|
private statusStops;
|
|
10
13
|
private generation;
|
|
14
|
+
private statusEpoch;
|
|
11
15
|
private readonly o;
|
|
12
16
|
private readonly callAny;
|
|
13
17
|
private readonly agents;
|
|
@@ -15,14 +19,20 @@ export declare class HerdrKit {
|
|
|
15
19
|
constructor(o: HerdrKitOptions);
|
|
16
20
|
get state(): HerdrState;
|
|
17
21
|
private publish;
|
|
22
|
+
private emitRaw;
|
|
18
23
|
private update;
|
|
19
24
|
private bootstrap;
|
|
25
|
+
/**
|
|
26
|
+
* Connect and bootstrap. Non-fatal: a rejected `start()` (e.g. `failed/socket` while Herdr is
|
|
27
|
+
* down) leaves the kit stopped, and `start()` may be called again afterwards — the host comes up
|
|
28
|
+
* while Herdr is down by retrying `start()` in a backoff loop until it reaches `ready`.
|
|
29
|
+
*/
|
|
20
30
|
start(): Promise<void>;
|
|
21
31
|
stop(): Promise<void>;
|
|
22
32
|
call<M extends HerdrMethod>(method: M, params: HerdrParams<M>, o?: {
|
|
23
33
|
timeoutMs?: number;
|
|
24
34
|
}): Promise<HerdrResult<M>>;
|
|
25
|
-
subscribe<E extends HerdrEventName>(subs: HerdrSubscription<E>[], on: (e: HerdrEventOf<E>) => void): () => void;
|
|
35
|
+
subscribe<E extends HerdrEventName>(subs: HerdrSubscription<E>[], on: (e: HerdrEventOf<E>) => void, onError?: (code: string, message: string) => void): () => void;
|
|
26
36
|
cli(args: string[], o?: {
|
|
27
37
|
timeoutMs?: number;
|
|
28
38
|
}): Promise<{
|
|
@@ -38,6 +48,21 @@ export declare class HerdrKit {
|
|
|
38
48
|
}): TerminalSession;
|
|
39
49
|
snapshot(): HerdrSnapshot;
|
|
40
50
|
onChange(fn: (s: HerdrSnapshot) => void): () => void;
|
|
51
|
+
/**
|
|
52
|
+
* Raw event tap (K3): delivers every event arriving on the kit's own batch and per-pane
|
|
53
|
+
* status sockets, with wire payloads intact (`pane.moved.previous_pane_id`, `workspace.*`).
|
|
54
|
+
* Fires on arrival — before the snapshot update/refresh for that event — so a listener sees
|
|
55
|
+
* the frame even when it triggers a re-bootstrap. Buffered replays never re-fire. Sockets
|
|
56
|
+
* opened via `subscribe()` are NOT tapped; `onEvent` itself opens no socket.
|
|
57
|
+
*/
|
|
58
|
+
onEvent(fn: (e: HerdrEvent) => void): () => void;
|
|
59
|
+
/**
|
|
60
|
+
* Ready signal for the status-watch set (K3): resolves when the latest bootstrap's per-pane
|
|
61
|
+
* `pane.agent_status_changed` subscriptions have acked (immediately when no pane needs one).
|
|
62
|
+
* Await after `start()` — and after any reconnect-driven re-bootstrap — before relying on
|
|
63
|
+
* status events instead of opening a second watch.
|
|
64
|
+
*/
|
|
65
|
+
statusWatchReady(): Promise<void>;
|
|
41
66
|
startAgent(o: StartAgent): Promise<AgentRef>;
|
|
42
67
|
prompt(target: AgentRef, text: string, o?: {
|
|
43
68
|
wait?: {
|
|
@@ -69,5 +94,6 @@ export declare class HerdrKit {
|
|
|
69
94
|
agentKinds(): Promise<string[]>;
|
|
70
95
|
installedAgentKinds(kinds: readonly string[], o: {
|
|
71
96
|
path: string[];
|
|
97
|
+
aliases?: Record<string, string[]>;
|
|
72
98
|
}): string[];
|
|
73
99
|
}
|