@theaileverage/marionette 0.2.1 → 0.3.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 +16 -0
- package/CONTRIBUTING.md +28 -2
- package/DESIGN.md +3 -3
- package/ORCHESTRATION.md +2 -2
- package/README.md +39 -235
- package/RELEASING.md +13 -11
- package/THIRD_PARTY_NOTICES.md +236 -0
- package/VERIFICATION.md +22 -0
- package/dist/cli.js +37558 -10372
- package/dist/herdr-protocol.d.ts +1916 -0
- package/dist/herdr-protocol.js +108 -0
- package/dist/herdr-sdk.d.ts +106 -0
- package/dist/herdr-sdk.js +111 -0
- package/dist/herdr-streams.d.ts +49 -0
- package/dist/herdr-streams.js +160 -0
- package/dist/herdr-transport.d.ts +54 -0
- package/dist/herdr-transport.js +232 -0
- package/dist/mcp.js +24292 -2694
- package/documentation/effect-compatibility.md +56 -0
- package/documentation/effect-runtime.md +73 -0
- package/package.json +52 -22
- package/public/assets/{index-BCi4LMak.js → index-DhL3znDT.js} +12 -12
- package/public/index.html +1 -1
- package/skills/marionette/SKILL.md +70 -0
- package/skills/marionette/references/coordination.md +63 -0
- package/skills/marionette/references/herdr-sdk.md +147 -0
- package/vendor/herdr-0.9.0/LICENSE +201 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import net from 'node:net';
|
|
3
|
+
import { isAbsolute } from 'node:path';
|
|
4
|
+
export class HerdrError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
constructor(code, message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.name = 'HerdrError';
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function validateSocketPath(path) {
|
|
13
|
+
if (!isAbsolute(path) && !/^\\\\[.?]\\pipe\\[^\\]/.test(path))
|
|
14
|
+
throw new TypeError('An absolute Herdr socket path or Windows named pipe is required');
|
|
15
|
+
}
|
|
16
|
+
export function validateTimeout(value) {
|
|
17
|
+
if (value !== null && (!Number.isFinite(value) || value <= 0 || value > 2147483647))
|
|
18
|
+
throw new TypeError('timeoutMs must be a positive bounded duration or null');
|
|
19
|
+
}
|
|
20
|
+
function limit(value, name) {
|
|
21
|
+
if (!Number.isSafeInteger(value) || value <= 0)
|
|
22
|
+
throw new TypeError(`${name} must be a positive integer`);
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
/** Internal, single-reader NDJSON connection. JSON reads and binary writes stay separate. */
|
|
26
|
+
export class JsonConnection {
|
|
27
|
+
options;
|
|
28
|
+
id = randomUUID();
|
|
29
|
+
closed;
|
|
30
|
+
resolveClosed;
|
|
31
|
+
rejectClosed;
|
|
32
|
+
socket;
|
|
33
|
+
buffer = '';
|
|
34
|
+
queue = [];
|
|
35
|
+
queuedBytes = 0;
|
|
36
|
+
reader;
|
|
37
|
+
writes = new Set();
|
|
38
|
+
ended = false;
|
|
39
|
+
failure;
|
|
40
|
+
explicitClose = false;
|
|
41
|
+
maxMessageBytes;
|
|
42
|
+
maxQueuedEvents;
|
|
43
|
+
maxQueuedBytes;
|
|
44
|
+
abort;
|
|
45
|
+
constructor(socketPath, options = {}) {
|
|
46
|
+
this.options = options;
|
|
47
|
+
validateSocketPath(socketPath);
|
|
48
|
+
this.maxMessageBytes = limit(options.maxResponseBytes ?? 32 * 1024 * 1024, 'maxResponseBytes');
|
|
49
|
+
this.maxQueuedEvents = limit(options.maxQueuedEvents ?? 1024, 'maxQueuedEvents');
|
|
50
|
+
this.maxQueuedBytes = limit(options.maxQueuedBytes ?? this.maxMessageBytes, 'maxQueuedBytes');
|
|
51
|
+
if (options.signal?.aborted)
|
|
52
|
+
throw new HerdrError('herdr_aborted', 'Herdr operation was aborted');
|
|
53
|
+
this.closed = new Promise((resolve, reject) => {
|
|
54
|
+
this.resolveClosed = resolve;
|
|
55
|
+
this.rejectClosed = reject;
|
|
56
|
+
});
|
|
57
|
+
// Consumers may observe this promise; an unobserved stream error must not crash Node.
|
|
58
|
+
void this.closed.catch(() => { });
|
|
59
|
+
this.socket = net.createConnection(socketPath);
|
|
60
|
+
this.socket.setEncoding('utf8');
|
|
61
|
+
this.abort = () => this.fail(new HerdrError('herdr_aborted', 'Herdr operation was aborted'), true);
|
|
62
|
+
options.signal?.addEventListener('abort', this.abort, { once: true });
|
|
63
|
+
this.socket.on('error', (error) => this.fail(new HerdrError('herdr_unavailable', error.message)));
|
|
64
|
+
this.socket.on('close', () => {
|
|
65
|
+
this.ended = true;
|
|
66
|
+
this.detachAbort();
|
|
67
|
+
this.rejectWrites(this.error());
|
|
68
|
+
if (this.failure || !this.explicitClose)
|
|
69
|
+
this.rejectClosed(this.error());
|
|
70
|
+
else
|
|
71
|
+
this.resolveClosed();
|
|
72
|
+
this.deliver();
|
|
73
|
+
});
|
|
74
|
+
this.socket.on('data', (data) => this.receive(data));
|
|
75
|
+
}
|
|
76
|
+
error() {
|
|
77
|
+
return (this.failure ??
|
|
78
|
+
new HerdrError('herdr_disconnected', 'Herdr disconnected before acknowledgement or stream completion'));
|
|
79
|
+
}
|
|
80
|
+
detachAbort() {
|
|
81
|
+
this.options.signal?.removeEventListener('abort', this.abort);
|
|
82
|
+
}
|
|
83
|
+
rejectWrites(error) {
|
|
84
|
+
for (const reject of this.writes)
|
|
85
|
+
reject(error);
|
|
86
|
+
this.writes.clear();
|
|
87
|
+
}
|
|
88
|
+
fail(error, discard = false) {
|
|
89
|
+
if (this.explicitClose)
|
|
90
|
+
return;
|
|
91
|
+
this.failure ??= error;
|
|
92
|
+
this.ended = true;
|
|
93
|
+
this.buffer = '';
|
|
94
|
+
if (discard) {
|
|
95
|
+
this.queue = [];
|
|
96
|
+
this.queuedBytes = 0;
|
|
97
|
+
}
|
|
98
|
+
this.detachAbort();
|
|
99
|
+
this.socket.destroy();
|
|
100
|
+
this.rejectWrites(this.failure);
|
|
101
|
+
this.deliver();
|
|
102
|
+
}
|
|
103
|
+
close() {
|
|
104
|
+
this.explicitClose = true;
|
|
105
|
+
this.ended = true;
|
|
106
|
+
this.buffer = '';
|
|
107
|
+
this.queue = [];
|
|
108
|
+
this.queuedBytes = 0;
|
|
109
|
+
this.detachAbort();
|
|
110
|
+
this.socket.destroy();
|
|
111
|
+
this.rejectWrites(this.error());
|
|
112
|
+
this.deliver();
|
|
113
|
+
}
|
|
114
|
+
deliver() {
|
|
115
|
+
if (!this.reader)
|
|
116
|
+
return;
|
|
117
|
+
const reader = this.reader;
|
|
118
|
+
if (this.failure) {
|
|
119
|
+
this.reader = undefined;
|
|
120
|
+
reader.reject(this.failure);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const next = this.queue.shift();
|
|
124
|
+
if (next) {
|
|
125
|
+
this.reader = undefined;
|
|
126
|
+
this.queuedBytes -= next.bytes;
|
|
127
|
+
reader.resolve(next.value);
|
|
128
|
+
}
|
|
129
|
+
else if (this.ended) {
|
|
130
|
+
this.reader = undefined;
|
|
131
|
+
if (this.explicitClose)
|
|
132
|
+
reader.resolve(undefined);
|
|
133
|
+
else
|
|
134
|
+
reader.reject(this.error());
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
receive(data) {
|
|
138
|
+
if (this.ended)
|
|
139
|
+
return;
|
|
140
|
+
this.buffer += data;
|
|
141
|
+
let index;
|
|
142
|
+
while ((index = this.buffer.indexOf('\n')) >= 0) {
|
|
143
|
+
const line = this.buffer.slice(0, index);
|
|
144
|
+
this.buffer = this.buffer.slice(index + 1);
|
|
145
|
+
const bytes = Buffer.byteLength(line);
|
|
146
|
+
if (bytes > this.maxMessageBytes)
|
|
147
|
+
return this.fail(new HerdrError('herdr_response_too_large', 'Herdr message exceeded maxResponseBytes'), true);
|
|
148
|
+
let value;
|
|
149
|
+
try {
|
|
150
|
+
value = JSON.parse(line);
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
return this.fail(new HerdrError('herdr_invalid_response', String(error)), true);
|
|
154
|
+
}
|
|
155
|
+
// Each connection owns one request; graphics replies append a frame identifier.
|
|
156
|
+
if (value?.id !== undefined &&
|
|
157
|
+
value.id !== this.id &&
|
|
158
|
+
!String(value.id).startsWith(this.id + ':'))
|
|
159
|
+
continue;
|
|
160
|
+
if (value?.error)
|
|
161
|
+
return this.fail(new HerdrError(value.error.code, value.error.message), true);
|
|
162
|
+
if (this.queue.length >= this.maxQueuedEvents ||
|
|
163
|
+
this.queuedBytes + bytes > this.maxQueuedBytes)
|
|
164
|
+
return this.fail(new HerdrError('herdr_stream_overflow', 'Herdr consumer fell behind; stream closed without silently dropping events'), true);
|
|
165
|
+
this.queue.push({ value, bytes });
|
|
166
|
+
this.queuedBytes += bytes;
|
|
167
|
+
this.deliver();
|
|
168
|
+
}
|
|
169
|
+
if (Buffer.byteLength(this.buffer) > this.maxMessageBytes)
|
|
170
|
+
this.fail(new HerdrError('herdr_response_too_large', 'Herdr message exceeded maxResponseBytes'), true);
|
|
171
|
+
}
|
|
172
|
+
read() {
|
|
173
|
+
if (this.reader)
|
|
174
|
+
return Promise.reject(new Error('Only one reader may consume a Herdr connection'));
|
|
175
|
+
return new Promise((resolve, reject) => {
|
|
176
|
+
this.reader = { resolve, reject };
|
|
177
|
+
this.deliver();
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
write(data) {
|
|
181
|
+
if (this.ended)
|
|
182
|
+
return Promise.reject(this.error());
|
|
183
|
+
return new Promise((resolve, reject) => {
|
|
184
|
+
this.writes.add(reject);
|
|
185
|
+
this.socket.write(data, (error) => {
|
|
186
|
+
this.writes.delete(reject);
|
|
187
|
+
if (error) {
|
|
188
|
+
this.fail(new HerdrError('herdr_unavailable', error.message));
|
|
189
|
+
reject(this.error());
|
|
190
|
+
}
|
|
191
|
+
else if (this.failure)
|
|
192
|
+
reject(this.failure);
|
|
193
|
+
else
|
|
194
|
+
resolve();
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
async within(timeoutMs, action) {
|
|
199
|
+
validateTimeout(timeoutMs);
|
|
200
|
+
const timer = timeoutMs === null
|
|
201
|
+
? undefined
|
|
202
|
+
: setTimeout(() => this.fail(new HerdrError('herdr_timeout', 'Herdr response timed out; delivery may be ambiguous'), true), timeoutMs);
|
|
203
|
+
try {
|
|
204
|
+
return await action();
|
|
205
|
+
}
|
|
206
|
+
finally {
|
|
207
|
+
clearTimeout(timer);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async start(method, params, timeoutMs) {
|
|
211
|
+
return this.within(timeoutMs, async () => {
|
|
212
|
+
await this.write(JSON.stringify({ id: this.id, method, params }) + '\n');
|
|
213
|
+
const message = await this.read();
|
|
214
|
+
if (!message || message.id !== this.id || !Object.hasOwn(message, 'result'))
|
|
215
|
+
throw new HerdrError('herdr_invalid_response', `${method}: missing correlated result`);
|
|
216
|
+
return message.result;
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
export async function socketRequest(socketPath, method, params, options = {}) {
|
|
221
|
+
const timeoutMs = options.timeoutMs === undefined ? 10000 : options.timeoutMs;
|
|
222
|
+
validateTimeout(timeoutMs);
|
|
223
|
+
// Validate serialization before opening a connection.
|
|
224
|
+
JSON.stringify(params);
|
|
225
|
+
const connection = new JsonConnection(socketPath, options);
|
|
226
|
+
try {
|
|
227
|
+
return await connection.start(method, params, timeoutMs);
|
|
228
|
+
}
|
|
229
|
+
finally {
|
|
230
|
+
connection.close();
|
|
231
|
+
}
|
|
232
|
+
}
|