@theaileverage/marionette 0.2.0 → 0.2.2
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 +15 -0
- package/CONTRIBUTING.md +7 -0
- package/DESIGN.md +10 -2
- package/LICENSE +21 -0
- package/ORCHESTRATION.md +33 -0
- package/README.md +34 -4
- package/RELEASING.md +64 -0
- package/THIRD_PARTY_NOTICES.md +208 -0
- package/VERIFICATION.md +20 -0
- package/dist/cli.js +1791 -400
- package/dist/herdr-protocol.d.ts +1916 -0
- package/dist/herdr-protocol.js +108 -0
- package/dist/herdr-sdk.d.ts +105 -0
- package/dist/herdr-sdk.js +105 -0
- package/dist/herdr-streams.d.ts +49 -0
- package/dist/herdr-streams.js +156 -0
- package/dist/herdr-transport.d.ts +50 -0
- package/dist/herdr-transport.js +232 -0
- package/dist/mcp.js +82 -2
- package/package.json +36 -5
- package/public/assets/index-CCfdv-uF.js +167 -0
- 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
- package/vendor/herdr-0.9.0/README.md +5 -0
- package/public/assets/index-DqehtCs9.js +0 -167
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import net from 'node:net';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
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
|
+
}
|
package/dist/mcp.js
CHANGED
|
@@ -21585,8 +21585,20 @@ var usageFields = external_exports.object({
|
|
|
21585
21585
|
costUsd: external_exports.number().min(0).nullable().default(null)
|
|
21586
21586
|
});
|
|
21587
21587
|
|
|
21588
|
+
// src/worktrees.ts
|
|
21589
|
+
import { execFile } from "node:child_process";
|
|
21590
|
+
import { promisify } from "node:util";
|
|
21591
|
+
var exec = promisify(execFile);
|
|
21592
|
+
|
|
21593
|
+
// src/cleanup.ts
|
|
21594
|
+
var cleanupPolicySchema = external_exports.object({
|
|
21595
|
+
autoRelease: external_exports.boolean().default(true),
|
|
21596
|
+
collectAfterHours: external_exports.number().min(0).max(87600).nullable().default(null),
|
|
21597
|
+
deleteMergedBranches: external_exports.boolean().default(false)
|
|
21598
|
+
}).strict();
|
|
21599
|
+
|
|
21588
21600
|
// src/version.ts
|
|
21589
|
-
var VERSION = "0.2.
|
|
21601
|
+
var VERSION = "0.2.2";
|
|
21590
21602
|
|
|
21591
21603
|
// src/config.ts
|
|
21592
21604
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
@@ -21621,7 +21633,9 @@ async function call(home2, action, input = {}) {
|
|
|
21621
21633
|
method: "POST",
|
|
21622
21634
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${c.token}` },
|
|
21623
21635
|
body: JSON.stringify({ action, input }),
|
|
21624
|
-
signal: AbortSignal.timeout(
|
|
21636
|
+
signal: AbortSignal.timeout(
|
|
21637
|
+
action.startsWith("cleanup.") ? 3e5 : action === "profile.validate" ? 135e3 : 45e3
|
|
21638
|
+
)
|
|
21625
21639
|
});
|
|
21626
21640
|
const body = await response.json();
|
|
21627
21641
|
if (!response.ok)
|
|
@@ -22021,4 +22035,70 @@ tool(
|
|
|
22021
22035
|
disagreements: external_exports.array(external_exports.string())
|
|
22022
22036
|
}
|
|
22023
22037
|
);
|
|
22038
|
+
tool(
|
|
22039
|
+
"cleanup_preview",
|
|
22040
|
+
"cleanup.preview",
|
|
22041
|
+
"Inspect release and collection eligibility, retained runs, delivery, archive and policy. Does not delete anything.",
|
|
22042
|
+
{ taskId: external_exports.string() },
|
|
22043
|
+
true
|
|
22044
|
+
);
|
|
22045
|
+
tool(
|
|
22046
|
+
"cleanup_configure",
|
|
22047
|
+
"cleanup.configure",
|
|
22048
|
+
"Authorize project cleanup policy. Automatic terminal release defaults on after integrated outcome completion; automatic worktree collection defaults off. Collection requires recorded delivery, sealed evidence and all consumers to finish.",
|
|
22049
|
+
{ lease: credentialsSchema, reason: external_exports.string(), policy: cleanupPolicySchema }
|
|
22050
|
+
);
|
|
22051
|
+
tool(
|
|
22052
|
+
"cleanup_release",
|
|
22053
|
+
"cleanup.release",
|
|
22054
|
+
"After inspecting the result and confirming no further native continuation is needed, save diagnostics and close only the settled worker tab. Failed/cancelled files remain intact. No session or workspace closure.",
|
|
22055
|
+
{
|
|
22056
|
+
lease: credentialsSchema,
|
|
22057
|
+
taskId: external_exports.string(),
|
|
22058
|
+
runId: external_exports.string().optional(),
|
|
22059
|
+
reason: external_exports.string()
|
|
22060
|
+
}
|
|
22061
|
+
);
|
|
22062
|
+
tool(
|
|
22063
|
+
"cleanup_reconcile",
|
|
22064
|
+
"cleanup.reconcile",
|
|
22065
|
+
"Reconcile uncertain tab closure using actual inspection. Closed requires the original tab to be absent; not-closed requires the original settled identity. Never replays closure.",
|
|
22066
|
+
{
|
|
22067
|
+
lease: credentialsSchema,
|
|
22068
|
+
taskId: external_exports.string(),
|
|
22069
|
+
runId: external_exports.string().optional(),
|
|
22070
|
+
reason: external_exports.string(),
|
|
22071
|
+
resolution: external_exports.enum(["closed", "not-closed"])
|
|
22072
|
+
}
|
|
22073
|
+
);
|
|
22074
|
+
tool(
|
|
22075
|
+
"cleanup_deliver",
|
|
22076
|
+
"cleanup.deliver",
|
|
22077
|
+
"Record merged, published, or explicitly abandoned work. This performs no commit, push, or merge. The checkout must be clean including ignored files. Merged/published needs an exact target ref containing its HEAD; published uses refreshed refs/remotes/... metadata.",
|
|
22078
|
+
{
|
|
22079
|
+
lease: credentialsSchema,
|
|
22080
|
+
taskId: external_exports.string(),
|
|
22081
|
+
reason: external_exports.string(),
|
|
22082
|
+
disposition: external_exports.enum(["merged", "published", "abandoned"]),
|
|
22083
|
+
targetRef: external_exports.string().optional()
|
|
22084
|
+
}
|
|
22085
|
+
);
|
|
22086
|
+
tool(
|
|
22087
|
+
"cleanup_archive",
|
|
22088
|
+
"cleanup.archive",
|
|
22089
|
+
"Seal all finished tasks sharing the managed checkout and preserve evidence, diagnostics and committed history. Release their terminals first. Sealed tasks cannot resume; use a new assignment for additional work.",
|
|
22090
|
+
{ lease: credentialsSchema, taskId: external_exports.string(), reason: external_exports.string() }
|
|
22091
|
+
);
|
|
22092
|
+
tool(
|
|
22093
|
+
"cleanup_collect",
|
|
22094
|
+
"cleanup.collect",
|
|
22095
|
+
"Collect the exact archived worktree after rechecking consumers, delivery and integrity. Supply the preview archiveId. Optional branch deletion compares and deletes the exact archived tip after rechecking merged ancestry, or after explicit abandonment. No forced worktree removal, sessions, or workspace removal.",
|
|
22096
|
+
{
|
|
22097
|
+
lease: credentialsSchema,
|
|
22098
|
+
taskId: external_exports.string(),
|
|
22099
|
+
reason: external_exports.string(),
|
|
22100
|
+
archiveId: external_exports.string(),
|
|
22101
|
+
deleteBranch: external_exports.boolean().optional()
|
|
22102
|
+
}
|
|
22103
|
+
);
|
|
22024
22104
|
await server.connect(new StdioServerTransport());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theaileverage/marionette",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=22.13"
|
|
@@ -13,9 +13,12 @@
|
|
|
13
13
|
"test": "node --import tsx --test tests/*.test.ts",
|
|
14
14
|
"cli": "node --import tsx src/cli.ts",
|
|
15
15
|
"mcp": "node --import tsx src/mcp.ts",
|
|
16
|
-
"format": "prettier --write src web tests scripts *.json *.ts *.md",
|
|
17
|
-
"format:check": "prettier --check src web tests scripts *.json *.ts *.md",
|
|
18
|
-
"prepack": "npm run check && npm test && npm run build"
|
|
16
|
+
"format": "prettier --write src web skills .github tests scripts *.json *.ts *.md",
|
|
17
|
+
"format:check": "prettier --check src web skills .github tests scripts *.json *.ts *.md",
|
|
18
|
+
"prepack": "npm run check && npm test && npm run build",
|
|
19
|
+
"release:prepare": "node scripts/release.mjs prepare",
|
|
20
|
+
"release:check": "node scripts/release.mjs check",
|
|
21
|
+
"sdk:generate": "node scripts/generate-herdr-sdk.mjs"
|
|
19
22
|
},
|
|
20
23
|
"devDependencies": {
|
|
21
24
|
"@types/express": "^5.0.0",
|
|
@@ -47,9 +50,37 @@
|
|
|
47
50
|
"ORCHESTRATION.md",
|
|
48
51
|
"CHANGELOG.md",
|
|
49
52
|
"DESIGN.md",
|
|
50
|
-
"VERIFICATION.md"
|
|
53
|
+
"VERIFICATION.md",
|
|
54
|
+
"LICENSE",
|
|
55
|
+
"CONTRIBUTING.md",
|
|
56
|
+
"RELEASING.md",
|
|
57
|
+
"dist/herdr-sdk.js",
|
|
58
|
+
"dist/herdr-sdk.d.ts",
|
|
59
|
+
"skills/",
|
|
60
|
+
"dist/herdr-protocol.js",
|
|
61
|
+
"dist/herdr-protocol.d.ts",
|
|
62
|
+
"dist/herdr-streams.js",
|
|
63
|
+
"dist/herdr-streams.d.ts",
|
|
64
|
+
"dist/herdr-transport.js",
|
|
65
|
+
"dist/herdr-transport.d.ts",
|
|
66
|
+
"vendor/herdr-0.9.0/LICENSE"
|
|
51
67
|
],
|
|
52
68
|
"publishConfig": {
|
|
53
69
|
"access": "public"
|
|
70
|
+
},
|
|
71
|
+
"license": "MIT",
|
|
72
|
+
"repository": {
|
|
73
|
+
"type": "git",
|
|
74
|
+
"url": "git+https://github.com/theaileverage/marionette.git"
|
|
75
|
+
},
|
|
76
|
+
"homepage": "https://github.com/theaileverage/marionette#readme",
|
|
77
|
+
"bugs": {
|
|
78
|
+
"url": "https://github.com/theaileverage/marionette/issues"
|
|
79
|
+
},
|
|
80
|
+
"exports": {
|
|
81
|
+
"./herdr-sdk": {
|
|
82
|
+
"types": "./dist/herdr-sdk.d.ts",
|
|
83
|
+
"import": "./dist/herdr-sdk.js"
|
|
84
|
+
}
|
|
54
85
|
}
|
|
55
86
|
}
|