@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
package/dist/cli.js
CHANGED
|
@@ -886,15 +886,15 @@ var require_depd = __commonJS({
|
|
|
886
886
|
return relative5(basePath, callSite[0]) + ":" + callSite[1] + ":" + callSite[2];
|
|
887
887
|
}
|
|
888
888
|
function getStack() {
|
|
889
|
-
var
|
|
889
|
+
var limit2 = Error.stackTraceLimit;
|
|
890
890
|
var obj = {};
|
|
891
891
|
var prep = Error.prepareStackTrace;
|
|
892
892
|
Error.prepareStackTrace = prepareObjectStackTrace;
|
|
893
|
-
Error.stackTraceLimit = Math.max(10,
|
|
893
|
+
Error.stackTraceLimit = Math.max(10, limit2);
|
|
894
894
|
Error.captureStackTrace(obj);
|
|
895
895
|
var stack = obj.stack.slice(1);
|
|
896
896
|
Error.prepareStackTrace = prep;
|
|
897
|
-
Error.stackTraceLimit =
|
|
897
|
+
Error.stackTraceLimit = limit2;
|
|
898
898
|
return stack;
|
|
899
899
|
}
|
|
900
900
|
function prepareObjectStackTrace(obj, stack) {
|
|
@@ -5341,15 +5341,15 @@ var require_raw_body = __commonJS({
|
|
|
5341
5341
|
throw new TypeError("argument callback is required");
|
|
5342
5342
|
}
|
|
5343
5343
|
var encoding = opts.encoding !== true ? opts.encoding : "utf-8";
|
|
5344
|
-
var
|
|
5344
|
+
var limit2 = bytes.parse(opts.limit);
|
|
5345
5345
|
var length = opts.length != null && !isNaN(opts.length) ? parseInt(opts.length, 10) : null;
|
|
5346
5346
|
if (done) {
|
|
5347
|
-
return readStream(stream, encoding, length,
|
|
5347
|
+
return readStream(stream, encoding, length, limit2, wrap(done));
|
|
5348
5348
|
}
|
|
5349
|
-
return new Promise(function executor(
|
|
5350
|
-
readStream(stream, encoding, length,
|
|
5349
|
+
return new Promise(function executor(resolve13, reject) {
|
|
5350
|
+
readStream(stream, encoding, length, limit2, function onRead(err, buf) {
|
|
5351
5351
|
if (err) return reject(err);
|
|
5352
|
-
|
|
5352
|
+
resolve13(buf);
|
|
5353
5353
|
});
|
|
5354
5354
|
});
|
|
5355
5355
|
}
|
|
@@ -5359,14 +5359,14 @@ var require_raw_body = __commonJS({
|
|
|
5359
5359
|
stream.pause();
|
|
5360
5360
|
}
|
|
5361
5361
|
}
|
|
5362
|
-
function readStream(stream, encoding, length,
|
|
5362
|
+
function readStream(stream, encoding, length, limit2, callback) {
|
|
5363
5363
|
var complete = false;
|
|
5364
5364
|
var sync = true;
|
|
5365
|
-
if (
|
|
5365
|
+
if (limit2 !== null && length !== null && length > limit2) {
|
|
5366
5366
|
return done(createError(413, "request entity too large", {
|
|
5367
5367
|
expected: length,
|
|
5368
5368
|
length,
|
|
5369
|
-
limit,
|
|
5369
|
+
limit: limit2,
|
|
5370
5370
|
type: "entity.too.large"
|
|
5371
5371
|
}));
|
|
5372
5372
|
}
|
|
@@ -5427,9 +5427,9 @@ var require_raw_body = __commonJS({
|
|
|
5427
5427
|
function onData(chunk) {
|
|
5428
5428
|
if (complete) return;
|
|
5429
5429
|
received += chunk.length;
|
|
5430
|
-
if (
|
|
5430
|
+
if (limit2 !== null && received > limit2) {
|
|
5431
5431
|
done(createError(413, "request entity too large", {
|
|
5432
|
-
limit,
|
|
5432
|
+
limit: limit2,
|
|
5433
5433
|
received,
|
|
5434
5434
|
type: "entity.too.large"
|
|
5435
5435
|
}));
|
|
@@ -15608,11 +15608,11 @@ var require_utils = __commonJS({
|
|
|
15608
15608
|
throw new TypeError("defaultType must be provided");
|
|
15609
15609
|
}
|
|
15610
15610
|
const inflate = options?.inflate !== false;
|
|
15611
|
-
const
|
|
15611
|
+
const limit2 = typeof options?.limit === "undefined" || options?.limit === null ? 102400 : bytes.parse(options.limit);
|
|
15612
15612
|
const type = options?.type || defaultType;
|
|
15613
15613
|
const verify = options?.verify || false;
|
|
15614
15614
|
const defaultCharset = options?.defaultCharset || "utf-8";
|
|
15615
|
-
if (
|
|
15615
|
+
if (limit2 === null) {
|
|
15616
15616
|
throw new TypeError(`option limit "${String(options.limit)}" is invalid`);
|
|
15617
15617
|
}
|
|
15618
15618
|
if (verify !== false && typeof verify !== "function") {
|
|
@@ -15621,7 +15621,7 @@ var require_utils = __commonJS({
|
|
|
15621
15621
|
const shouldParse = typeof type !== "function" ? typeChecker(type) : type;
|
|
15622
15622
|
return {
|
|
15623
15623
|
inflate,
|
|
15624
|
-
limit,
|
|
15624
|
+
limit: limit2,
|
|
15625
15625
|
verify,
|
|
15626
15626
|
defaultCharset,
|
|
15627
15627
|
shouldParse
|
|
@@ -17756,7 +17756,7 @@ var require_utils2 = __commonJS({
|
|
|
17756
17756
|
return strWithoutPlus;
|
|
17757
17757
|
}
|
|
17758
17758
|
};
|
|
17759
|
-
var
|
|
17759
|
+
var limit2 = 1024;
|
|
17760
17760
|
var encode = function encode2(str, defaultEncoder, charset, kind, format) {
|
|
17761
17761
|
if (str.length === 0) {
|
|
17762
17762
|
return str;
|
|
@@ -17773,9 +17773,9 @@ var require_utils2 = __commonJS({
|
|
|
17773
17773
|
});
|
|
17774
17774
|
}
|
|
17775
17775
|
var out = "";
|
|
17776
|
-
for (var j = 0; j < string.length; j +=
|
|
17777
|
-
var segment = string.length >=
|
|
17778
|
-
if (j +
|
|
17776
|
+
for (var j = 0; j < string.length; j += limit2) {
|
|
17777
|
+
var segment = string.length >= limit2 ? string.slice(j, j + limit2) : string;
|
|
17778
|
+
if (j + limit2 < string.length) {
|
|
17779
17779
|
var last = segment.charCodeAt(segment.length - 1);
|
|
17780
17780
|
if (last >= 55296 && last <= 56319) {
|
|
17781
17781
|
segment = segment.slice(0, -1);
|
|
@@ -18246,13 +18246,13 @@ var require_parse = __commonJS({
|
|
|
18246
18246
|
var obj = { __proto__: null };
|
|
18247
18247
|
var cleanStr = options.ignoreQueryPrefix ? str.replace(/^\?/, "") : str;
|
|
18248
18248
|
cleanStr = cleanStr.replace(/%5B/gi, "[").replace(/%5D/gi, "]");
|
|
18249
|
-
var
|
|
18249
|
+
var limit2 = options.parameterLimit === Infinity ? void 0 : options.parameterLimit;
|
|
18250
18250
|
var parts = cleanStr.split(
|
|
18251
18251
|
options.delimiter,
|
|
18252
|
-
options.throwOnLimitExceeded && typeof
|
|
18252
|
+
options.throwOnLimitExceeded && typeof limit2 !== "undefined" ? limit2 + 1 : limit2
|
|
18253
18253
|
);
|
|
18254
|
-
if (options.throwOnLimitExceeded && typeof
|
|
18255
|
-
throw new RangeError("Parameter limit exceeded. Only " +
|
|
18254
|
+
if (options.throwOnLimitExceeded && typeof limit2 !== "undefined" && parts.length > limit2) {
|
|
18255
|
+
throw new RangeError("Parameter limit exceeded. Only " + limit2 + " parameter" + (limit2 === 1 ? "" : "s") + " allowed.");
|
|
18256
18256
|
}
|
|
18257
18257
|
var skipIndex = -1;
|
|
18258
18258
|
var i;
|
|
@@ -18600,12 +18600,12 @@ var require_urlencoded = __commonJS({
|
|
|
18600
18600
|
}
|
|
18601
18601
|
};
|
|
18602
18602
|
}
|
|
18603
|
-
function parameterCount(body,
|
|
18603
|
+
function parameterCount(body, limit2) {
|
|
18604
18604
|
let count = 0;
|
|
18605
18605
|
let index = -1;
|
|
18606
18606
|
do {
|
|
18607
18607
|
count++;
|
|
18608
|
-
if (count >
|
|
18608
|
+
if (count > limit2) return void 0;
|
|
18609
18609
|
index = body.indexOf("&", index + 1);
|
|
18610
18610
|
} while (index !== -1);
|
|
18611
18611
|
return count;
|
|
@@ -18931,11 +18931,11 @@ var require_view = __commonJS({
|
|
|
18931
18931
|
var debug = require_src()("express:view");
|
|
18932
18932
|
var path = __require("node:path");
|
|
18933
18933
|
var fs = __require("node:fs");
|
|
18934
|
-
var
|
|
18934
|
+
var dirname11 = path.dirname;
|
|
18935
18935
|
var basename2 = path.basename;
|
|
18936
18936
|
var extname = path.extname;
|
|
18937
18937
|
var join = path.join;
|
|
18938
|
-
var
|
|
18938
|
+
var resolve13 = path.resolve;
|
|
18939
18939
|
module.exports = View;
|
|
18940
18940
|
function View(name, options) {
|
|
18941
18941
|
var opts = options || {};
|
|
@@ -18969,8 +18969,8 @@ var require_view = __commonJS({
|
|
|
18969
18969
|
debug('lookup "%s"', name);
|
|
18970
18970
|
for (var i = 0; i < roots.length && !path2; i++) {
|
|
18971
18971
|
var root = roots[i];
|
|
18972
|
-
var loc =
|
|
18973
|
-
var dir =
|
|
18972
|
+
var loc = resolve13(root, name);
|
|
18973
|
+
var dir = dirname11(loc);
|
|
18974
18974
|
var file = basename2(loc);
|
|
18975
18975
|
path2 = this.resolve(dir, file);
|
|
18976
18976
|
}
|
|
@@ -18994,7 +18994,7 @@ var require_view = __commonJS({
|
|
|
18994
18994
|
});
|
|
18995
18995
|
sync = false;
|
|
18996
18996
|
};
|
|
18997
|
-
View.prototype.resolve = function
|
|
18997
|
+
View.prototype.resolve = function resolve14(dir, file) {
|
|
18998
18998
|
var ext = this.ext;
|
|
18999
18999
|
var path2 = join(dir, file);
|
|
19000
19000
|
var stat = tryStat(path2);
|
|
@@ -21253,7 +21253,7 @@ var require_application = __commonJS({
|
|
|
21253
21253
|
var compileETag = require_utils3().compileETag;
|
|
21254
21254
|
var compileQueryParser = require_utils3().compileQueryParser;
|
|
21255
21255
|
var compileTrust = require_utils3().compileTrust;
|
|
21256
|
-
var
|
|
21256
|
+
var resolve13 = __require("node:path").resolve;
|
|
21257
21257
|
var once = require_once();
|
|
21258
21258
|
var Router = require_router();
|
|
21259
21259
|
var slice = Array.prototype.slice;
|
|
@@ -21307,7 +21307,7 @@ var require_application = __commonJS({
|
|
|
21307
21307
|
this.mountpath = "/";
|
|
21308
21308
|
this.locals.settings = this.settings;
|
|
21309
21309
|
this.set("view", View);
|
|
21310
|
-
this.set("views",
|
|
21310
|
+
this.set("views", resolve13("views"));
|
|
21311
21311
|
this.set("jsonp callback name", "callback");
|
|
21312
21312
|
if (env === "production") {
|
|
21313
21313
|
this.enable("view cache");
|
|
@@ -22887,7 +22887,7 @@ var require_send = __commonJS({
|
|
|
22887
22887
|
var extname = path.extname;
|
|
22888
22888
|
var join = path.join;
|
|
22889
22889
|
var normalize = path.normalize;
|
|
22890
|
-
var
|
|
22890
|
+
var resolve13 = path.resolve;
|
|
22891
22891
|
var sep = path.sep;
|
|
22892
22892
|
var BYTES_RANGE_REGEXP = /^ *bytes=/;
|
|
22893
22893
|
var MAX_MAXAGE = 60 * 60 * 24 * 365 * 1e3;
|
|
@@ -22916,7 +22916,7 @@ var require_send = __commonJS({
|
|
|
22916
22916
|
this._maxage = opts.maxAge || opts.maxage;
|
|
22917
22917
|
this._maxage = typeof this._maxage === "string" ? ms(this._maxage) : Number(this._maxage);
|
|
22918
22918
|
this._maxage = !isNaN(this._maxage) ? Math.min(Math.max(0, this._maxage), MAX_MAXAGE) : 0;
|
|
22919
|
-
this._root = opts.root ?
|
|
22919
|
+
this._root = opts.root ? resolve13(opts.root) : null;
|
|
22920
22920
|
}
|
|
22921
22921
|
util2.inherits(SendStream, Stream);
|
|
22922
22922
|
SendStream.prototype.error = function error(status, err) {
|
|
@@ -23065,7 +23065,7 @@ var require_send = __commonJS({
|
|
|
23065
23065
|
return res;
|
|
23066
23066
|
}
|
|
23067
23067
|
parts = normalize(path2).split(sep);
|
|
23068
|
-
path2 =
|
|
23068
|
+
path2 = resolve13(path2);
|
|
23069
23069
|
}
|
|
23070
23070
|
if (containsDotFile(parts)) {
|
|
23071
23071
|
debug('%s dotfile "%s"', this._dotfiles, path2);
|
|
@@ -23443,7 +23443,7 @@ var require_response = __commonJS({
|
|
|
23443
23443
|
var cookie = require_cookie();
|
|
23444
23444
|
var send = require_send();
|
|
23445
23445
|
var extname = path.extname;
|
|
23446
|
-
var
|
|
23446
|
+
var resolve13 = path.resolve;
|
|
23447
23447
|
var vary = require_vary();
|
|
23448
23448
|
var { Buffer: Buffer2 } = __require("node:buffer");
|
|
23449
23449
|
var res = Object.create(http.ServerResponse.prototype);
|
|
@@ -23649,7 +23649,7 @@ var require_response = __commonJS({
|
|
|
23649
23649
|
}
|
|
23650
23650
|
opts = Object.create(opts);
|
|
23651
23651
|
opts.headers = headers;
|
|
23652
|
-
var fullPath = !opts.root ?
|
|
23652
|
+
var fullPath = !opts.root ? resolve13(path2) : path2;
|
|
23653
23653
|
return this.sendFile(fullPath, opts, done);
|
|
23654
23654
|
};
|
|
23655
23655
|
res.contentType = res.type = function contentType(type) {
|
|
@@ -23898,7 +23898,7 @@ var require_serve_static = __commonJS({
|
|
|
23898
23898
|
var encodeUrl = require_encodeurl();
|
|
23899
23899
|
var escapeHtml = require_escape_html();
|
|
23900
23900
|
var parseUrl = require_parseurl();
|
|
23901
|
-
var
|
|
23901
|
+
var resolve13 = __require("path").resolve;
|
|
23902
23902
|
var send = require_send();
|
|
23903
23903
|
var url = __require("url");
|
|
23904
23904
|
module.exports = serveStatic;
|
|
@@ -23917,7 +23917,7 @@ var require_serve_static = __commonJS({
|
|
|
23917
23917
|
throw new TypeError("option setHeaders must be function");
|
|
23918
23918
|
}
|
|
23919
23919
|
opts.maxage = opts.maxage || opts.maxAge || 0;
|
|
23920
|
-
opts.root =
|
|
23920
|
+
opts.root = resolve13(root);
|
|
23921
23921
|
var onDirectory = redirect ? createRedirectDirectoryListener() : createNotFoundDirectoryListener();
|
|
23922
23922
|
return function serveStatic2(req, res, next) {
|
|
23923
23923
|
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
@@ -24044,8 +24044,8 @@ var require_express2 = __commonJS({
|
|
|
24044
24044
|
});
|
|
24045
24045
|
|
|
24046
24046
|
// src/cli.ts
|
|
24047
|
-
import { readFileSync as
|
|
24048
|
-
import { resolve as
|
|
24047
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync8, openSync as openSync4, closeSync as closeSync4, existsSync as existsSync9 } from "node:fs";
|
|
24048
|
+
import { resolve as resolve12 } from "node:path";
|
|
24049
24049
|
import { spawn as spawn4 } from "node:child_process";
|
|
24050
24050
|
|
|
24051
24051
|
// src/config.ts
|
|
@@ -24365,8 +24365,8 @@ var ZodError = class _ZodError extends Error {
|
|
|
24365
24365
|
let i = 0;
|
|
24366
24366
|
while (i < issue.path.length) {
|
|
24367
24367
|
const el = issue.path[i];
|
|
24368
|
-
const
|
|
24369
|
-
if (!
|
|
24368
|
+
const terminal3 = i === issue.path.length - 1;
|
|
24369
|
+
if (!terminal3) {
|
|
24370
24370
|
curr[el] = curr[el] || { _errors: [] };
|
|
24371
24371
|
} else {
|
|
24372
24372
|
curr[el] = curr[el] || { _errors: [] };
|
|
@@ -28138,7 +28138,9 @@ async function call(home2, action, input = {}) {
|
|
|
28138
28138
|
method: "POST",
|
|
28139
28139
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${c.token}` },
|
|
28140
28140
|
body: JSON.stringify({ action, input }),
|
|
28141
|
-
signal: AbortSignal.timeout(
|
|
28141
|
+
signal: AbortSignal.timeout(
|
|
28142
|
+
action.startsWith("cleanup.") ? 3e5 : action === "profile.validate" ? 135e3 : 45e3
|
|
28143
|
+
)
|
|
28142
28144
|
});
|
|
28143
28145
|
const body = await response.json();
|
|
28144
28146
|
if (!response.ok)
|
|
@@ -28150,9 +28152,9 @@ async function call(home2, action, input = {}) {
|
|
|
28150
28152
|
|
|
28151
28153
|
// src/server.ts
|
|
28152
28154
|
var import_express = __toESM(require_express2(), 1);
|
|
28153
|
-
import { timingSafeEqual, randomUUID as
|
|
28154
|
-
import { existsSync as
|
|
28155
|
-
import { resolve as
|
|
28155
|
+
import { timingSafeEqual, randomUUID as randomUUID9 } from "node:crypto";
|
|
28156
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync5, unlinkSync as unlinkSync2 } from "node:fs";
|
|
28157
|
+
import { resolve as resolve9, dirname as dirname8 } from "node:path";
|
|
28156
28158
|
import { fileURLToPath } from "node:url";
|
|
28157
28159
|
|
|
28158
28160
|
// src/store.ts
|
|
@@ -28276,8 +28278,8 @@ var Store = class {
|
|
|
28276
28278
|
const r = this.db.prepare("INSERT INTO events(project_id,data) VALUES(?,?)").run(projectId, JSON.stringify(event));
|
|
28277
28279
|
return { ...event, id: Number(r.lastInsertRowid) };
|
|
28278
28280
|
}
|
|
28279
|
-
events(projectId, after = 0,
|
|
28280
|
-
return this.db.prepare("SELECT id,data FROM events WHERE project_id=? AND id>? ORDER BY id LIMIT ?").all(projectId, after,
|
|
28281
|
+
events(projectId, after = 0, limit2 = 200) {
|
|
28282
|
+
return this.db.prepare("SELECT id,data FROM events WHERE project_id=? AND id>? ORDER BY id LIMIT ?").all(projectId, after, limit2).map((r) => ({ ...JSON.parse(r.data), id: r.id }));
|
|
28281
28283
|
}
|
|
28282
28284
|
close() {
|
|
28283
28285
|
this.db.close();
|
|
@@ -28285,9 +28287,9 @@ var Store = class {
|
|
|
28285
28287
|
};
|
|
28286
28288
|
|
|
28287
28289
|
// src/service.ts
|
|
28288
|
-
import { randomUUID as
|
|
28289
|
-
import { realpathSync as
|
|
28290
|
-
import { isAbsolute as
|
|
28290
|
+
import { randomUUID as randomUUID6, randomBytes as randomBytes2 } from "node:crypto";
|
|
28291
|
+
import { realpathSync as realpathSync4, statSync as statSync4 } from "node:fs";
|
|
28292
|
+
import { isAbsolute as isAbsolute4 } from "node:path";
|
|
28291
28293
|
|
|
28292
28294
|
// src/continuation.ts
|
|
28293
28295
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
@@ -28341,7 +28343,7 @@ function digest(path) {
|
|
|
28341
28343
|
}
|
|
28342
28344
|
}
|
|
28343
28345
|
function command(command2, args2, cwd, timeoutMs = 3e4) {
|
|
28344
|
-
return new Promise((
|
|
28346
|
+
return new Promise((resolve13, reject) => {
|
|
28345
28347
|
const env = { ...process.env };
|
|
28346
28348
|
for (const k of Object.keys(env))
|
|
28347
28349
|
if (k.startsWith("MARIONETTE_") || k.startsWith("HERDR_")) delete env[k];
|
|
@@ -28372,7 +28374,7 @@ function command(command2, args2, cwd, timeoutMs = 3e4) {
|
|
|
28372
28374
|
});
|
|
28373
28375
|
child.on("close", (code) => {
|
|
28374
28376
|
clearTimeout(timer);
|
|
28375
|
-
|
|
28377
|
+
resolve13({ code, output, timedOut });
|
|
28376
28378
|
});
|
|
28377
28379
|
});
|
|
28378
28380
|
}
|
|
@@ -28590,7 +28592,7 @@ var Continuation = class {
|
|
|
28590
28592
|
}
|
|
28591
28593
|
}
|
|
28592
28594
|
async stop() {
|
|
28593
|
-
while (this.busy.size) await new Promise((
|
|
28595
|
+
while (this.busy.size) await new Promise((resolve13) => setTimeout(resolve13, 25));
|
|
28594
28596
|
}
|
|
28595
28597
|
async process(w) {
|
|
28596
28598
|
if (!["waiting", "ready"].includes(w.state)) {
|
|
@@ -28913,6 +28915,789 @@ ${JSON.stringify(summary)}`;
|
|
|
28913
28915
|
}
|
|
28914
28916
|
};
|
|
28915
28917
|
|
|
28918
|
+
// src/cleanup.ts
|
|
28919
|
+
import { randomUUID as randomUUID3, createHash as createHash2 } from "node:crypto";
|
|
28920
|
+
import {
|
|
28921
|
+
existsSync as existsSync3,
|
|
28922
|
+
mkdirSync as mkdirSync4,
|
|
28923
|
+
readFileSync as readFileSync4,
|
|
28924
|
+
writeFileSync as writeFileSync2,
|
|
28925
|
+
realpathSync as realpathSync3,
|
|
28926
|
+
openSync,
|
|
28927
|
+
fsyncSync,
|
|
28928
|
+
closeSync,
|
|
28929
|
+
createReadStream,
|
|
28930
|
+
statSync as statSync3
|
|
28931
|
+
} from "node:fs";
|
|
28932
|
+
import { dirname as dirname4, resolve as resolve4 } from "node:path";
|
|
28933
|
+
|
|
28934
|
+
// src/worktrees.ts
|
|
28935
|
+
import { execFile } from "node:child_process";
|
|
28936
|
+
import { promisify } from "node:util";
|
|
28937
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync3, realpathSync as realpathSync2, statSync as statSync2 } from "node:fs";
|
|
28938
|
+
import { dirname as dirname3, relative as relative2, resolve as resolve3 } from "node:path";
|
|
28939
|
+
var exec = promisify(execFile);
|
|
28940
|
+
async function git(cwd, args2) {
|
|
28941
|
+
const env = { ...process.env };
|
|
28942
|
+
for (const key of Object.keys(env)) if (key.startsWith("GIT_")) delete env[key];
|
|
28943
|
+
env.GIT_TERMINAL_PROMPT = "0";
|
|
28944
|
+
try {
|
|
28945
|
+
const result = await exec("git", ["-c", "core.hooksPath=/dev/null", "-C", cwd, ...args2], {
|
|
28946
|
+
env,
|
|
28947
|
+
timeout: 12e4,
|
|
28948
|
+
maxBuffer: 4 * 1024 * 1024
|
|
28949
|
+
});
|
|
28950
|
+
return result.stdout.trimEnd();
|
|
28951
|
+
} catch (error) {
|
|
28952
|
+
const e = error;
|
|
28953
|
+
throw new AppError(
|
|
28954
|
+
"worktree_git",
|
|
28955
|
+
`Git worktree operation failed: ${e.stderr?.trim() || e.message}`
|
|
28956
|
+
);
|
|
28957
|
+
}
|
|
28958
|
+
}
|
|
28959
|
+
async function planWorktree(task, stateRoot) {
|
|
28960
|
+
const repositoryRoot = realpathSync2(await git(task.cwd, ["rev-parse", "--show-toplevel"]));
|
|
28961
|
+
const commonDir = realpathSync2(
|
|
28962
|
+
await git(task.cwd, ["rev-parse", "--path-format=absolute", "--git-common-dir"])
|
|
28963
|
+
);
|
|
28964
|
+
const baseRef = task.execution?.mode === "worktree" ? task.execution.baseRef ?? "HEAD" : "HEAD";
|
|
28965
|
+
const baseCommit = await git(task.cwd, [
|
|
28966
|
+
"rev-parse",
|
|
28967
|
+
"--verify",
|
|
28968
|
+
"--end-of-options",
|
|
28969
|
+
`${baseRef}^{commit}`
|
|
28970
|
+
]);
|
|
28971
|
+
if (!inside(repositoryRoot, task.cwd))
|
|
28972
|
+
throw new AppError("worktree_scope", "Task directory is outside its Git repository");
|
|
28973
|
+
const path = safePath(stateRoot, `worktrees/${task.projectId}/${task.id}`);
|
|
28974
|
+
return {
|
|
28975
|
+
state: "planned",
|
|
28976
|
+
repositoryRoot,
|
|
28977
|
+
commonDir,
|
|
28978
|
+
sourceCwd: task.cwd,
|
|
28979
|
+
path,
|
|
28980
|
+
cwd: resolve3(path, relative2(repositoryRoot, task.cwd)),
|
|
28981
|
+
branch: `marionette/${task.id}`,
|
|
28982
|
+
baseCommit
|
|
28983
|
+
};
|
|
28984
|
+
}
|
|
28985
|
+
async function validateWorktree(w, requireClean = false) {
|
|
28986
|
+
const listing = await git(w.repositoryRoot, ["worktree", "list", "--porcelain", "-z"]);
|
|
28987
|
+
const records = listing.split("\0\0").map((record2) => record2.split("\0"));
|
|
28988
|
+
const record = records.find((fields) => fields.includes(`worktree ${w.path}`));
|
|
28989
|
+
if (!record || !record.includes(`branch refs/heads/${w.branch}`) || record.some((field) => /^(locked|prunable)( |$)/.test(field)) || !existsSync2(w.path) || realpathSync2(w.path) !== w.path)
|
|
28990
|
+
throw new AppError(
|
|
28991
|
+
"worktree_identity",
|
|
28992
|
+
"Managed worktree is missing, incomplete, or has changed identity. Inspect it before retrying; existing work is preserved."
|
|
28993
|
+
);
|
|
28994
|
+
const commonDir = realpathSync2(
|
|
28995
|
+
await git(w.path, ["rev-parse", "--path-format=absolute", "--git-common-dir"])
|
|
28996
|
+
);
|
|
28997
|
+
const root = realpathSync2(await git(w.path, ["rev-parse", "--show-toplevel"]));
|
|
28998
|
+
const branch = await git(w.path, ["symbolic-ref", "HEAD"]);
|
|
28999
|
+
if (commonDir !== w.commonDir || root !== w.path || branch !== `refs/heads/${w.branch}`)
|
|
29000
|
+
throw new AppError(
|
|
29001
|
+
"worktree_identity",
|
|
29002
|
+
"Managed worktree repository or branch changed; refusing to reuse it."
|
|
29003
|
+
);
|
|
29004
|
+
if (requireClean) {
|
|
29005
|
+
const head = await git(w.path, ["rev-parse", "HEAD"]);
|
|
29006
|
+
const status = await git(w.path, ["status", "--porcelain", "--untracked-files=all"]);
|
|
29007
|
+
if (head !== w.baseCommit || status)
|
|
29008
|
+
throw new AppError(
|
|
29009
|
+
"worktree_incomplete",
|
|
29010
|
+
"Worktree creation was interrupted and its checkout is not clean at the pinned base. Inspect it before retrying; no files were reset."
|
|
29011
|
+
);
|
|
29012
|
+
}
|
|
29013
|
+
const cwd = safePath(w.path, relative2(w.path, w.cwd), true);
|
|
29014
|
+
if (!statSync2(cwd).isDirectory())
|
|
29015
|
+
throw new AppError(
|
|
29016
|
+
"worktree_cwd",
|
|
29017
|
+
"Task working directory does not exist in the selected Git revision"
|
|
29018
|
+
);
|
|
29019
|
+
return cwd;
|
|
29020
|
+
}
|
|
29021
|
+
async function createWorktree(w) {
|
|
29022
|
+
mkdirSync3(dirname3(w.path), { recursive: true, mode: 448 });
|
|
29023
|
+
await git(w.repositoryRoot, ["worktree", "add", "-b", w.branch, "--", w.path, w.baseCommit]);
|
|
29024
|
+
return validateWorktree(w, true);
|
|
29025
|
+
}
|
|
29026
|
+
|
|
29027
|
+
// src/cleanup.ts
|
|
29028
|
+
var cleanupPolicySchema = external_exports.object({
|
|
29029
|
+
autoRelease: external_exports.boolean().default(true),
|
|
29030
|
+
collectAfterHours: external_exports.number().min(0).max(87600).nullable().default(null),
|
|
29031
|
+
deleteMergedBranches: external_exports.boolean().default(false)
|
|
29032
|
+
}).strict();
|
|
29033
|
+
var terminal2 = (t) => ["completed", "failed", "cancelled"].includes(t.status);
|
|
29034
|
+
function refuse(message) {
|
|
29035
|
+
throw new AppError("cleanup_blocked", message, 409);
|
|
29036
|
+
}
|
|
29037
|
+
function flush(path) {
|
|
29038
|
+
const fd = openSync(path, "r");
|
|
29039
|
+
try {
|
|
29040
|
+
fsyncSync(fd);
|
|
29041
|
+
} finally {
|
|
29042
|
+
closeSync(fd);
|
|
29043
|
+
}
|
|
29044
|
+
}
|
|
29045
|
+
async function fileHash(path) {
|
|
29046
|
+
const value = createHash2("sha256");
|
|
29047
|
+
for await (const chunk of createReadStream(path)) value.update(chunk);
|
|
29048
|
+
return value.digest("hex");
|
|
29049
|
+
}
|
|
29050
|
+
var Cleanup = class {
|
|
29051
|
+
constructor(s) {
|
|
29052
|
+
this.s = s;
|
|
29053
|
+
}
|
|
29054
|
+
s;
|
|
29055
|
+
busy = /* @__PURE__ */ new Set();
|
|
29056
|
+
stopped = false;
|
|
29057
|
+
lastAutomatic = 0;
|
|
29058
|
+
policy(projectId) {
|
|
29059
|
+
return this.s.store.get("cleanup-policy", projectId) ?? cleanupPolicySchema.parse({});
|
|
29060
|
+
}
|
|
29061
|
+
archive(t) {
|
|
29062
|
+
return t.archiveId ? this.s.store.get("archive", t.archiveId) : void 0;
|
|
29063
|
+
}
|
|
29064
|
+
group(t) {
|
|
29065
|
+
return t.worktree ? this.s.store.all("task").filter((x) => x.worktree?.path === t.worktree.path || inside(t.worktree.path, x.cwd)) : [t];
|
|
29066
|
+
}
|
|
29067
|
+
assertMutable(t) {
|
|
29068
|
+
if (t.archiveId || this.busy.has(t.id))
|
|
29069
|
+
refuse(
|
|
29070
|
+
"Task is archived or cleanup is in progress. Create a new assignment for further work."
|
|
29071
|
+
);
|
|
29072
|
+
const affected = /* @__PURE__ */ new Set([t.id]);
|
|
29073
|
+
const tasks = this.s.store.all("task");
|
|
29074
|
+
for (const id of affected) {
|
|
29075
|
+
const parentId = tasks.find((child) => child.id === id)?.parentId;
|
|
29076
|
+
for (const other of tasks)
|
|
29077
|
+
if (other.dependencies.includes(id) || other.id === parentId) affected.add(other.id);
|
|
29078
|
+
}
|
|
29079
|
+
if ([...affected].some((id) => this.busy.has(id)))
|
|
29080
|
+
refuse("Cleanup is inspecting a consumer of this task; retry after it finishes");
|
|
29081
|
+
const r = t.runId ? this.s.store.get("run", t.runId) : void 0;
|
|
29082
|
+
if (r?.cleanup && ["closing", "uncertain"].includes(r.cleanup.state))
|
|
29083
|
+
refuse("Reconcile terminal cleanup before changing this task.");
|
|
29084
|
+
}
|
|
29085
|
+
active(taskId) {
|
|
29086
|
+
return this.busy.has(taskId);
|
|
29087
|
+
}
|
|
29088
|
+
assertOutcomeMutable(outcomeId) {
|
|
29089
|
+
if (this.s.store.all("task").some((t) => t.outcomeId === outcomeId && this.busy.has(t.id)))
|
|
29090
|
+
refuse("Cleanup is inspecting this outcome; retry after it finishes");
|
|
29091
|
+
}
|
|
29092
|
+
consumers(tasks) {
|
|
29093
|
+
const ids = new Set(tasks.map((t) => t.id));
|
|
29094
|
+
const roots = tasks.map((t) => t.worktree?.path ?? t.cwd);
|
|
29095
|
+
const reasons = [];
|
|
29096
|
+
for (const t of this.s.store.all("task")) {
|
|
29097
|
+
if (ids.has(t.id)) continue;
|
|
29098
|
+
if (!terminal2(t) && (ids.has(t.parentId ?? "") || t.dependencies.some((id) => ids.has(id)) || (t.waitForChildren ?? []).some((id) => ids.has(id))))
|
|
29099
|
+
reasons.push(`Task ${t.id} still consumes these results`);
|
|
29100
|
+
}
|
|
29101
|
+
for (const p of this.s.store.all("project")) {
|
|
29102
|
+
if (roots.some((root) => tasks.some((t) => t.worktree?.path === root) && inside(root, p.root)))
|
|
29103
|
+
reasons.push(`Registered project ${p.id} uses this checkout`);
|
|
29104
|
+
}
|
|
29105
|
+
return reasons;
|
|
29106
|
+
}
|
|
29107
|
+
runReasons(t, r, automatic) {
|
|
29108
|
+
const reasons = [];
|
|
29109
|
+
if (!terminal2(t) || r.phase !== "stopped")
|
|
29110
|
+
reasons.push("Task and native run must be finished and settled");
|
|
29111
|
+
if (t.resumePending) reasons.push("A parent continuation is pending");
|
|
29112
|
+
if (this.s.store.all("operation").some((o) => o.taskId === t.id && !["done", "failed"].includes(o.phase)))
|
|
29113
|
+
reasons.push("A task control is unresolved");
|
|
29114
|
+
if (this.s.store.all("lead-wait").some(
|
|
29115
|
+
(w) => w.adapter.type === "herdr" && w.adapter.paneId === r.paneId && !["invalidated", "acknowledged"].includes(w.state)
|
|
29116
|
+
))
|
|
29117
|
+
reasons.push("This pane is pinned by a lead wait");
|
|
29118
|
+
reasons.push(...this.consumers([t]));
|
|
29119
|
+
if (automatic && (!this.policy(t.projectId).autoRelease || t.status !== "completed" || !t.outcomeId || this.s.orchestration.outcome(t.outcomeId).status !== "completed"))
|
|
29120
|
+
reasons.push(
|
|
29121
|
+
"Automatic release waits for the completed integrated outcome and enabled policy; failures require inspection"
|
|
29122
|
+
);
|
|
29123
|
+
return reasons;
|
|
29124
|
+
}
|
|
29125
|
+
groupReasons(t) {
|
|
29126
|
+
const tasks = this.group(t), reasons = this.consumers(tasks);
|
|
29127
|
+
if (!t.worktree || t.worktree.state !== "ready")
|
|
29128
|
+
reasons.push("Only ready Marionette-managed worktrees can be collected");
|
|
29129
|
+
for (const item of tasks) {
|
|
29130
|
+
if (!terminal2(item)) reasons.push(`Task ${item.id} is ${item.status}`);
|
|
29131
|
+
for (const r of this.s.store.all("run").filter((r2) => r2.taskId === item.id))
|
|
29132
|
+
if (r.paneId && r.cleanup?.state !== "closed")
|
|
29133
|
+
reasons.push(`Run ${r.id} must release its terminal first`);
|
|
29134
|
+
if (item.status === "completed" && item.outcomeId && this.s.orchestration.outcome(item.outcomeId).status !== "completed")
|
|
29135
|
+
reasons.push(`Outcome ${item.outcomeId} still needs integration`);
|
|
29136
|
+
else if (item.status === "completed" && item.outcomeId && this.s.orchestration.unmet(this.s.orchestration.outcome(item.outcomeId)).length)
|
|
29137
|
+
reasons.push(`Outcome ${item.outcomeId} evidence is no longer current`);
|
|
29138
|
+
}
|
|
29139
|
+
for (const o of this.s.store.all("outcome")) {
|
|
29140
|
+
if (o.status === "completed") continue;
|
|
29141
|
+
for (const ref of [
|
|
29142
|
+
...o.assessments.flatMap((a) => a.references),
|
|
29143
|
+
...o.integrated?.evidence ?? []
|
|
29144
|
+
]) {
|
|
29145
|
+
if (tasks.some((t2) => ref.path.startsWith(`task:${t2.id}:`)))
|
|
29146
|
+
reasons.push(`Outcome ${o.id} still references this checkout`);
|
|
29147
|
+
}
|
|
29148
|
+
}
|
|
29149
|
+
return [...new Set(reasons)];
|
|
29150
|
+
}
|
|
29151
|
+
async preview(taskId) {
|
|
29152
|
+
const t = this.s.task(taskId), runs = this.s.store.all("run").filter((r) => r.taskId === taskId), archive = this.archive(t);
|
|
29153
|
+
let gitReason;
|
|
29154
|
+
if (t.worktree && !archive)
|
|
29155
|
+
try {
|
|
29156
|
+
await this.clean(t.worktree);
|
|
29157
|
+
} catch (e) {
|
|
29158
|
+
gitReason = String(e);
|
|
29159
|
+
}
|
|
29160
|
+
return {
|
|
29161
|
+
taskId,
|
|
29162
|
+
policy: this.policy(t.projectId),
|
|
29163
|
+
runs: runs.map((r) => ({
|
|
29164
|
+
runId: r.id,
|
|
29165
|
+
tabId: r.tabId,
|
|
29166
|
+
paneId: r.paneId,
|
|
29167
|
+
cleanup: r.cleanup,
|
|
29168
|
+
reasons: this.runReasons(t, r, true)
|
|
29169
|
+
})),
|
|
29170
|
+
delivery: this.s.store.get("delivery", taskId),
|
|
29171
|
+
archive,
|
|
29172
|
+
collectionReasons: [
|
|
29173
|
+
...this.groupReasons(t),
|
|
29174
|
+
...gitReason ? [gitReason] : [],
|
|
29175
|
+
...!this.s.store.get("delivery", taskId) && !archive ? ["Record delivery or explicit abandonment first"] : []
|
|
29176
|
+
],
|
|
29177
|
+
boundary: "Completion retains branches and worktrees. Collection never removes sessions or workspaces."
|
|
29178
|
+
};
|
|
29179
|
+
}
|
|
29180
|
+
async locked(tasks, fn) {
|
|
29181
|
+
if (this.stopped || tasks.some((t) => this.busy.has(t.id)))
|
|
29182
|
+
refuse("Cleanup is stopping or already in progress");
|
|
29183
|
+
tasks.forEach((t) => this.busy.add(t.id));
|
|
29184
|
+
try {
|
|
29185
|
+
return await fn();
|
|
29186
|
+
} finally {
|
|
29187
|
+
tasks.forEach((t) => this.busy.delete(t.id));
|
|
29188
|
+
}
|
|
29189
|
+
}
|
|
29190
|
+
recover() {
|
|
29191
|
+
this.stopped = false;
|
|
29192
|
+
for (const r of this.s.store.all("run"))
|
|
29193
|
+
if (r.cleanup?.state === "closing")
|
|
29194
|
+
this.saveRun(r, {
|
|
29195
|
+
...r.cleanup,
|
|
29196
|
+
state: "uncertain",
|
|
29197
|
+
error: "Restart during terminal closure; inspect and reconcile. No automatic replay."
|
|
29198
|
+
});
|
|
29199
|
+
}
|
|
29200
|
+
async stop() {
|
|
29201
|
+
this.stopped = true;
|
|
29202
|
+
while (this.busy.size) await new Promise((r) => setTimeout(r, 25));
|
|
29203
|
+
}
|
|
29204
|
+
tick() {
|
|
29205
|
+
if (this.stopped || Date.now() - this.lastAutomatic < 1e4) return;
|
|
29206
|
+
this.lastAutomatic = Date.now();
|
|
29207
|
+
for (const t of this.s.store.all("task")) {
|
|
29208
|
+
if (this.busy.has(t.id)) continue;
|
|
29209
|
+
const p = this.policy(t.projectId);
|
|
29210
|
+
const r = t.runId ? this.s.store.get("run", t.runId) : void 0;
|
|
29211
|
+
if (p.autoRelease && r && !r.cleanup && !this.runReasons(t, r, true).length) {
|
|
29212
|
+
void this.locked([t], () => this.release(t, r, "Integrated outcome completed", true)).catch(
|
|
29213
|
+
(e) => this.s.store.event(t.projectId, "cleanup.error", String(e), t.id)
|
|
29214
|
+
);
|
|
29215
|
+
continue;
|
|
29216
|
+
}
|
|
29217
|
+
const delivery = this.s.store.get("delivery", t.id), a = this.archive(t);
|
|
29218
|
+
if (p.collectAfterHours === null || !delivery || delivery.disposition === "abandoned" || a && a.taskId !== t.id || this.group(t).some((x) => x.status !== "completed"))
|
|
29219
|
+
continue;
|
|
29220
|
+
if (a?.phase === "collected" && (!p.deleteMergedBranches || a.branchDeleted || delivery.disposition !== "merged"))
|
|
29221
|
+
continue;
|
|
29222
|
+
if (Date.now() - Date.parse(delivery.createdAt) < p.collectAfterHours * 36e5 || this.groupReasons(t).length)
|
|
29223
|
+
continue;
|
|
29224
|
+
if (a?.error || a?.phase === "removing") continue;
|
|
29225
|
+
void this.locked(this.group(t), async () => {
|
|
29226
|
+
const guard = () => {
|
|
29227
|
+
if (JSON.stringify(this.policy(t.projectId)) !== JSON.stringify(p))
|
|
29228
|
+
refuse("Cleanup policy changed during inspection");
|
|
29229
|
+
};
|
|
29230
|
+
const archived = a ?? await this.createArchive(t, guard);
|
|
29231
|
+
await this.collect(
|
|
29232
|
+
archived,
|
|
29233
|
+
p.deleteMergedBranches && delivery.disposition === "merged",
|
|
29234
|
+
guard
|
|
29235
|
+
);
|
|
29236
|
+
}).catch((e) => this.s.store.event(t.projectId, "cleanup.error", String(e), t.id));
|
|
29237
|
+
}
|
|
29238
|
+
}
|
|
29239
|
+
saveRun(r, cleanup) {
|
|
29240
|
+
const latest = this.s.store.get("run", r.id);
|
|
29241
|
+
this.s.store.put("run", r.id, { ...latest, cleanup: { ...cleanup, updatedAt: now() } });
|
|
29242
|
+
}
|
|
29243
|
+
async pinned(t, r) {
|
|
29244
|
+
const p = this.s.project(t.projectId), h = this.s.port(p);
|
|
29245
|
+
if (!r.tabId || !r.paneId || !r.terminalId || !r.nativeSession)
|
|
29246
|
+
refuse("A full saved worker and native session identity is required");
|
|
29247
|
+
const tab = (await h.call("tab.get", { tab_id: r.tabId })).tab;
|
|
29248
|
+
const panes = (await h.call("pane.list", { workspace_id: p.workspaceId })).panes;
|
|
29249
|
+
const members = panes?.filter((pane) => pane.tab_id === r.tabId);
|
|
29250
|
+
if (tab?.workspace_id !== p.workspaceId || tab.tab_id !== r.tabId || !r.terminalScope && (tab.pane_count !== 1 || members?.length !== 1) || members?.filter((pane) => pane.pane_id === r.paneId && pane.terminal_id === r.terminalId).length !== 1)
|
|
29251
|
+
refuse("Tab contents changed; refusing to close another pane");
|
|
29252
|
+
const a = (await h.call("agent.get", { target: r.paneId })).agent;
|
|
29253
|
+
if (a?.workspace_id !== p.workspaceId || a.pane_id !== r.paneId || a.terminal_id !== r.terminalId || a.name !== r.agentName || a.agent !== r.kind || a.agent_session?.value !== r.nativeSession || !["idle", "done"].includes(a.agent_status) || a.launch_pending || a.interactive_ready === false)
|
|
29254
|
+
refuse("Native worker identity or readiness changed");
|
|
29255
|
+
return h;
|
|
29256
|
+
}
|
|
29257
|
+
async absent(t, r) {
|
|
29258
|
+
const h = this.s.port(this.s.project(t.projectId));
|
|
29259
|
+
if (r.terminalScope === "pane") {
|
|
29260
|
+
const panes2 = (await h.call("pane.list")).panes;
|
|
29261
|
+
if (!Array.isArray(panes2)) refuse("Cannot inspect worker terminal membership");
|
|
29262
|
+
if (panes2.some(
|
|
29263
|
+
(pane) => (pane.pane_id === r.paneId || pane.terminal_id === r.terminalId) && (pane.pane_id !== r.paneId || pane.tab_id !== r.tabId || pane.terminal_id !== r.terminalId)
|
|
29264
|
+
))
|
|
29265
|
+
refuse("Original terminal may have moved; inspect it before cleanup");
|
|
29266
|
+
return !panes2.some(
|
|
29267
|
+
(pane) => pane.pane_id === r.paneId || pane.terminal_id === r.terminalId
|
|
29268
|
+
);
|
|
29269
|
+
}
|
|
29270
|
+
try {
|
|
29271
|
+
await h.call("tab.get", { tab_id: r.tabId });
|
|
29272
|
+
return false;
|
|
29273
|
+
} catch (e) {
|
|
29274
|
+
if (e.code !== "tab_not_found") throw e;
|
|
29275
|
+
}
|
|
29276
|
+
const panes = (await h.call("pane.list")).panes;
|
|
29277
|
+
if (!Array.isArray(panes) || panes.some((p) => p.pane_id === r.paneId || p.terminal_id === r.terminalId))
|
|
29278
|
+
refuse("Original terminal may have moved to another tab; inspect it before cleanup");
|
|
29279
|
+
return true;
|
|
29280
|
+
}
|
|
29281
|
+
async checkReleased(tasks) {
|
|
29282
|
+
for (const t of tasks) {
|
|
29283
|
+
for (const r of this.s.store.all("run").filter((r2) => r2.taskId === t.id && r2.paneId))
|
|
29284
|
+
if (r.cleanup?.state !== "closed" || !await this.absent(t, r))
|
|
29285
|
+
refuse(
|
|
29286
|
+
"A recorded worker terminal is still present; inspect it before collecting its checkout"
|
|
29287
|
+
);
|
|
29288
|
+
}
|
|
29289
|
+
}
|
|
29290
|
+
async release(t, r, reason, automatic, guard = () => {
|
|
29291
|
+
}) {
|
|
29292
|
+
if (r.cleanup?.state === "closed") return r.cleanup;
|
|
29293
|
+
if (r.cleanup && ["closing", "uncertain"].includes(r.cleanup.state))
|
|
29294
|
+
refuse("Reconcile ambiguous terminal closure first");
|
|
29295
|
+
try {
|
|
29296
|
+
const reasons = this.runReasons(this.s.task(t.id), r, automatic);
|
|
29297
|
+
if (reasons.length) refuse(reasons.join("; "));
|
|
29298
|
+
if (await this.absent(t, r)) {
|
|
29299
|
+
guard();
|
|
29300
|
+
this.saveRun(r, { state: "closed", reason, updatedAt: now(), output: t.output });
|
|
29301
|
+
return this.s.store.get("run", r.id).cleanup;
|
|
29302
|
+
}
|
|
29303
|
+
const h = await this.pinned(t, r);
|
|
29304
|
+
const output = (await h.call("pane.read", {
|
|
29305
|
+
pane_id: r.paneId,
|
|
29306
|
+
source: "recent_unwrapped",
|
|
29307
|
+
lines: 500,
|
|
29308
|
+
format: "text"
|
|
29309
|
+
})).read?.text ?? t.output;
|
|
29310
|
+
await this.pinned(t, r);
|
|
29311
|
+
guard();
|
|
29312
|
+
const current = this.s.task(t.id);
|
|
29313
|
+
if (current.revision !== t.revision || this.runReasons(current, this.s.store.get("run", r.id), automatic).length)
|
|
29314
|
+
refuse("Task changed during cleanup inspection");
|
|
29315
|
+
this.saveRun(r, {
|
|
29316
|
+
state: "closing",
|
|
29317
|
+
reason,
|
|
29318
|
+
updatedAt: now(),
|
|
29319
|
+
output: String(output).slice(-1e5)
|
|
29320
|
+
});
|
|
29321
|
+
if (r.terminalScope === "pane") await h.call("pane.close", { pane_id: r.paneId });
|
|
29322
|
+
else await h.call("tab.close", { tab_id: r.tabId });
|
|
29323
|
+
this.saveRun(r, { ...this.s.store.get("run", r.id).cleanup, state: "closed" });
|
|
29324
|
+
this.s.store.event(
|
|
29325
|
+
t.projectId,
|
|
29326
|
+
"cleanup.released",
|
|
29327
|
+
"Saved worker diagnostics and closed its settled terminal",
|
|
29328
|
+
t.id,
|
|
29329
|
+
{ runId: r.id }
|
|
29330
|
+
);
|
|
29331
|
+
return this.s.store.get("run", r.id).cleanup;
|
|
29332
|
+
} catch (e) {
|
|
29333
|
+
const current = this.s.store.get("run", r.id).cleanup;
|
|
29334
|
+
this.saveRun(r, {
|
|
29335
|
+
...current,
|
|
29336
|
+
state: current?.state === "closing" ? "uncertain" : "retained",
|
|
29337
|
+
reason,
|
|
29338
|
+
updatedAt: now(),
|
|
29339
|
+
error: String(e)
|
|
29340
|
+
});
|
|
29341
|
+
throw e;
|
|
29342
|
+
}
|
|
29343
|
+
}
|
|
29344
|
+
async clean(w) {
|
|
29345
|
+
await validateWorktree(w);
|
|
29346
|
+
const status = await git(w.path, [
|
|
29347
|
+
"status",
|
|
29348
|
+
"--porcelain",
|
|
29349
|
+
"--untracked-files=all",
|
|
29350
|
+
"--ignored"
|
|
29351
|
+
]);
|
|
29352
|
+
if (status)
|
|
29353
|
+
refuse(
|
|
29354
|
+
"Worktree has modified, untracked, or ignored files; preserve or remove them explicitly before collection"
|
|
29355
|
+
);
|
|
29356
|
+
return git(w.path, ["rev-parse", "HEAD"]);
|
|
29357
|
+
}
|
|
29358
|
+
async checkDelivery(w, d) {
|
|
29359
|
+
if (await this.clean(w) !== d.head) refuse("Worktree HEAD changed after delivery");
|
|
29360
|
+
if (d.disposition !== "abandoned") {
|
|
29361
|
+
if (!d.targetRef || !d.targetCommit) refuse("Delivery target is missing");
|
|
29362
|
+
const current = await git(w.repositoryRoot, [
|
|
29363
|
+
"rev-parse",
|
|
29364
|
+
"--verify",
|
|
29365
|
+
"--end-of-options",
|
|
29366
|
+
`${d.targetRef}^{commit}`
|
|
29367
|
+
]);
|
|
29368
|
+
await git(w.repositoryRoot, ["merge-base", "--is-ancestor", d.head, current]);
|
|
29369
|
+
}
|
|
29370
|
+
}
|
|
29371
|
+
archiveRoot(id) {
|
|
29372
|
+
return safePath(dirname4(this.s.store.path), `archives/${id}`);
|
|
29373
|
+
}
|
|
29374
|
+
/** Live files remain authoritative until collection actually removes the checkout. */
|
|
29375
|
+
evidencePath(t, input) {
|
|
29376
|
+
const a = this.archive(t);
|
|
29377
|
+
if (!a || a.phase === "archived" || existsSync3(a.worktree.path))
|
|
29378
|
+
return safePath(t.cwd, input, true);
|
|
29379
|
+
const source = resolve4(t.cwd, input);
|
|
29380
|
+
if (!inside(t.cwd, source)) refuse("Evidence escapes task directory");
|
|
29381
|
+
const f = a.files.find((f2) => f2.source === source);
|
|
29382
|
+
if (!f) refuse("Evidence was not preserved in this archive");
|
|
29383
|
+
const path = safePath(this.archiveRoot(a.id), `files/${f.digest}`, true);
|
|
29384
|
+
if (digest(path) !== f.digest) refuse("Archived evidence digest changed");
|
|
29385
|
+
return path;
|
|
29386
|
+
}
|
|
29387
|
+
async createArchive(t, guard) {
|
|
29388
|
+
const prior = this.archive(t);
|
|
29389
|
+
if (prior) return prior;
|
|
29390
|
+
const reasons = this.groupReasons(t);
|
|
29391
|
+
if (reasons.length) refuse(reasons.join("; "));
|
|
29392
|
+
const w = t.worktree, d = this.s.store.get("delivery", t.id);
|
|
29393
|
+
if (!d) refuse("Record delivery or abandonment before archiving");
|
|
29394
|
+
await this.checkDelivery(w, d);
|
|
29395
|
+
const tasks = this.group(t), taskIds = new Set(tasks.map((t2) => t2.id));
|
|
29396
|
+
await this.checkReleased(tasks);
|
|
29397
|
+
const id = randomUUID3(), root = this.archiveRoot(id);
|
|
29398
|
+
mkdirSync4(resolve4(root, "files"), { recursive: true, mode: 448 });
|
|
29399
|
+
const files = /* @__PURE__ */ new Map();
|
|
29400
|
+
const copy = (source, expected, alias = source) => {
|
|
29401
|
+
const actual = digest(source);
|
|
29402
|
+
if (!actual || expected && expected !== actual)
|
|
29403
|
+
refuse("Evidence is missing or changed before archival");
|
|
29404
|
+
const bytes = readFileSync4(source);
|
|
29405
|
+
if (hash(bytes) !== actual) refuse("Evidence changed during archival");
|
|
29406
|
+
writeFileSync2(resolve4(root, "files", actual), bytes, { mode: 384, flush: true });
|
|
29407
|
+
files.set(source, actual);
|
|
29408
|
+
files.set(alias, actual);
|
|
29409
|
+
};
|
|
29410
|
+
for (const item of tasks) {
|
|
29411
|
+
for (const v of item.verification ?? [])
|
|
29412
|
+
if (v.passed && v.check.type === "file")
|
|
29413
|
+
copy(safePath(item.cwd, v.check.path, true), v.digest, resolve4(item.cwd, v.check.path));
|
|
29414
|
+
for (const path of item.receipt?.artifacts ?? []) {
|
|
29415
|
+
const source = safePath(item.cwd, path, true);
|
|
29416
|
+
if (!item.ownership.some((owned) => inside(safePath(item.cwd, owned), source)))
|
|
29417
|
+
refuse("Reported artifact no longer belongs to the task");
|
|
29418
|
+
if (statSync3(source).isDirectory()) continue;
|
|
29419
|
+
copy(source, void 0, resolve4(item.cwd, path));
|
|
29420
|
+
}
|
|
29421
|
+
}
|
|
29422
|
+
for (const o of this.s.store.all("outcome")) {
|
|
29423
|
+
for (const ref of [
|
|
29424
|
+
...o.assessments.flatMap((a2) => a2.references),
|
|
29425
|
+
...o.integrated?.evidence ?? []
|
|
29426
|
+
]) {
|
|
29427
|
+
const match = /^task:([^:]+):(.+)$/.exec(ref.path);
|
|
29428
|
+
if (match && taskIds.has(match[1]))
|
|
29429
|
+
copy(
|
|
29430
|
+
safePath(this.s.task(match[1]).cwd, match[2], true),
|
|
29431
|
+
ref.digest,
|
|
29432
|
+
resolve4(this.s.task(match[1]).cwd, match[2])
|
|
29433
|
+
);
|
|
29434
|
+
else if (!match) {
|
|
29435
|
+
const path = resolve4(this.s.project(o.projectId).root, ref.path);
|
|
29436
|
+
if (inside(w.path, path))
|
|
29437
|
+
refuse(
|
|
29438
|
+
"Project-relative evidence still uses this checkout; replace it with a task reference"
|
|
29439
|
+
);
|
|
29440
|
+
}
|
|
29441
|
+
}
|
|
29442
|
+
}
|
|
29443
|
+
await git(w.repositoryRoot, [
|
|
29444
|
+
"bundle",
|
|
29445
|
+
"create",
|
|
29446
|
+
resolve4(root, "commits.bundle"),
|
|
29447
|
+
`refs/heads/${w.branch}`
|
|
29448
|
+
]);
|
|
29449
|
+
await git(w.repositoryRoot, ["bundle", "verify", resolve4(root, "commits.bundle")]);
|
|
29450
|
+
flush(resolve4(root, "commits.bundle"));
|
|
29451
|
+
const bundleDigest = await fileHash(resolve4(root, "commits.bundle"));
|
|
29452
|
+
const manifest = JSON.stringify(
|
|
29453
|
+
{
|
|
29454
|
+
tasks,
|
|
29455
|
+
runs: this.s.store.all("run").filter((r) => taskIds.has(r.taskId)).map(({ tokenHash, ...r }) => r),
|
|
29456
|
+
outcomes: this.s.store.all("outcome").filter((o) => tasks.some((t2) => t2.outcomeId === o.id)),
|
|
29457
|
+
delivery: d,
|
|
29458
|
+
files: [...files],
|
|
29459
|
+
archivedAt: now()
|
|
29460
|
+
},
|
|
29461
|
+
null,
|
|
29462
|
+
2
|
|
29463
|
+
);
|
|
29464
|
+
writeFileSync2(resolve4(root, "manifest.json"), manifest, { mode: 384, flush: true });
|
|
29465
|
+
flush(resolve4(root, "files"));
|
|
29466
|
+
flush(root);
|
|
29467
|
+
flush(dirname4(root));
|
|
29468
|
+
flush(dirname4(dirname4(root)));
|
|
29469
|
+
await this.checkDelivery(w, d);
|
|
29470
|
+
guard();
|
|
29471
|
+
if (this.groupReasons(t).length || this.group(t).some((x) => !tasks.some((y) => x.id === y.id && x.revision === y.revision)))
|
|
29472
|
+
refuse("Checkout consumers changed during archival");
|
|
29473
|
+
for (const [path, expected] of files)
|
|
29474
|
+
if (digest(path) !== expected) refuse("Evidence changed during archival");
|
|
29475
|
+
const a = {
|
|
29476
|
+
id,
|
|
29477
|
+
taskId: t.id,
|
|
29478
|
+
projectId: t.projectId,
|
|
29479
|
+
taskIds: [...taskIds],
|
|
29480
|
+
phase: "archived",
|
|
29481
|
+
worktree: w,
|
|
29482
|
+
delivery: d,
|
|
29483
|
+
files: [...files].map(([source, digest2]) => ({ source, digest: digest2 })),
|
|
29484
|
+
manifestDigest: hash(manifest),
|
|
29485
|
+
bundleDigest,
|
|
29486
|
+
createdAt: now(),
|
|
29487
|
+
updatedAt: now()
|
|
29488
|
+
};
|
|
29489
|
+
this.s.store.transaction(() => {
|
|
29490
|
+
this.s.store.put("archive", id, a);
|
|
29491
|
+
for (const item of tasks) this.s.updateTask(item, { archiveId: id });
|
|
29492
|
+
this.s.store.event(
|
|
29493
|
+
t.projectId,
|
|
29494
|
+
"cleanup.archived",
|
|
29495
|
+
"Preserved evidence, diagnostics, and committed history; tasks are sealed",
|
|
29496
|
+
t.id,
|
|
29497
|
+
{ archiveId: id }
|
|
29498
|
+
);
|
|
29499
|
+
});
|
|
29500
|
+
return a;
|
|
29501
|
+
}
|
|
29502
|
+
async verifyArchive(a) {
|
|
29503
|
+
const root = this.archiveRoot(a.id);
|
|
29504
|
+
if (hash(readFileSync4(safePath(root, "manifest.json", true))) !== a.manifestDigest || await fileHash(safePath(root, "commits.bundle", true)) !== a.bundleDigest)
|
|
29505
|
+
refuse("Archive integrity check failed");
|
|
29506
|
+
for (const f of a.files)
|
|
29507
|
+
if (digest(safePath(root, `files/${f.digest}`, true)) !== f.digest)
|
|
29508
|
+
refuse("Archived evidence integrity check failed");
|
|
29509
|
+
}
|
|
29510
|
+
saveArchive(a, patch) {
|
|
29511
|
+
const next = { ...this.s.store.get("archive", a.id), ...patch, updatedAt: now() };
|
|
29512
|
+
this.s.store.put("archive", a.id, next);
|
|
29513
|
+
return next;
|
|
29514
|
+
}
|
|
29515
|
+
async collect(a, deleteBranch, guard) {
|
|
29516
|
+
if (a.phase === "collected" && (!deleteBranch || a.branchDeleted)) return a;
|
|
29517
|
+
if (deleteBranch && a.delivery.disposition !== "merged" && a.delivery.disposition !== "abandoned")
|
|
29518
|
+
refuse("Published PR branches are retained until merged or explicitly abandoned");
|
|
29519
|
+
try {
|
|
29520
|
+
await this.verifyArchive(a);
|
|
29521
|
+
const t = this.s.task(a.taskId), reasons = this.groupReasons(t);
|
|
29522
|
+
if (reasons.length) refuse(reasons.join("; "));
|
|
29523
|
+
await this.checkReleased(this.group(t));
|
|
29524
|
+
const w = a.worktree;
|
|
29525
|
+
const common = realpathSync3(
|
|
29526
|
+
await git(w.repositoryRoot, ["rev-parse", "--path-format=absolute", "--git-common-dir"])
|
|
29527
|
+
);
|
|
29528
|
+
if (common !== w.commonDir) refuse("Source repository identity changed");
|
|
29529
|
+
if (existsSync3(w.path)) {
|
|
29530
|
+
await this.checkDelivery(w, a.delivery);
|
|
29531
|
+
for (const f of a.files)
|
|
29532
|
+
if (digest(f.source) !== f.digest) refuse("Live evidence changed after archival");
|
|
29533
|
+
guard();
|
|
29534
|
+
if (this.groupReasons(t).length) refuse("Checkout consumers changed before collection");
|
|
29535
|
+
a = this.saveArchive(a, { phase: "removing", error: void 0 });
|
|
29536
|
+
await git(w.repositoryRoot, ["worktree", "remove", "--", w.path]);
|
|
29537
|
+
}
|
|
29538
|
+
const listing = await git(w.repositoryRoot, ["worktree", "list", "--porcelain", "-z"]);
|
|
29539
|
+
if (common !== w.commonDir || listing.split("\0").includes(`worktree ${w.path}`) || existsSync3(w.path))
|
|
29540
|
+
refuse("Worktree removal is incomplete; inspect its registration");
|
|
29541
|
+
a = this.saveArchive(a, { phase: "worktree-removed", error: void 0 });
|
|
29542
|
+
if (deleteBranch) {
|
|
29543
|
+
const refs = await git(w.repositoryRoot, [
|
|
29544
|
+
"for-each-ref",
|
|
29545
|
+
"--format=%(refname) %(objectname)",
|
|
29546
|
+
`refs/heads/${w.branch}`
|
|
29547
|
+
]);
|
|
29548
|
+
if (refs) {
|
|
29549
|
+
if (refs !== `refs/heads/${w.branch} ${a.delivery.head}`)
|
|
29550
|
+
refuse("Branch moved after archival; preserving it");
|
|
29551
|
+
if (listing.split("\0").includes(`branch refs/heads/${w.branch}`))
|
|
29552
|
+
refuse("Another checkout still uses the branch");
|
|
29553
|
+
if (a.delivery.disposition === "abandoned") {
|
|
29554
|
+
guard();
|
|
29555
|
+
await git(w.repositoryRoot, [
|
|
29556
|
+
"update-ref",
|
|
29557
|
+
"-d",
|
|
29558
|
+
`refs/heads/${w.branch}`,
|
|
29559
|
+
a.delivery.head
|
|
29560
|
+
]);
|
|
29561
|
+
} else {
|
|
29562
|
+
const target = await git(w.repositoryRoot, [
|
|
29563
|
+
"rev-parse",
|
|
29564
|
+
"--verify",
|
|
29565
|
+
"--end-of-options",
|
|
29566
|
+
`${a.delivery.targetRef}^{commit}`
|
|
29567
|
+
]);
|
|
29568
|
+
await git(w.repositoryRoot, ["merge-base", "--is-ancestor", a.delivery.head, target]);
|
|
29569
|
+
guard();
|
|
29570
|
+
await git(w.repositoryRoot, [
|
|
29571
|
+
"update-ref",
|
|
29572
|
+
"-d",
|
|
29573
|
+
`refs/heads/${w.branch}`,
|
|
29574
|
+
a.delivery.head
|
|
29575
|
+
]);
|
|
29576
|
+
}
|
|
29577
|
+
}
|
|
29578
|
+
}
|
|
29579
|
+
a = this.saveArchive(a, {
|
|
29580
|
+
phase: "collected",
|
|
29581
|
+
branchDeleted: deleteBranch,
|
|
29582
|
+
error: void 0
|
|
29583
|
+
});
|
|
29584
|
+
this.s.store.event(
|
|
29585
|
+
a.projectId,
|
|
29586
|
+
"cleanup.collected",
|
|
29587
|
+
"Collected delivered worktree; archive retained",
|
|
29588
|
+
a.taskId,
|
|
29589
|
+
{ archiveId: a.id, branchDeleted: deleteBranch }
|
|
29590
|
+
);
|
|
29591
|
+
return a;
|
|
29592
|
+
} catch (e) {
|
|
29593
|
+
this.saveArchive(a, { error: String(e) });
|
|
29594
|
+
throw e;
|
|
29595
|
+
}
|
|
29596
|
+
}
|
|
29597
|
+
async invoke(action, raw) {
|
|
29598
|
+
if (action === "cleanup.preview") return this.preview(external_exports.string().parse(raw.taskId));
|
|
29599
|
+
const c = this.s.guard(raw.lease), reason = external_exports.string().min(1).max(4e3).parse(raw.reason);
|
|
29600
|
+
const guard = () => {
|
|
29601
|
+
this.s.guard(raw.lease);
|
|
29602
|
+
};
|
|
29603
|
+
if (action === "cleanup.configure") {
|
|
29604
|
+
const p = cleanupPolicySchema.parse(raw.policy);
|
|
29605
|
+
this.s.store.put("cleanup-policy", c.projectId, p);
|
|
29606
|
+
this.s.store.event(c.projectId, "cleanup.policy", `${c.owner}: ${reason}`, void 0, p);
|
|
29607
|
+
return p;
|
|
29608
|
+
}
|
|
29609
|
+
const t = this.s.task(external_exports.string().parse(raw.taskId));
|
|
29610
|
+
if (t.projectId !== c.projectId) refuse("Task and lead belong to different projects");
|
|
29611
|
+
if (action === "cleanup.release" || action === "cleanup.reconcile") {
|
|
29612
|
+
const r = this.s.store.get("run", external_exports.string().parse(raw.runId ?? t.runId));
|
|
29613
|
+
if (!r || r.taskId !== t.id) refuse("Run does not belong to this task");
|
|
29614
|
+
return this.locked([t], async () => {
|
|
29615
|
+
if (action === "cleanup.release") return this.release(t, r, reason, false, guard);
|
|
29616
|
+
if (r.cleanup?.state !== "uncertain")
|
|
29617
|
+
refuse("Only uncertain cleanup needs reconciliation");
|
|
29618
|
+
const resolution = external_exports.enum(["closed", "not-closed"]).parse(raw.resolution);
|
|
29619
|
+
if (resolution === "closed") {
|
|
29620
|
+
if (await this.absent(t, r)) {
|
|
29621
|
+
guard();
|
|
29622
|
+
this.saveRun(r, { ...r.cleanup, state: "closed", reason, error: void 0 });
|
|
29623
|
+
return this.s.store.get("run", r.id).cleanup;
|
|
29624
|
+
}
|
|
29625
|
+
refuse("Original worker terminal is still present");
|
|
29626
|
+
}
|
|
29627
|
+
await this.pinned(t, r);
|
|
29628
|
+
guard();
|
|
29629
|
+
this.saveRun(r, { ...r.cleanup, state: "retained", reason, error: void 0 });
|
|
29630
|
+
return this.s.store.get("run", r.id).cleanup;
|
|
29631
|
+
});
|
|
29632
|
+
}
|
|
29633
|
+
if (!t.worktree) refuse("Task has no managed worktree");
|
|
29634
|
+
return this.locked(this.group(t), async () => {
|
|
29635
|
+
if (action === "cleanup.deliver") {
|
|
29636
|
+
const archived = this.archive(t);
|
|
29637
|
+
const disposition = external_exports.enum(["merged", "published", "abandoned"]).parse(raw.disposition);
|
|
29638
|
+
if (archived && archived.taskId !== t.id)
|
|
29639
|
+
refuse("Update delivery on the task that owns the archive");
|
|
29640
|
+
const head = archived && !existsSync3(t.worktree.path) ? await git(t.worktree.repositoryRoot, [
|
|
29641
|
+
"rev-parse",
|
|
29642
|
+
"--verify",
|
|
29643
|
+
"--end-of-options",
|
|
29644
|
+
`refs/heads/${t.worktree.branch}^{commit}`
|
|
29645
|
+
]) : await this.clean(t.worktree);
|
|
29646
|
+
if (archived && (head !== archived.delivery.head || archived.branchDeleted))
|
|
29647
|
+
refuse("Archived branch changed or was already deleted");
|
|
29648
|
+
let targetRef, targetCommit;
|
|
29649
|
+
if (disposition !== "abandoned") {
|
|
29650
|
+
targetRef = external_exports.string().min(1).parse(raw.targetRef);
|
|
29651
|
+
if (!/^refs\/(heads|remotes)\//.test(targetRef) || targetRef === `refs/heads/${t.worktree.branch}` || disposition === "published" && !targetRef.startsWith("refs/remotes/"))
|
|
29652
|
+
refuse(
|
|
29653
|
+
"Use an explicit target branch ref; published delivery needs a refreshed remote-tracking ref"
|
|
29654
|
+
);
|
|
29655
|
+
await git(t.worktree.repositoryRoot, ["check-ref-format", targetRef]);
|
|
29656
|
+
targetCommit = await git(t.worktree.repositoryRoot, [
|
|
29657
|
+
"rev-parse",
|
|
29658
|
+
"--verify",
|
|
29659
|
+
"--end-of-options",
|
|
29660
|
+
`${targetRef}^{commit}`
|
|
29661
|
+
]);
|
|
29662
|
+
await git(t.worktree.repositoryRoot, [
|
|
29663
|
+
"merge-base",
|
|
29664
|
+
"--is-ancestor",
|
|
29665
|
+
head,
|
|
29666
|
+
targetCommit
|
|
29667
|
+
]);
|
|
29668
|
+
}
|
|
29669
|
+
guard();
|
|
29670
|
+
const d = {
|
|
29671
|
+
taskId: t.id,
|
|
29672
|
+
projectId: t.projectId,
|
|
29673
|
+
disposition,
|
|
29674
|
+
head,
|
|
29675
|
+
targetRef,
|
|
29676
|
+
targetCommit,
|
|
29677
|
+
reason,
|
|
29678
|
+
owner: c.owner,
|
|
29679
|
+
createdAt: now()
|
|
29680
|
+
};
|
|
29681
|
+
const previous = this.s.store.get("delivery", t.id);
|
|
29682
|
+
if (previous && JSON.stringify({ ...previous, createdAt: "" }) === JSON.stringify({ ...d, createdAt: "" }))
|
|
29683
|
+
return previous;
|
|
29684
|
+
this.s.store.put("delivery", t.id, d);
|
|
29685
|
+
if (archived) this.saveArchive(archived, { delivery: d, error: void 0 });
|
|
29686
|
+
this.s.store.event(t.projectId, "cleanup.delivered", `${disposition}: ${reason}`, t.id, d);
|
|
29687
|
+
return d;
|
|
29688
|
+
}
|
|
29689
|
+
if (action === "cleanup.archive") return this.createArchive(t, guard);
|
|
29690
|
+
if (action === "cleanup.collect") {
|
|
29691
|
+
const a = this.archive(t);
|
|
29692
|
+
if (!a || a.id !== raw.archiveId)
|
|
29693
|
+
refuse("Preview and supply the current archiveId before collection");
|
|
29694
|
+
return this.collect(a, external_exports.boolean().parse(raw.deleteBranch ?? false), guard);
|
|
29695
|
+
}
|
|
29696
|
+
throw new AppError("unknown_action", `Unknown action: ${action}`, 404);
|
|
29697
|
+
});
|
|
29698
|
+
}
|
|
29699
|
+
};
|
|
29700
|
+
|
|
28916
29701
|
// src/model-catalog.ts
|
|
28917
29702
|
import { spawn as spawn2 } from "node:child_process";
|
|
28918
29703
|
|
|
@@ -28973,7 +29758,7 @@ var strategySchema = external_exports.object({
|
|
|
28973
29758
|
|
|
28974
29759
|
// src/model-catalog.ts
|
|
28975
29760
|
async function metadata(binary, args2, cwd, kind) {
|
|
28976
|
-
return new Promise((
|
|
29761
|
+
return new Promise((resolve13, reject) => {
|
|
28977
29762
|
const child = spawn2(binary, args2, { cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
28978
29763
|
let buffer = "", bytes = 0, finished = false, nextId = 2;
|
|
28979
29764
|
const rows = [];
|
|
@@ -28983,7 +29768,7 @@ async function metadata(binary, args2, cwd, kind) {
|
|
|
28983
29768
|
clearTimeout(timer);
|
|
28984
29769
|
child.stdin.end();
|
|
28985
29770
|
child.kill("SIGTERM");
|
|
28986
|
-
error ? reject(error) :
|
|
29771
|
+
error ? reject(error) : resolve13(rows);
|
|
28987
29772
|
};
|
|
28988
29773
|
const timer = setTimeout(
|
|
28989
29774
|
() => finish(new AppError("catalog_timeout", `${binary} model metadata timed out`)),
|
|
@@ -29569,9 +30354,9 @@ async function probeProfile(profile, cwd) {
|
|
|
29569
30354
|
}
|
|
29570
30355
|
|
|
29571
30356
|
// src/orchestration.ts
|
|
29572
|
-
import { mkdirSync as
|
|
29573
|
-
import { dirname as
|
|
29574
|
-
import { randomUUID as
|
|
30357
|
+
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
30358
|
+
import { dirname as dirname5, resolve as resolve5, relative as relative3 } from "node:path";
|
|
30359
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
29575
30360
|
var fail = (code, message) => {
|
|
29576
30361
|
throw new AppError(code, message, 409);
|
|
29577
30362
|
};
|
|
@@ -29590,7 +30375,7 @@ var Orchestration = class {
|
|
|
29590
30375
|
id: `legacy-${t.id}`,
|
|
29591
30376
|
projectId: t.projectId,
|
|
29592
30377
|
objective: t.prompt,
|
|
29593
|
-
scope: t.ownership.map((path) =>
|
|
30378
|
+
scope: t.ownership.map((path) => resolve5(t.worktree?.sourceCwd ?? t.cwd, path)),
|
|
29594
30379
|
category: "software",
|
|
29595
30380
|
criteria: t.checks.map((check, index) => ({
|
|
29596
30381
|
id: `check-${index + 1}`,
|
|
@@ -29660,7 +30445,7 @@ var Orchestration = class {
|
|
|
29660
30445
|
if (!v.passed) return false;
|
|
29661
30446
|
if (v.check.type !== "file") return true;
|
|
29662
30447
|
try {
|
|
29663
|
-
return !!v.digest && digest(
|
|
30448
|
+
return !!v.digest && digest(this.s.cleanup.evidencePath(t, v.check.path)) === v.digest;
|
|
29664
30449
|
} catch {
|
|
29665
30450
|
return false;
|
|
29666
30451
|
}
|
|
@@ -29701,8 +30486,15 @@ var Orchestration = class {
|
|
|
29701
30486
|
const t = this.s.task(taskReference[1]);
|
|
29702
30487
|
if (t.projectId !== projectId)
|
|
29703
30488
|
fail("evidence_scope", "Evidence task belongs to another project");
|
|
29704
|
-
const
|
|
29705
|
-
|
|
30489
|
+
const original = resolve5(t.cwd, taskReference[2]);
|
|
30490
|
+
const path = this.s.cleanup.evidencePath(t, taskReference[2]);
|
|
30491
|
+
const archived = !inside(t.cwd, path);
|
|
30492
|
+
if (!t.ownership.some(
|
|
30493
|
+
(owned) => inside(
|
|
30494
|
+
archived ? resolve5(t.cwd, owned) : safePath(t.cwd, owned),
|
|
30495
|
+
archived ? original : path
|
|
30496
|
+
)
|
|
30497
|
+
))
|
|
29706
30498
|
fail("evidence_scope", "Task evidence must be inside its ownership");
|
|
29707
30499
|
return path;
|
|
29708
30500
|
}
|
|
@@ -29737,7 +30529,7 @@ var Orchestration = class {
|
|
|
29737
30529
|
}
|
|
29738
30530
|
revision(o, reason, owner, before, after, taskId, evidence = []) {
|
|
29739
30531
|
const r = {
|
|
29740
|
-
id:
|
|
30532
|
+
id: randomUUID4(),
|
|
29741
30533
|
projectId: o.projectId,
|
|
29742
30534
|
outcomeId: o.id,
|
|
29743
30535
|
revision: o.revision,
|
|
@@ -29842,6 +30634,9 @@ var Orchestration = class {
|
|
|
29842
30634
|
}
|
|
29843
30635
|
references(projectId, paths) {
|
|
29844
30636
|
return external_exports.array(text).min(1).parse(paths).map((path) => {
|
|
30637
|
+
const taskReference = /^task:([^:]+):/.exec(path);
|
|
30638
|
+
if (taskReference && this.s.cleanup.active(taskReference[1]))
|
|
30639
|
+
fail("cleanup_busy", "Evidence is being archived; retry after cleanup finishes");
|
|
29845
30640
|
const value = digest(this.evidencePath(projectId, path));
|
|
29846
30641
|
if (!value)
|
|
29847
30642
|
fail(
|
|
@@ -29922,6 +30717,7 @@ var Orchestration = class {
|
|
|
29922
30717
|
fail("depth_limit", "Outcome delegation depth reached");
|
|
29923
30718
|
}
|
|
29924
30719
|
if (task.outcomeId) {
|
|
30720
|
+
this.s.cleanup.assertOutcomeMutable(task.outcomeId);
|
|
29925
30721
|
const o = this.outcome(task.outcomeId);
|
|
29926
30722
|
if (o.projectId !== task.projectId)
|
|
29927
30723
|
fail("outcome_scope", "Outcome belongs to another project");
|
|
@@ -29932,7 +30728,7 @@ var Orchestration = class {
|
|
|
29932
30728
|
safePath(this.s.project(task.projectId).root, scope),
|
|
29933
30729
|
task.worktree ? safePath(
|
|
29934
30730
|
task.worktree.sourceCwd,
|
|
29935
|
-
|
|
30731
|
+
relative3(task.worktree.cwd, safePath(task.cwd, owned))
|
|
29936
30732
|
) : safePath(task.cwd, owned)
|
|
29937
30733
|
)
|
|
29938
30734
|
))
|
|
@@ -29945,7 +30741,7 @@ var Orchestration = class {
|
|
|
29945
30741
|
);
|
|
29946
30742
|
} else {
|
|
29947
30743
|
const o = {
|
|
29948
|
-
id:
|
|
30744
|
+
id: randomUUID4(),
|
|
29949
30745
|
projectId: task.projectId,
|
|
29950
30746
|
objective: task.prompt,
|
|
29951
30747
|
scope: task.ownership.map((p) => safePath(task.cwd, p)),
|
|
@@ -30055,6 +30851,7 @@ var Orchestration = class {
|
|
|
30055
30851
|
}
|
|
30056
30852
|
reviseTask(raw, c) {
|
|
30057
30853
|
const t = this.s.task(text.parse(raw.taskId));
|
|
30854
|
+
this.s.cleanup.assertMutable(t);
|
|
30058
30855
|
if (t.projectId !== c.projectId || !t.outcomeId)
|
|
30059
30856
|
fail("project_mismatch", "Task must belong to this lead and an outcome");
|
|
30060
30857
|
const o = this.outcome(t.outcomeId);
|
|
@@ -30062,6 +30859,9 @@ var Orchestration = class {
|
|
|
30062
30859
|
if (t.revision !== external_exports.number().int().parse(raw.expectedRevision))
|
|
30063
30860
|
fail("stale_revision", "Task changed; refresh before revising");
|
|
30064
30861
|
const patch = planPatchSchema.parse(raw.patch), reason = text.parse(raw.reason);
|
|
30862
|
+
for (const id of patch.dependencies ?? [])
|
|
30863
|
+
if (this.s.cleanup.active(id))
|
|
30864
|
+
fail("cleanup_busy", "Dependency is being cleaned up; retry after it finishes");
|
|
30065
30865
|
if (t.runId && this.s.store.get("run", t.runId)?.phase !== "stopped" && !["paused", "waiting"].includes(t.status))
|
|
30066
30866
|
fail("task_active", "Pause and settle the task before revising it");
|
|
30067
30867
|
if (patch.supersededBy) {
|
|
@@ -30070,13 +30870,15 @@ var Orchestration = class {
|
|
|
30070
30870
|
fail("supersede_scope", "Replacement must be required work in the same outcome");
|
|
30071
30871
|
}
|
|
30072
30872
|
for (const check of patch.checks ?? []) if (check.type === "file") safePath(t.cwd, check.path);
|
|
30873
|
+
const released = t.runId && this.s.store.get("run", t.runId)?.cleanup?.state === "closed";
|
|
30073
30874
|
const updated = {
|
|
30074
30875
|
...t,
|
|
30075
30876
|
...patch,
|
|
30076
30877
|
revision: t.revision + 1,
|
|
30077
30878
|
receipt: void 0,
|
|
30078
30879
|
verification: void 0,
|
|
30079
|
-
|
|
30880
|
+
runId: released ? void 0 : t.runId,
|
|
30881
|
+
status: patch.supersededBy ? "cancelled" : t.runId && !released ? "paused" : "queued",
|
|
30080
30882
|
updatedAt: now()
|
|
30081
30883
|
};
|
|
30082
30884
|
this.validateGraph(
|
|
@@ -30121,7 +30923,7 @@ var Orchestration = class {
|
|
|
30121
30923
|
}
|
|
30122
30924
|
if (raw.action === "finding") {
|
|
30123
30925
|
const finding = {
|
|
30124
|
-
id:
|
|
30926
|
+
id: randomUUID4(),
|
|
30125
30927
|
projectId: t.projectId,
|
|
30126
30928
|
outcomeId: t.outcomeId,
|
|
30127
30929
|
taskId: t.id,
|
|
@@ -30217,9 +31019,9 @@ var Orchestration = class {
|
|
|
30217
31019
|
"profile_changed",
|
|
30218
31020
|
"Profile changed during validation; validate the current configuration"
|
|
30219
31021
|
);
|
|
30220
|
-
const path =
|
|
30221
|
-
|
|
30222
|
-
|
|
31022
|
+
const path = resolve5(dirname5(this.s.store.path), "profile-evidence", randomUUID4() + ".json");
|
|
31023
|
+
mkdirSync5(dirname5(path), { recursive: true, mode: 448 });
|
|
31024
|
+
writeFileSync3(path, result.output, { mode: 384 });
|
|
30223
31025
|
const updated = {
|
|
30224
31026
|
...profile,
|
|
30225
31027
|
availability,
|
|
@@ -30244,7 +31046,7 @@ var Orchestration = class {
|
|
|
30244
31046
|
const { key, ...fields } = i;
|
|
30245
31047
|
const o2 = {
|
|
30246
31048
|
...fields,
|
|
30247
|
-
id:
|
|
31049
|
+
id: randomUUID4(),
|
|
30248
31050
|
leadOwner: c.owner,
|
|
30249
31051
|
revision: 1,
|
|
30250
31052
|
status: "open",
|
|
@@ -30304,6 +31106,7 @@ var Orchestration = class {
|
|
|
30304
31106
|
}
|
|
30305
31107
|
if (action.startsWith("strategy.")) return this.strategy(action, raw, c);
|
|
30306
31108
|
const o = this.outcome(text.parse(raw.outcomeId));
|
|
31109
|
+
this.s.cleanup.assertOutcomeMutable(o.id);
|
|
30307
31110
|
if (o.projectId !== c.projectId) fail("project_mismatch", "Outcome and lead differ");
|
|
30308
31111
|
this.checkRevision(o, raw.expectedRevision);
|
|
30309
31112
|
if (action === "outcome.revise") {
|
|
@@ -30372,6 +31175,8 @@ var Orchestration = class {
|
|
|
30372
31175
|
});
|
|
30373
31176
|
}
|
|
30374
31177
|
strategy(action, raw, c) {
|
|
31178
|
+
const cleanupOutcome = raw.strategy?.outcomeId ?? raw.outcomeId ?? (raw.strategyId ? this.s.store.get("strategy", raw.strategyId)?.outcomeId : void 0);
|
|
31179
|
+
if (cleanupOutcome) this.s.cleanup.assertOutcomeMutable(cleanupOutcome);
|
|
30375
31180
|
if (action === "strategy.create") {
|
|
30376
31181
|
const i = strategySchema.parse(raw.strategy), o = this.outcome(i.outcomeId);
|
|
30377
31182
|
if (o.projectId !== c.projectId) fail("project_mismatch", "Strategy and lead differ");
|
|
@@ -30382,7 +31187,7 @@ var Orchestration = class {
|
|
|
30382
31187
|
fail("quorum", "Quorum exceeds participant count");
|
|
30383
31188
|
const strategy2 = {
|
|
30384
31189
|
...i,
|
|
30385
|
-
id:
|
|
31190
|
+
id: randomUUID4(),
|
|
30386
31191
|
projectId: c.projectId,
|
|
30387
31192
|
revision: 1,
|
|
30388
31193
|
round: 1,
|
|
@@ -30475,68 +31280,620 @@ var Orchestration = class {
|
|
|
30475
31280
|
}
|
|
30476
31281
|
};
|
|
30477
31282
|
|
|
30478
|
-
// src/herdr.ts
|
|
31283
|
+
// src/herdr-protocol.ts
|
|
31284
|
+
var HERDR_METHODS = [
|
|
31285
|
+
"ping",
|
|
31286
|
+
"server.stop",
|
|
31287
|
+
"server.live_handoff",
|
|
31288
|
+
"server.reload_config",
|
|
31289
|
+
"server.agent_manifests",
|
|
31290
|
+
"server.reload_agent_manifests",
|
|
31291
|
+
"notification.show",
|
|
31292
|
+
"product_announcement.dismiss",
|
|
31293
|
+
"release_notes.dismiss",
|
|
31294
|
+
"command.invoke",
|
|
31295
|
+
"client.window_title.set",
|
|
31296
|
+
"client.window_title.clear",
|
|
31297
|
+
"client_shell.surface.set",
|
|
31298
|
+
"session.snapshot",
|
|
31299
|
+
"workspace.create",
|
|
31300
|
+
"workspace.list",
|
|
31301
|
+
"workspace.get",
|
|
31302
|
+
"workspace.focus",
|
|
31303
|
+
"workspace.rename",
|
|
31304
|
+
"workspace.move",
|
|
31305
|
+
"workspace.move_block",
|
|
31306
|
+
"workspace.report_metadata",
|
|
31307
|
+
"workspace.close",
|
|
31308
|
+
"worktree.list",
|
|
31309
|
+
"worktree.create",
|
|
31310
|
+
"worktree.open",
|
|
31311
|
+
"worktree.remove",
|
|
31312
|
+
"tab.create",
|
|
31313
|
+
"tab.list",
|
|
31314
|
+
"tab.get",
|
|
31315
|
+
"tab.focus",
|
|
31316
|
+
"tab.rename",
|
|
31317
|
+
"tab.move",
|
|
31318
|
+
"tab.close",
|
|
31319
|
+
"agent.list",
|
|
31320
|
+
"agent.get",
|
|
31321
|
+
"agent.read",
|
|
31322
|
+
"agent.explain",
|
|
31323
|
+
"agent.send_keys",
|
|
31324
|
+
"agent.rename",
|
|
31325
|
+
"agent.view.set",
|
|
31326
|
+
"agent.view.clear",
|
|
31327
|
+
"agent.focus",
|
|
31328
|
+
"agent.start",
|
|
31329
|
+
"agent.prompt",
|
|
31330
|
+
"agent.wait",
|
|
31331
|
+
"pane.split",
|
|
31332
|
+
"pane.swap",
|
|
31333
|
+
"pane.move",
|
|
31334
|
+
"pane.zoom",
|
|
31335
|
+
"pane.layout",
|
|
31336
|
+
"pane.process_info",
|
|
31337
|
+
"layout.export",
|
|
31338
|
+
"layout.apply",
|
|
31339
|
+
"layout.set_split_ratio",
|
|
31340
|
+
"pane.neighbor",
|
|
31341
|
+
"pane.edges",
|
|
31342
|
+
"pane.focus_direction",
|
|
31343
|
+
"pane.resize",
|
|
31344
|
+
"pane.scroll",
|
|
31345
|
+
"pane.edit_scrollback",
|
|
31346
|
+
"pane.selection.read",
|
|
31347
|
+
"pane.copy_motion",
|
|
31348
|
+
"pane.copy_search",
|
|
31349
|
+
"pane.list",
|
|
31350
|
+
"pane.current",
|
|
31351
|
+
"pane.get",
|
|
31352
|
+
"pane.focus",
|
|
31353
|
+
"pane.input.set",
|
|
31354
|
+
"pane.link.activate",
|
|
31355
|
+
"pane.rename",
|
|
31356
|
+
"pane.send_text",
|
|
31357
|
+
"pane.send_keys",
|
|
31358
|
+
"pane.send_input",
|
|
31359
|
+
"pane.read",
|
|
31360
|
+
"pane.graphics.set",
|
|
31361
|
+
"pane.graphics.clear",
|
|
31362
|
+
"pane.graphics.info",
|
|
31363
|
+
"pane.report_agent",
|
|
31364
|
+
"pane.report_agent_session",
|
|
31365
|
+
"pane.report_metadata",
|
|
31366
|
+
"pane.clear_agent_authority",
|
|
31367
|
+
"pane.release_agent",
|
|
31368
|
+
"pane.close",
|
|
31369
|
+
"popup.close",
|
|
31370
|
+
"events.subscribe",
|
|
31371
|
+
"events.wait",
|
|
31372
|
+
"pane.wait_for_output",
|
|
31373
|
+
"integration.list",
|
|
31374
|
+
"integration.install",
|
|
31375
|
+
"integration.uninstall",
|
|
31376
|
+
"plugin.link",
|
|
31377
|
+
"plugin.list",
|
|
31378
|
+
"plugin.unlink",
|
|
31379
|
+
"plugin.enable",
|
|
31380
|
+
"plugin.disable",
|
|
31381
|
+
"plugin.action.list",
|
|
31382
|
+
"plugin.action.invoke",
|
|
31383
|
+
"plugin.log.list",
|
|
31384
|
+
"plugin.pane.open",
|
|
31385
|
+
"plugin.pane.focus",
|
|
31386
|
+
"plugin.pane.close"
|
|
31387
|
+
];
|
|
31388
|
+
|
|
31389
|
+
// src/herdr-streams.ts
|
|
31390
|
+
import { isAbsolute as isAbsolute3 } from "node:path";
|
|
31391
|
+
|
|
31392
|
+
// src/herdr-transport.ts
|
|
30479
31393
|
import net from "node:net";
|
|
30480
|
-
import { randomUUID as
|
|
30481
|
-
|
|
31394
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
31395
|
+
import { isAbsolute as isAbsolute2 } from "node:path";
|
|
31396
|
+
var HerdrError = class extends Error {
|
|
31397
|
+
constructor(code, message) {
|
|
31398
|
+
super(message);
|
|
31399
|
+
this.code = code;
|
|
31400
|
+
this.name = "HerdrError";
|
|
31401
|
+
}
|
|
31402
|
+
code;
|
|
31403
|
+
};
|
|
31404
|
+
function validateSocketPath(path) {
|
|
31405
|
+
if (!isAbsolute2(path) && !/^\\\\[.?]\\pipe\\[^\\]/.test(path))
|
|
31406
|
+
throw new TypeError("An absolute Herdr socket path or Windows named pipe is required");
|
|
31407
|
+
}
|
|
31408
|
+
function validateTimeout(value) {
|
|
31409
|
+
if (value !== null && (!Number.isFinite(value) || value <= 0 || value > 2147483647))
|
|
31410
|
+
throw new TypeError("timeoutMs must be a positive bounded duration or null");
|
|
31411
|
+
}
|
|
31412
|
+
function limit(value, name) {
|
|
31413
|
+
if (!Number.isSafeInteger(value) || value <= 0)
|
|
31414
|
+
throw new TypeError(`${name} must be a positive integer`);
|
|
31415
|
+
return value;
|
|
31416
|
+
}
|
|
31417
|
+
var JsonConnection = class {
|
|
31418
|
+
constructor(socketPath, options = {}) {
|
|
31419
|
+
this.options = options;
|
|
31420
|
+
validateSocketPath(socketPath);
|
|
31421
|
+
this.maxMessageBytes = limit(options.maxResponseBytes ?? 32 * 1024 * 1024, "maxResponseBytes");
|
|
31422
|
+
this.maxQueuedEvents = limit(options.maxQueuedEvents ?? 1024, "maxQueuedEvents");
|
|
31423
|
+
this.maxQueuedBytes = limit(options.maxQueuedBytes ?? this.maxMessageBytes, "maxQueuedBytes");
|
|
31424
|
+
if (options.signal?.aborted)
|
|
31425
|
+
throw new HerdrError("herdr_aborted", "Herdr operation was aborted");
|
|
31426
|
+
this.closed = new Promise((resolve13, reject) => {
|
|
31427
|
+
this.resolveClosed = resolve13;
|
|
31428
|
+
this.rejectClosed = reject;
|
|
31429
|
+
});
|
|
31430
|
+
void this.closed.catch(() => {
|
|
31431
|
+
});
|
|
31432
|
+
this.socket = net.createConnection(socketPath);
|
|
31433
|
+
this.socket.setEncoding("utf8");
|
|
31434
|
+
this.abort = () => this.fail(new HerdrError("herdr_aborted", "Herdr operation was aborted"), true);
|
|
31435
|
+
options.signal?.addEventListener("abort", this.abort, { once: true });
|
|
31436
|
+
this.socket.on(
|
|
31437
|
+
"error",
|
|
31438
|
+
(error) => this.fail(new HerdrError("herdr_unavailable", error.message))
|
|
31439
|
+
);
|
|
31440
|
+
this.socket.on("close", () => {
|
|
31441
|
+
this.ended = true;
|
|
31442
|
+
this.detachAbort();
|
|
31443
|
+
this.rejectWrites(this.error());
|
|
31444
|
+
if (this.failure || !this.explicitClose) this.rejectClosed(this.error());
|
|
31445
|
+
else this.resolveClosed();
|
|
31446
|
+
this.deliver();
|
|
31447
|
+
});
|
|
31448
|
+
this.socket.on("data", (data) => this.receive(data));
|
|
31449
|
+
}
|
|
31450
|
+
options;
|
|
31451
|
+
id = randomUUID5();
|
|
31452
|
+
closed;
|
|
31453
|
+
resolveClosed;
|
|
31454
|
+
rejectClosed;
|
|
31455
|
+
socket;
|
|
31456
|
+
buffer = "";
|
|
31457
|
+
queue = [];
|
|
31458
|
+
queuedBytes = 0;
|
|
31459
|
+
reader;
|
|
31460
|
+
writes = /* @__PURE__ */ new Set();
|
|
31461
|
+
ended = false;
|
|
31462
|
+
failure;
|
|
31463
|
+
explicitClose = false;
|
|
31464
|
+
maxMessageBytes;
|
|
31465
|
+
maxQueuedEvents;
|
|
31466
|
+
maxQueuedBytes;
|
|
31467
|
+
abort;
|
|
31468
|
+
error() {
|
|
31469
|
+
return this.failure ?? new HerdrError(
|
|
31470
|
+
"herdr_disconnected",
|
|
31471
|
+
"Herdr disconnected before acknowledgement or stream completion"
|
|
31472
|
+
);
|
|
31473
|
+
}
|
|
31474
|
+
detachAbort() {
|
|
31475
|
+
this.options.signal?.removeEventListener("abort", this.abort);
|
|
31476
|
+
}
|
|
31477
|
+
rejectWrites(error) {
|
|
31478
|
+
for (const reject of this.writes) reject(error);
|
|
31479
|
+
this.writes.clear();
|
|
31480
|
+
}
|
|
31481
|
+
fail(error, discard = false) {
|
|
31482
|
+
if (this.explicitClose) return;
|
|
31483
|
+
this.failure ??= error;
|
|
31484
|
+
this.ended = true;
|
|
31485
|
+
this.buffer = "";
|
|
31486
|
+
if (discard) {
|
|
31487
|
+
this.queue = [];
|
|
31488
|
+
this.queuedBytes = 0;
|
|
31489
|
+
}
|
|
31490
|
+
this.detachAbort();
|
|
31491
|
+
this.socket.destroy();
|
|
31492
|
+
this.rejectWrites(this.failure);
|
|
31493
|
+
this.deliver();
|
|
31494
|
+
}
|
|
31495
|
+
close() {
|
|
31496
|
+
this.explicitClose = true;
|
|
31497
|
+
this.ended = true;
|
|
31498
|
+
this.buffer = "";
|
|
31499
|
+
this.queue = [];
|
|
31500
|
+
this.queuedBytes = 0;
|
|
31501
|
+
this.detachAbort();
|
|
31502
|
+
this.socket.destroy();
|
|
31503
|
+
this.rejectWrites(this.error());
|
|
31504
|
+
this.deliver();
|
|
31505
|
+
}
|
|
31506
|
+
deliver() {
|
|
31507
|
+
if (!this.reader) return;
|
|
31508
|
+
const reader = this.reader;
|
|
31509
|
+
if (this.failure) {
|
|
31510
|
+
this.reader = void 0;
|
|
31511
|
+
reader.reject(this.failure);
|
|
31512
|
+
return;
|
|
31513
|
+
}
|
|
31514
|
+
const next = this.queue.shift();
|
|
31515
|
+
if (next) {
|
|
31516
|
+
this.reader = void 0;
|
|
31517
|
+
this.queuedBytes -= next.bytes;
|
|
31518
|
+
reader.resolve(next.value);
|
|
31519
|
+
} else if (this.ended) {
|
|
31520
|
+
this.reader = void 0;
|
|
31521
|
+
if (this.explicitClose) reader.resolve(void 0);
|
|
31522
|
+
else reader.reject(this.error());
|
|
31523
|
+
}
|
|
31524
|
+
}
|
|
31525
|
+
receive(data) {
|
|
31526
|
+
if (this.ended) return;
|
|
31527
|
+
this.buffer += data;
|
|
31528
|
+
let index;
|
|
31529
|
+
while ((index = this.buffer.indexOf("\n")) >= 0) {
|
|
31530
|
+
const line = this.buffer.slice(0, index);
|
|
31531
|
+
this.buffer = this.buffer.slice(index + 1);
|
|
31532
|
+
const bytes = Buffer.byteLength(line);
|
|
31533
|
+
if (bytes > this.maxMessageBytes)
|
|
31534
|
+
return this.fail(
|
|
31535
|
+
new HerdrError("herdr_response_too_large", "Herdr message exceeded maxResponseBytes"),
|
|
31536
|
+
true
|
|
31537
|
+
);
|
|
31538
|
+
let value;
|
|
31539
|
+
try {
|
|
31540
|
+
value = JSON.parse(line);
|
|
31541
|
+
} catch (error) {
|
|
31542
|
+
return this.fail(new HerdrError("herdr_invalid_response", String(error)), true);
|
|
31543
|
+
}
|
|
31544
|
+
if (value?.id !== void 0 && value.id !== this.id && !String(value.id).startsWith(this.id + ":"))
|
|
31545
|
+
continue;
|
|
31546
|
+
if (value?.error)
|
|
31547
|
+
return this.fail(new HerdrError(value.error.code, value.error.message), true);
|
|
31548
|
+
if (this.queue.length >= this.maxQueuedEvents || this.queuedBytes + bytes > this.maxQueuedBytes)
|
|
31549
|
+
return this.fail(
|
|
31550
|
+
new HerdrError(
|
|
31551
|
+
"herdr_stream_overflow",
|
|
31552
|
+
"Herdr consumer fell behind; stream closed without silently dropping events"
|
|
31553
|
+
),
|
|
31554
|
+
true
|
|
31555
|
+
);
|
|
31556
|
+
this.queue.push({ value, bytes });
|
|
31557
|
+
this.queuedBytes += bytes;
|
|
31558
|
+
this.deliver();
|
|
31559
|
+
}
|
|
31560
|
+
if (Buffer.byteLength(this.buffer) > this.maxMessageBytes)
|
|
31561
|
+
this.fail(
|
|
31562
|
+
new HerdrError("herdr_response_too_large", "Herdr message exceeded maxResponseBytes"),
|
|
31563
|
+
true
|
|
31564
|
+
);
|
|
31565
|
+
}
|
|
31566
|
+
read() {
|
|
31567
|
+
if (this.reader)
|
|
31568
|
+
return Promise.reject(new Error("Only one reader may consume a Herdr connection"));
|
|
31569
|
+
return new Promise((resolve13, reject) => {
|
|
31570
|
+
this.reader = { resolve: resolve13, reject };
|
|
31571
|
+
this.deliver();
|
|
31572
|
+
});
|
|
31573
|
+
}
|
|
31574
|
+
write(data) {
|
|
31575
|
+
if (this.ended) return Promise.reject(this.error());
|
|
31576
|
+
return new Promise((resolve13, reject) => {
|
|
31577
|
+
this.writes.add(reject);
|
|
31578
|
+
this.socket.write(data, (error) => {
|
|
31579
|
+
this.writes.delete(reject);
|
|
31580
|
+
if (error) {
|
|
31581
|
+
this.fail(new HerdrError("herdr_unavailable", error.message));
|
|
31582
|
+
reject(this.error());
|
|
31583
|
+
} else if (this.failure) reject(this.failure);
|
|
31584
|
+
else resolve13();
|
|
31585
|
+
});
|
|
31586
|
+
});
|
|
31587
|
+
}
|
|
31588
|
+
async within(timeoutMs, action) {
|
|
31589
|
+
validateTimeout(timeoutMs);
|
|
31590
|
+
const timer = timeoutMs === null ? void 0 : setTimeout(
|
|
31591
|
+
() => this.fail(
|
|
31592
|
+
new HerdrError(
|
|
31593
|
+
"herdr_timeout",
|
|
31594
|
+
"Herdr response timed out; delivery may be ambiguous"
|
|
31595
|
+
),
|
|
31596
|
+
true
|
|
31597
|
+
),
|
|
31598
|
+
timeoutMs
|
|
31599
|
+
);
|
|
31600
|
+
try {
|
|
31601
|
+
return await action();
|
|
31602
|
+
} finally {
|
|
31603
|
+
clearTimeout(timer);
|
|
31604
|
+
}
|
|
31605
|
+
}
|
|
31606
|
+
async start(method, params, timeoutMs) {
|
|
31607
|
+
return this.within(timeoutMs, async () => {
|
|
31608
|
+
await this.write(JSON.stringify({ id: this.id, method, params }) + "\n");
|
|
31609
|
+
const message = await this.read();
|
|
31610
|
+
if (!message || message.id !== this.id || !Object.hasOwn(message, "result"))
|
|
31611
|
+
throw new HerdrError("herdr_invalid_response", `${method}: missing correlated result`);
|
|
31612
|
+
return message.result;
|
|
31613
|
+
});
|
|
31614
|
+
}
|
|
31615
|
+
};
|
|
31616
|
+
async function socketRequest(socketPath, method, params, options = {}) {
|
|
31617
|
+
const timeoutMs = options.timeoutMs === void 0 ? 1e4 : options.timeoutMs;
|
|
31618
|
+
validateTimeout(timeoutMs);
|
|
31619
|
+
JSON.stringify(params);
|
|
31620
|
+
const connection = new JsonConnection(socketPath, options);
|
|
31621
|
+
try {
|
|
31622
|
+
return await connection.start(method, params, timeoutMs);
|
|
31623
|
+
} finally {
|
|
31624
|
+
connection.close();
|
|
31625
|
+
}
|
|
31626
|
+
}
|
|
31627
|
+
|
|
31628
|
+
// src/herdr-streams.ts
|
|
31629
|
+
var HerdrEventStream = class _HerdrEventStream {
|
|
31630
|
+
constructor(connection) {
|
|
31631
|
+
this.connection = connection;
|
|
31632
|
+
}
|
|
31633
|
+
connection;
|
|
31634
|
+
static async open(socketPath, subscriptions, options = {}) {
|
|
31635
|
+
const timeout = options.timeoutMs === void 0 ? 1e4 : options.timeoutMs;
|
|
31636
|
+
validateTimeout(timeout);
|
|
31637
|
+
JSON.stringify(subscriptions);
|
|
31638
|
+
const connection = new JsonConnection(socketPath, options);
|
|
31639
|
+
try {
|
|
31640
|
+
const result = await connection.start("events.subscribe", { subscriptions }, timeout);
|
|
31641
|
+
if (result?.type !== "subscription_started")
|
|
31642
|
+
throw new HerdrError("herdr_invalid_response", "Missing subscription acknowledgement");
|
|
31643
|
+
return new _HerdrEventStream(connection);
|
|
31644
|
+
} catch (error) {
|
|
31645
|
+
connection.close();
|
|
31646
|
+
throw error;
|
|
31647
|
+
}
|
|
31648
|
+
}
|
|
31649
|
+
get closed() {
|
|
31650
|
+
return this.connection.closed;
|
|
31651
|
+
}
|
|
31652
|
+
[Symbol.asyncIterator]() {
|
|
31653
|
+
return this;
|
|
31654
|
+
}
|
|
31655
|
+
async next() {
|
|
31656
|
+
const value = await this.connection.read();
|
|
31657
|
+
if (value === void 0) return { done: true, value: void 0 };
|
|
31658
|
+
if (!value || typeof value.event !== "string" || !Object.hasOwn(value, "data")) {
|
|
31659
|
+
const error = new HerdrError("herdr_invalid_response", "Expected a Herdr event envelope");
|
|
31660
|
+
this.connection.fail(error, true);
|
|
31661
|
+
throw error;
|
|
31662
|
+
}
|
|
31663
|
+
return { done: false, value };
|
|
31664
|
+
}
|
|
31665
|
+
close() {
|
|
31666
|
+
this.connection.close();
|
|
31667
|
+
}
|
|
31668
|
+
async return() {
|
|
31669
|
+
this.close();
|
|
31670
|
+
return { done: true, value: void 0 };
|
|
31671
|
+
}
|
|
31672
|
+
};
|
|
31673
|
+
var maxInlineBytes = 16 * 1024 * 1024;
|
|
31674
|
+
function integer(value, name, min = 0, max = Number.MAX_SAFE_INTEGER) {
|
|
31675
|
+
if (!Number.isSafeInteger(value) || value < min || value > max)
|
|
31676
|
+
throw new TypeError(`Invalid ${name}`);
|
|
31677
|
+
}
|
|
31678
|
+
function frameHeader(frame) {
|
|
31679
|
+
integer(frame.image_width, "image_width", 1, 4294967295);
|
|
31680
|
+
integer(frame.image_height, "image_height", 1, 4294967295);
|
|
31681
|
+
if (!["png", "rgb", "rgba", "bgra"].includes(frame.format))
|
|
31682
|
+
throw new TypeError("Invalid frame format");
|
|
31683
|
+
return {
|
|
31684
|
+
format: frame.format,
|
|
31685
|
+
image_width: frame.image_width,
|
|
31686
|
+
image_height: frame.image_height,
|
|
31687
|
+
...frame.placement ? { placement: frame.placement } : {}
|
|
31688
|
+
};
|
|
31689
|
+
}
|
|
31690
|
+
function encodeHeader(header) {
|
|
31691
|
+
const line = JSON.stringify(header) + "\n";
|
|
31692
|
+
if (Buffer.byteLength(line) > 64 * 1024) throw new TypeError("Graphics header exceeds 64 KiB");
|
|
31693
|
+
return line;
|
|
31694
|
+
}
|
|
31695
|
+
var HerdrGraphicsStream = class _HerdrGraphicsStream {
|
|
31696
|
+
constructor(connection) {
|
|
31697
|
+
this.connection = connection;
|
|
31698
|
+
}
|
|
31699
|
+
connection;
|
|
31700
|
+
busy = false;
|
|
31701
|
+
static async open(socketPath, params, options = {}) {
|
|
31702
|
+
const timeout = options.timeoutMs === void 0 ? 1e4 : options.timeoutMs;
|
|
31703
|
+
validateTimeout(timeout);
|
|
31704
|
+
JSON.stringify(params);
|
|
31705
|
+
const connection = new JsonConnection(socketPath, options);
|
|
31706
|
+
try {
|
|
31707
|
+
const result = await connection.start("pane.graphics.stream", params, timeout);
|
|
31708
|
+
if (result?.type !== "ok")
|
|
31709
|
+
throw new HerdrError("herdr_invalid_response", "Missing graphics stream acknowledgement");
|
|
31710
|
+
return new _HerdrGraphicsStream(connection);
|
|
31711
|
+
} catch (error) {
|
|
31712
|
+
connection.close();
|
|
31713
|
+
throw error;
|
|
31714
|
+
}
|
|
31715
|
+
}
|
|
31716
|
+
/** Resolves after local close; rejects on remote failure, cancellation, or disconnect. */
|
|
31717
|
+
get closed() {
|
|
31718
|
+
return this.connection.closed;
|
|
31719
|
+
}
|
|
31720
|
+
async exclusive(action) {
|
|
31721
|
+
if (this.busy)
|
|
31722
|
+
throw new HerdrError("herdr_stream_busy", "Await the previous frame before sending another");
|
|
31723
|
+
this.busy = true;
|
|
31724
|
+
try {
|
|
31725
|
+
return await action();
|
|
31726
|
+
} finally {
|
|
31727
|
+
this.busy = false;
|
|
31728
|
+
}
|
|
31729
|
+
}
|
|
31730
|
+
/** Resolves after local socket write, not a render ACK: inline success has no wire reply. */
|
|
31731
|
+
async frame(frame, data, timeoutMs = 3e4) {
|
|
31732
|
+
validateTimeout(timeoutMs);
|
|
31733
|
+
if (!data.byteLength || data.byteLength > maxInlineBytes)
|
|
31734
|
+
throw new TypeError("Inline graphics data must be 1 byte to 16 MiB");
|
|
31735
|
+
const header = encodeHeader({ ...frameHeader(frame), data_length: data.byteLength });
|
|
31736
|
+
return this.exclusive(async () => {
|
|
31737
|
+
const bytes = Buffer.from(data);
|
|
31738
|
+
await this.connection.within(timeoutMs, async () => {
|
|
31739
|
+
await this.connection.write(header);
|
|
31740
|
+
await this.connection.write(bytes);
|
|
31741
|
+
});
|
|
31742
|
+
});
|
|
31743
|
+
}
|
|
31744
|
+
/** Keep the immutable source file until this method acknowledges its sequence and revision. */
|
|
31745
|
+
async fileFrame(frame, timeoutMs = 3e4) {
|
|
31746
|
+
validateTimeout(timeoutMs);
|
|
31747
|
+
if (!["rgba", "bgra"].includes(frame.format) || !isAbsolute3(frame.file.path))
|
|
31748
|
+
throw new TypeError("File frames require rgba/bgra and an absolute source path");
|
|
31749
|
+
integer(frame.sequence, "sequence");
|
|
31750
|
+
integer(frame.revision, "revision");
|
|
31751
|
+
const { sequence, revision } = frame;
|
|
31752
|
+
const header = encodeHeader({
|
|
31753
|
+
...frameHeader(frame),
|
|
31754
|
+
file: { path: frame.file.path },
|
|
31755
|
+
sequence,
|
|
31756
|
+
revision
|
|
31757
|
+
});
|
|
31758
|
+
return this.exclusive(
|
|
31759
|
+
() => this.connection.within(timeoutMs, async () => {
|
|
31760
|
+
await this.connection.write(header);
|
|
31761
|
+
const message = await this.connection.read();
|
|
31762
|
+
const result = message?.result;
|
|
31763
|
+
if (message?.id !== `${this.connection.id}:file:${sequence}` || result?.type !== "pane_graphics_frame_ack" || result.sequence !== sequence || result.revision !== revision) {
|
|
31764
|
+
const error = new HerdrError(
|
|
31765
|
+
"herdr_invalid_response",
|
|
31766
|
+
"Graphics acknowledgement did not match the file frame"
|
|
31767
|
+
);
|
|
31768
|
+
this.connection.fail(error, true);
|
|
31769
|
+
throw error;
|
|
31770
|
+
}
|
|
31771
|
+
return result;
|
|
31772
|
+
})
|
|
31773
|
+
);
|
|
31774
|
+
}
|
|
31775
|
+
close() {
|
|
31776
|
+
this.connection.close();
|
|
31777
|
+
}
|
|
31778
|
+
};
|
|
31779
|
+
|
|
31780
|
+
// src/herdr-sdk.ts
|
|
31781
|
+
function deadline(method, params) {
|
|
31782
|
+
if (method === "agent.start") return (params.timeout_ms ?? 3e4) + 5e3;
|
|
31783
|
+
const wait = method === "agent.prompt" ? params.wait : ["agent.wait", "events.wait", "pane.wait_for_output"].includes(method) ? params : void 0;
|
|
31784
|
+
if (wait) return wait.timeout_ms == null ? null : wait.timeout_ms + 5e3;
|
|
31785
|
+
return 1e4;
|
|
31786
|
+
}
|
|
31787
|
+
var HerdrClient = class _HerdrClient {
|
|
30482
31788
|
constructor(socketPath) {
|
|
30483
31789
|
this.socketPath = socketPath;
|
|
31790
|
+
validateSocketPath(socketPath);
|
|
30484
31791
|
}
|
|
30485
31792
|
socketPath;
|
|
31793
|
+
static fromEnv(env = process.env) {
|
|
31794
|
+
if (env.HERDR_ENV !== "1" || !env.HERDR_SOCKET_PATH)
|
|
31795
|
+
throw new Error("Run inside Herdr or supply an explicitly selected socket path");
|
|
31796
|
+
return new _HerdrClient(env.HERDR_SOCKET_PATH);
|
|
31797
|
+
}
|
|
31798
|
+
/** Escape hatch for future one-shot methods. Streams require their dedicated transports. */
|
|
30486
31799
|
call(method, params = {}, timeoutMs = 1e4) {
|
|
30487
|
-
|
|
30488
|
-
|
|
30489
|
-
|
|
30490
|
-
let settled2 = false;
|
|
30491
|
-
const socket = net.createConnection(this.socketPath);
|
|
30492
|
-
const finish = (error, result) => {
|
|
30493
|
-
if (settled2) return;
|
|
30494
|
-
settled2 = true;
|
|
30495
|
-
clearTimeout(timer);
|
|
30496
|
-
socket.destroy();
|
|
30497
|
-
error ? reject(error) : resolve12(result);
|
|
30498
|
-
};
|
|
30499
|
-
const timer = setTimeout(
|
|
30500
|
-
() => finish(
|
|
30501
|
-
new AppError(
|
|
30502
|
-
"herdr_timeout",
|
|
30503
|
-
`${method}: response timed out; delivery may be ambiguous`,
|
|
30504
|
-
503
|
|
30505
|
-
)
|
|
30506
|
-
),
|
|
30507
|
-
timeoutMs
|
|
31800
|
+
if (method === "events.subscribe" || method === "pane.graphics.stream")
|
|
31801
|
+
return Promise.reject(
|
|
31802
|
+
new TypeError("Use subscribe(), graphicsStream(), or api for a streaming method")
|
|
30508
31803
|
);
|
|
30509
|
-
|
|
30510
|
-
|
|
30511
|
-
|
|
30512
|
-
|
|
30513
|
-
|
|
30514
|
-
|
|
30515
|
-
|
|
30516
|
-
|
|
30517
|
-
|
|
31804
|
+
return socketRequest(this.socketPath, method, params, { timeoutMs });
|
|
31805
|
+
}
|
|
31806
|
+
/** All schema-defined one-shot methods with exact typed parameter objects. */
|
|
31807
|
+
request(method, ...args2) {
|
|
31808
|
+
const [params = {}, options = {}] = args2;
|
|
31809
|
+
if (!HERDR_METHODS.includes(method) || method === "events.subscribe")
|
|
31810
|
+
return Promise.reject(
|
|
31811
|
+
new TypeError(
|
|
31812
|
+
"Unknown one-shot method; use call() for extensions or subscribe() for events"
|
|
30518
31813
|
)
|
|
30519
31814
|
);
|
|
30520
|
-
|
|
30521
|
-
|
|
30522
|
-
|
|
30523
|
-
return finish(new Error("Herdr response exceeded 8 MiB"));
|
|
30524
|
-
let index;
|
|
30525
|
-
while ((index = buffer.indexOf("\n")) >= 0) {
|
|
30526
|
-
const line = buffer.slice(0, index);
|
|
30527
|
-
buffer = buffer.slice(index + 1);
|
|
30528
|
-
try {
|
|
30529
|
-
const v = JSON.parse(line);
|
|
30530
|
-
if (v.id !== id) continue;
|
|
30531
|
-
if (v.error) finish(new AppError(v.error.code, v.error.message, 502));
|
|
30532
|
-
else finish(void 0, v.result);
|
|
30533
|
-
} catch (e) {
|
|
30534
|
-
finish(e);
|
|
30535
|
-
}
|
|
30536
|
-
}
|
|
30537
|
-
});
|
|
31815
|
+
return socketRequest(this.socketPath, method, params, {
|
|
31816
|
+
...options,
|
|
31817
|
+
timeoutMs: options.timeoutMs === void 0 ? deadline(method, params) : options.timeoutMs
|
|
30538
31818
|
});
|
|
30539
31819
|
}
|
|
31820
|
+
subscribe(subscriptions, options) {
|
|
31821
|
+
return HerdrEventStream.open(this.socketPath, subscriptions, options);
|
|
31822
|
+
}
|
|
31823
|
+
graphicsStream(params, options) {
|
|
31824
|
+
return HerdrGraphicsStream.open(this.socketPath, params, options);
|
|
31825
|
+
}
|
|
31826
|
+
/** One-to-one method names, including streams, for discovery and generated agent code. */
|
|
31827
|
+
api = Object.freeze(
|
|
31828
|
+
Object.fromEntries([
|
|
31829
|
+
...HERDR_METHODS.map((method) => [
|
|
31830
|
+
method,
|
|
31831
|
+
method === "events.subscribe" ? (params, options) => this.subscribe(params.subscriptions, options) : (params = {}, options) => this.request(method, params, options)
|
|
31832
|
+
]),
|
|
31833
|
+
[
|
|
31834
|
+
"pane.graphics.stream",
|
|
31835
|
+
(params, options) => this.graphicsStream(params, options)
|
|
31836
|
+
]
|
|
31837
|
+
])
|
|
31838
|
+
);
|
|
31839
|
+
workspace = {
|
|
31840
|
+
list: () => this.call("workspace.list"),
|
|
31841
|
+
create: (options) => this.call("workspace.create", {
|
|
31842
|
+
focus: false,
|
|
31843
|
+
...options
|
|
31844
|
+
})
|
|
31845
|
+
};
|
|
31846
|
+
tab = {
|
|
31847
|
+
create: (workspaceId, options) => this.call("tab.create", {
|
|
31848
|
+
focus: false,
|
|
31849
|
+
...options,
|
|
31850
|
+
workspace_id: workspaceId
|
|
31851
|
+
}),
|
|
31852
|
+
list: (workspaceId) => this.call("tab.list", { workspace_id: workspaceId }),
|
|
31853
|
+
close: (tabId) => this.call("tab.close", { tab_id: tabId })
|
|
31854
|
+
};
|
|
31855
|
+
pane = {
|
|
31856
|
+
list: (workspaceId) => this.call("pane.list", { workspace_id: workspaceId }),
|
|
31857
|
+
layout: (paneId) => this.call("pane.layout", { pane_id: paneId }),
|
|
31858
|
+
split: (paneId, options) => this.call("pane.split", { focus: false, ...options, target_pane_id: paneId }),
|
|
31859
|
+
read: (paneId, lines = 120) => this.call("pane.read", {
|
|
31860
|
+
pane_id: paneId,
|
|
31861
|
+
source: "recent_unwrapped",
|
|
31862
|
+
format: "text",
|
|
31863
|
+
lines
|
|
31864
|
+
}),
|
|
31865
|
+
sendText: (paneId, text2) => this.call("pane.send_text", { pane_id: paneId, text: text2 }),
|
|
31866
|
+
sendKeys: (paneId, keys) => this.call("pane.send_keys", { pane_id: paneId, keys }),
|
|
31867
|
+
close: (paneId) => this.call("pane.close", { pane_id: paneId })
|
|
31868
|
+
};
|
|
31869
|
+
agent = {
|
|
31870
|
+
start: (paneId, name, kind, args2 = [], timeoutMs = 3e4) => this.call(
|
|
31871
|
+
"agent.start",
|
|
31872
|
+
{ pane_id: paneId, name, kind, args: args2, timeout_ms: timeoutMs },
|
|
31873
|
+
timeoutMs + 5e3
|
|
31874
|
+
),
|
|
31875
|
+
get: (target) => this.call("agent.get", { target }),
|
|
31876
|
+
prompt: (target, text2, wait) => this.call(
|
|
31877
|
+
"agent.prompt",
|
|
31878
|
+
{ target, text: text2, ...wait ? { wait } : {} },
|
|
31879
|
+
wait ? wait.timeout_ms + 5e3 : 1e4
|
|
31880
|
+
),
|
|
31881
|
+
wait: (target, options) => this.call("agent.wait", { target, ...options }, options.timeout_ms + 5e3),
|
|
31882
|
+
sendKeys: (target, keys) => this.call("agent.send_keys", { target, keys })
|
|
31883
|
+
};
|
|
31884
|
+
};
|
|
31885
|
+
|
|
31886
|
+
// src/herdr.ts
|
|
31887
|
+
var Herdr = class extends HerdrClient {
|
|
31888
|
+
async call(method, params = {}, timeoutMs = 1e4) {
|
|
31889
|
+
try {
|
|
31890
|
+
return await super.call(method, params, timeoutMs);
|
|
31891
|
+
} catch (error) {
|
|
31892
|
+
if (error instanceof HerdrError)
|
|
31893
|
+
throw new AppError(error.code, error.message, error.code.startsWith("herdr_") ? 503 : 502);
|
|
31894
|
+
throw error;
|
|
31895
|
+
}
|
|
31896
|
+
}
|
|
30540
31897
|
};
|
|
30541
31898
|
|
|
30542
31899
|
// src/service.ts
|
|
@@ -30551,6 +31908,7 @@ var Service = class {
|
|
|
30551
31908
|
port;
|
|
30552
31909
|
orchestration = new Orchestration(this);
|
|
30553
31910
|
continuation = new Continuation(this);
|
|
31911
|
+
cleanup = new Cleanup(this);
|
|
30554
31912
|
project(id) {
|
|
30555
31913
|
const p = this.store.get("project", id);
|
|
30556
31914
|
if (!p) throw new AppError("not_found", "Project not found", 404);
|
|
@@ -30591,6 +31949,7 @@ var Service = class {
|
|
|
30591
31949
|
);
|
|
30592
31950
|
return {
|
|
30593
31951
|
project,
|
|
31952
|
+
cleanupPolicy: this.cleanup.policy(projectId),
|
|
30594
31953
|
lead: this.publicLead(projectId),
|
|
30595
31954
|
...this.orchestration.board(projectId),
|
|
30596
31955
|
tasks,
|
|
@@ -30613,7 +31972,7 @@ var Service = class {
|
|
|
30613
31972
|
const existing = this.store.all("question").find((q2) => q2.taskId === task.id && !q2.answeredAt && q2.text === text2);
|
|
30614
31973
|
if (existing) return existing;
|
|
30615
31974
|
const q = {
|
|
30616
|
-
id:
|
|
31975
|
+
id: randomUUID6(),
|
|
30617
31976
|
projectId: task.projectId,
|
|
30618
31977
|
taskId: task.id,
|
|
30619
31978
|
text: text2,
|
|
@@ -30641,6 +32000,7 @@ var Service = class {
|
|
|
30641
32000
|
return result;
|
|
30642
32001
|
}
|
|
30643
32002
|
async invoke(action, raw = {}) {
|
|
32003
|
+
if (action.startsWith("cleanup.")) return this.cleanup.invoke(action, raw);
|
|
30644
32004
|
if (/^(lead\.wait|checkpoint\.|usage\.|adapter\.)/.test(action))
|
|
30645
32005
|
return this.continuation.invoke(action, raw);
|
|
30646
32006
|
if (/^(outcome\.|plan\.|profile\.|limits\.|strategy\.|board\.)/.test(action))
|
|
@@ -30659,10 +32019,10 @@ var Service = class {
|
|
|
30659
32019
|
agentArgs: external_exports.record(kindSchema, external_exports.array(external_exports.string())).default({}),
|
|
30660
32020
|
trustAgyWorkspaces: external_exports.boolean().default(false)
|
|
30661
32021
|
}).parse(raw);
|
|
30662
|
-
if (!
|
|
32022
|
+
if (!isAbsolute4(input.root) || !isAbsolute4(input.socketPath))
|
|
30663
32023
|
throw new AppError("absolute_paths", "root and socketPath must be absolute");
|
|
30664
|
-
const root =
|
|
30665
|
-
if (!
|
|
32024
|
+
const root = realpathSync4(input.root);
|
|
32025
|
+
if (!statSync4(root).isDirectory())
|
|
30666
32026
|
throw new AppError("root", "Project root is not a directory");
|
|
30667
32027
|
const existing = this.store.all("project").find((p2) => p2.socketPath === input.socketPath && p2.workspaceId === input.workspaceId);
|
|
30668
32028
|
if (existing) {
|
|
@@ -30673,11 +32033,13 @@ var Service = class {
|
|
|
30673
32033
|
409
|
|
30674
32034
|
);
|
|
30675
32035
|
}
|
|
30676
|
-
const p = { ...input, root, id:
|
|
32036
|
+
const p = { ...input, root, id: randomUUID6(), createdAt: now() };
|
|
30677
32037
|
const h = this.port(p);
|
|
30678
32038
|
await h.call("ping");
|
|
30679
32039
|
await h.call("workspace.get", { workspace_id: p.workspaceId });
|
|
30680
32040
|
return this.store.transaction(() => {
|
|
32041
|
+
for (const task of this.store.all("task"))
|
|
32042
|
+
if (task.worktree && inside(task.worktree.path, root)) this.cleanup.assertMutable(task);
|
|
30681
32043
|
const concurrent = this.store.all("project").find((v) => v.socketPath === p.socketPath && v.workspaceId === p.workspaceId);
|
|
30682
32044
|
if (concurrent) {
|
|
30683
32045
|
if (concurrent.root === root) return concurrent;
|
|
@@ -30820,6 +32182,7 @@ var Service = class {
|
|
|
30820
32182
|
const c = this.guard(raw.lease), t = this.task(external_exports.string().parse(raw.taskId)), key = external_exports.string().min(1).parse(raw.key);
|
|
30821
32183
|
if (c.projectId !== t.projectId)
|
|
30822
32184
|
throw new AppError("project_mismatch", "Task and lease differ");
|
|
32185
|
+
this.cleanup.assertMutable(t);
|
|
30823
32186
|
const cached = this.store.get("idempotency", t.projectId + ":retry:" + key);
|
|
30824
32187
|
if (cached)
|
|
30825
32188
|
return this.store.transaction(
|
|
@@ -30855,6 +32218,7 @@ var Service = class {
|
|
|
30855
32218
|
}
|
|
30856
32219
|
this.guard(raw.lease);
|
|
30857
32220
|
const current = this.task(t.id);
|
|
32221
|
+
this.cleanup.assertMutable(current);
|
|
30858
32222
|
if (current.runId !== t.runId || !["failed", "cancelled"].includes(current.status))
|
|
30859
32223
|
throw new AppError(
|
|
30860
32224
|
"retry_state",
|
|
@@ -30902,29 +32266,55 @@ var Service = class {
|
|
|
30902
32266
|
"No task prompt was attempted. Inspect the workspace and reconcile creation as not-delivered."
|
|
30903
32267
|
);
|
|
30904
32268
|
const project = this.project(t.projectId);
|
|
30905
|
-
|
|
30906
|
-
|
|
30907
|
-
(
|
|
30908
|
-
|
|
30909
|
-
|
|
30910
|
-
|
|
30911
|
-
|
|
30912
|
-
|
|
30913
|
-
|
|
32269
|
+
if (r.creation?.mode === "pane") {
|
|
32270
|
+
const all = (await h.call("pane.list", { workspace_id: project.workspaceId })).panes;
|
|
32271
|
+
if (!Array.isArray(all) || !r.creation.beforePaneIds)
|
|
32272
|
+
throw new AppError(
|
|
32273
|
+
"identity_changed",
|
|
32274
|
+
"Missing split membership; inspect the workspace",
|
|
32275
|
+
409
|
|
32276
|
+
);
|
|
32277
|
+
const added = all.filter(
|
|
32278
|
+
(pane) => !r.creation.beforePaneIds.includes(pane.pane_id)
|
|
30914
32279
|
);
|
|
30915
|
-
|
|
30916
|
-
|
|
30917
|
-
if (panes.length !== 1 || panes[0].agent || panes[0].launch_pending || panes[0].workspace_id !== project.workspaceId || panes[0].cwd !== t.cwd)
|
|
32280
|
+
const panes = added.filter((pane) => pane.tab_id === r.creation.tabId);
|
|
32281
|
+
if (added.length !== panes.length || panes.length > 1 || panes.length === 1 && (panes[0].agent || panes[0].launch_pending || panes[0].workspace_id !== project.workspaceId || panes[0].cwd !== t.cwd))
|
|
30918
32282
|
throw new AppError(
|
|
30919
32283
|
"identity_changed",
|
|
30920
|
-
"
|
|
32284
|
+
"Interrupted split is ambiguous or no longer an untouched shell; inspect it before recovery",
|
|
30921
32285
|
409
|
|
30922
32286
|
);
|
|
30923
|
-
|
|
30924
|
-
|
|
30925
|
-
|
|
30926
|
-
|
|
30927
|
-
|
|
32287
|
+
if (panes.length === 1)
|
|
32288
|
+
Object.assign(r, {
|
|
32289
|
+
paneId: panes[0].pane_id,
|
|
32290
|
+
terminalId: panes[0].terminal_id,
|
|
32291
|
+
tabId: panes[0].tab_id
|
|
32292
|
+
});
|
|
32293
|
+
} else {
|
|
32294
|
+
const tabs = (await h.call("tab.list", { workspace_id: project.workspaceId })).tabs ?? [];
|
|
32295
|
+
const matches = tabs.filter(
|
|
32296
|
+
(tab) => tab.workspace_id === project.workspaceId && tab.label === r.agentName
|
|
32297
|
+
);
|
|
32298
|
+
if (matches.length > 1)
|
|
32299
|
+
throw new AppError(
|
|
32300
|
+
"identity_changed",
|
|
32301
|
+
"Multiple tabs match the interrupted creation; manual inspection is required",
|
|
32302
|
+
409
|
|
32303
|
+
);
|
|
32304
|
+
if (matches.length === 1) {
|
|
32305
|
+
const panes = ((await h.call("pane.list", { workspace_id: project.workspaceId })).panes ?? []).filter((pane) => pane.tab_id === matches[0].tab_id);
|
|
32306
|
+
if (panes.length !== 1 || panes[0].agent || panes[0].launch_pending || panes[0].workspace_id !== project.workspaceId || panes[0].cwd !== t.cwd)
|
|
32307
|
+
throw new AppError(
|
|
32308
|
+
"identity_changed",
|
|
32309
|
+
"The orphan tab is no longer an untouched shell; inspect it before recovery",
|
|
32310
|
+
409
|
|
32311
|
+
);
|
|
32312
|
+
Object.assign(r, {
|
|
32313
|
+
paneId: panes[0].pane_id,
|
|
32314
|
+
terminalId: panes[0].terminal_id,
|
|
32315
|
+
tabId: panes[0].tab_id
|
|
32316
|
+
});
|
|
32317
|
+
}
|
|
30928
32318
|
}
|
|
30929
32319
|
} else {
|
|
30930
32320
|
const a = (await h.call("agent.get", { target: r.paneId })).agent;
|
|
@@ -30968,7 +32358,7 @@ var Service = class {
|
|
|
30968
32358
|
case "decision.record": {
|
|
30969
32359
|
const c = this.guard(raw.lease), text2 = external_exports.string().min(1).parse(raw.text), rationale = external_exports.string().default("").parse(raw.rationale);
|
|
30970
32360
|
const d = {
|
|
30971
|
-
id:
|
|
32361
|
+
id: randomUUID6(),
|
|
30972
32362
|
projectId: c.projectId,
|
|
30973
32363
|
text: text2,
|
|
30974
32364
|
rationale,
|
|
@@ -31022,12 +32412,15 @@ var Service = class {
|
|
|
31022
32412
|
checks: external_exports.array(checkSchema).min(1).optional()
|
|
31023
32413
|
}).parse(raw);
|
|
31024
32414
|
return this.store.transaction(() => {
|
|
31025
|
-
|
|
32415
|
+
let t = this.task(i.taskId);
|
|
32416
|
+
this.cleanup.assertMutable(t);
|
|
31026
32417
|
if (t.projectId !== c.projectId)
|
|
31027
32418
|
throw new AppError("project_mismatch", "Task and lease differ");
|
|
31028
32419
|
return this.idempotent(c.projectId, "control:" + i.key, i, () => {
|
|
31029
32420
|
if (terminalStates.has(t.status))
|
|
31030
32421
|
throw new AppError("task_finished", "Task is already finished", 409);
|
|
32422
|
+
if (t.runId && this.store.get("run", t.runId)?.cleanup?.state === "closed")
|
|
32423
|
+
t = this.updateTask(t, { runId: void 0 });
|
|
31031
32424
|
if (["redirect", "reply"].includes(i.type) && !i.text?.trim())
|
|
31032
32425
|
throw new AppError("text_required", "Provide the new instructions or answer");
|
|
31033
32426
|
if (i.type === "keys" && !i.keys)
|
|
@@ -31048,7 +32441,7 @@ var Service = class {
|
|
|
31048
32441
|
for (const check of i.checks) if (check.type === "file") safePath(t.cwd, check.path);
|
|
31049
32442
|
}
|
|
31050
32443
|
const op = {
|
|
31051
|
-
id:
|
|
32444
|
+
id: randomUUID6(),
|
|
31052
32445
|
projectId: t.projectId,
|
|
31053
32446
|
taskId: t.id,
|
|
31054
32447
|
type: i.type,
|
|
@@ -31105,8 +32498,11 @@ var Service = class {
|
|
|
31105
32498
|
const a = assignmentSchema.parse(rawAssignment);
|
|
31106
32499
|
if (c.projectId !== a.projectId)
|
|
31107
32500
|
throw new AppError("project_mismatch", "Lease does not match assignment");
|
|
31108
|
-
const p = this.project(a.projectId), cwd =
|
|
32501
|
+
const p = this.project(a.projectId), cwd = realpathSync4(a.cwd ?? p.root);
|
|
31109
32502
|
const parent = a.parentId ? this.task(a.parentId) : void 0;
|
|
32503
|
+
if (parent) this.cleanup.assertMutable(parent);
|
|
32504
|
+
for (const task of this.store.all("task"))
|
|
32505
|
+
if (task.worktree && inside(task.worktree.path, cwd)) this.cleanup.assertMutable(task);
|
|
31110
32506
|
const inheritedWorktree = parent?.projectId === p.id && parent.worktree?.state === "ready" && parent.cwd === cwd;
|
|
31111
32507
|
if (!inside(p.root, cwd) && !inheritedWorktree)
|
|
31112
32508
|
throw new AppError(
|
|
@@ -31114,7 +32510,7 @@ var Service = class {
|
|
|
31114
32510
|
"Task directory must be within the registered root. Register external worktrees as projects."
|
|
31115
32511
|
);
|
|
31116
32512
|
for (const path of a.ownership) {
|
|
31117
|
-
if (a.execution?.mode === "worktree" &&
|
|
32513
|
+
if (a.execution?.mode === "worktree" && isAbsolute4(path))
|
|
31118
32514
|
throw new AppError(
|
|
31119
32515
|
"worktree_path",
|
|
31120
32516
|
"Worktree ownership paths must be relative to the task directory"
|
|
@@ -31128,7 +32524,7 @@ var Service = class {
|
|
|
31128
32524
|
}
|
|
31129
32525
|
for (const check of a.checks)
|
|
31130
32526
|
if (check.type === "file") {
|
|
31131
|
-
if (a.execution?.mode === "worktree" &&
|
|
32527
|
+
if (a.execution?.mode === "worktree" && isAbsolute4(check.path))
|
|
31132
32528
|
throw new AppError(
|
|
31133
32529
|
"worktree_path",
|
|
31134
32530
|
"Worktree file checks must be relative to the task directory"
|
|
@@ -31137,6 +32533,12 @@ var Service = class {
|
|
|
31137
32533
|
}
|
|
31138
32534
|
for (const id of a.dependencies) {
|
|
31139
32535
|
const d = this.task(id);
|
|
32536
|
+
if (this.cleanup.active(d.id))
|
|
32537
|
+
throw new AppError(
|
|
32538
|
+
"cleanup_busy",
|
|
32539
|
+
"Dependency is being cleaned up; retry after it finishes",
|
|
32540
|
+
409
|
|
32541
|
+
);
|
|
31140
32542
|
if (d.projectId !== a.projectId)
|
|
31141
32543
|
throw new AppError("dependency_scope", "Dependencies must belong to the same project");
|
|
31142
32544
|
}
|
|
@@ -31146,7 +32548,7 @@ var Service = class {
|
|
|
31146
32548
|
const task = {
|
|
31147
32549
|
...fields,
|
|
31148
32550
|
cwd,
|
|
31149
|
-
id:
|
|
32551
|
+
id: randomUUID6(),
|
|
31150
32552
|
status: a.deferStart ? "paused" : "queued",
|
|
31151
32553
|
revision: 1,
|
|
31152
32554
|
attempt: 0,
|
|
@@ -31168,6 +32570,7 @@ var Service = class {
|
|
|
31168
32570
|
}
|
|
31169
32571
|
workerGuard(taskId, token, revision) {
|
|
31170
32572
|
const t = this.task(taskId), r = t.runId ? this.store.get("run", t.runId) : void 0;
|
|
32573
|
+
this.cleanup.assertMutable(t);
|
|
31171
32574
|
if (!r || hash(token) !== r.tokenHash)
|
|
31172
32575
|
throw new AppError("worker_auth", "Invalid or obsolete worker token", 401);
|
|
31173
32576
|
if (terminalStates.has(t.status) || revision !== t.revision || r.phase === "stopped")
|
|
@@ -31185,6 +32588,7 @@ var Service = class {
|
|
|
31185
32588
|
}).parse(raw);
|
|
31186
32589
|
return this.store.transaction(() => {
|
|
31187
32590
|
const t = this.task(taskId), r = t.runId ? this.store.get("run", t.runId) : void 0;
|
|
32591
|
+
this.cleanup.assertMutable(t);
|
|
31188
32592
|
if (!r || hash(token) !== r.tokenHash)
|
|
31189
32593
|
throw new AppError("worker_auth", "Invalid or obsolete worker token", 401);
|
|
31190
32594
|
if (terminalStates.has(t.status) || i.revision !== t.revision)
|
|
@@ -31254,6 +32658,52 @@ var Service = class {
|
|
|
31254
32658
|
}
|
|
31255
32659
|
};
|
|
31256
32660
|
|
|
32661
|
+
// src/worker-layout.ts
|
|
32662
|
+
async function planWorkerPane(h, workspaceId, runs) {
|
|
32663
|
+
const panes = (await h.call("pane.list", { workspace_id: workspaceId })).panes;
|
|
32664
|
+
if (!Array.isArray(panes)) throw new Error("Herdr did not return workspace panes");
|
|
32665
|
+
const candidates = runs.filter(
|
|
32666
|
+
(r) => r.terminalScope === "pane" && r.paneId && r.tabId && !r.cleanup && r.phase !== "creating"
|
|
32667
|
+
);
|
|
32668
|
+
for (const tabId of new Set(candidates.map((r) => r.tabId))) {
|
|
32669
|
+
if (runs.some((r) => r.phase === "creating" && r.creation?.tabId === tabId)) continue;
|
|
32670
|
+
const members = panes.filter((p) => p.workspace_id === workspaceId && p.tab_id === tabId);
|
|
32671
|
+
if (!members.length || members.length >= 4) continue;
|
|
32672
|
+
if (!members.every(
|
|
32673
|
+
(p) => candidates.some(
|
|
32674
|
+
(r) => r.paneId === p.pane_id && r.terminalId === p.terminal_id && r.tabId === p.tab_id
|
|
32675
|
+
)
|
|
32676
|
+
))
|
|
32677
|
+
continue;
|
|
32678
|
+
const layout = (await h.call("pane.layout", { pane_id: members[0].pane_id })).layout;
|
|
32679
|
+
if (layout?.workspace_id !== workspaceId || layout.tab_id !== tabId || !Array.isArray(layout.panes))
|
|
32680
|
+
continue;
|
|
32681
|
+
const choices = layout.panes.flatMap((p) => {
|
|
32682
|
+
if (!members.some((m) => m.pane_id === p.pane_id)) return [];
|
|
32683
|
+
const { width, height } = p.rect;
|
|
32684
|
+
const right = width >= 121 && height >= 12;
|
|
32685
|
+
const down = height >= 25 && width >= 60;
|
|
32686
|
+
if (!right && !down) return [];
|
|
32687
|
+
return [
|
|
32688
|
+
{
|
|
32689
|
+
paneId: p.pane_id,
|
|
32690
|
+
area: width * height,
|
|
32691
|
+
direction: right && (!down || width >= height * 3) ? "right" : "down"
|
|
32692
|
+
}
|
|
32693
|
+
];
|
|
32694
|
+
}).sort((a, b) => b.area - a.area);
|
|
32695
|
+
if (choices.length)
|
|
32696
|
+
return {
|
|
32697
|
+
mode: "pane",
|
|
32698
|
+
tabId,
|
|
32699
|
+
targetPaneId: choices[0].paneId,
|
|
32700
|
+
direction: choices[0].direction,
|
|
32701
|
+
beforePaneIds: panes.map((p) => p.pane_id)
|
|
32702
|
+
};
|
|
32703
|
+
}
|
|
32704
|
+
return { mode: "tab" };
|
|
32705
|
+
}
|
|
32706
|
+
|
|
31257
32707
|
// src/prompts.ts
|
|
31258
32708
|
var leadContract = `Own the user's outcome through verified completion. Establish a persistent outcome with objective, explicit scope, observable criteria and required evidence before dispatch. Use criteria suited to the work: executable behavior checks for software, corroborated sources and traceable findings for research, reproducible calculations for analysis, and documented alternatives, tradeoffs and unresolved disagreements for decisions. Ask for clarification only when the evidence cannot resolve a necessary ambiguity.
|
|
31259
32709
|
Choose exact available model profiles and an appropriate bounded collaboration strategy. Preserve a user's explicit model choice; if unavailable, report it and make any proposed fallback explicit. Stage independent council, debate or competing-proposal participants with deferStart: true, then use strategy_create to establish common evaluation criteria and release their initial assessments. Do not contaminate initial assessments by sharing another participant's conclusions. Use verified participant results in synthesis and preserve material disagreements. For later debate rounds, obtain fresh verified participant revisions before contributing their rebuttals.
|
|
@@ -31272,39 +32722,39 @@ var strategyInstructions = {
|
|
|
31272
32722
|
|
|
31273
32723
|
// src/agy-trust.ts
|
|
31274
32724
|
import {
|
|
31275
|
-
existsSync as
|
|
31276
|
-
mkdirSync as
|
|
31277
|
-
openSync,
|
|
31278
|
-
closeSync,
|
|
31279
|
-
readFileSync as
|
|
31280
|
-
writeFileSync as
|
|
32725
|
+
existsSync as existsSync4,
|
|
32726
|
+
mkdirSync as mkdirSync6,
|
|
32727
|
+
openSync as openSync2,
|
|
32728
|
+
closeSync as closeSync2,
|
|
32729
|
+
readFileSync as readFileSync5,
|
|
32730
|
+
writeFileSync as writeFileSync4,
|
|
31281
32731
|
renameSync,
|
|
31282
32732
|
unlinkSync,
|
|
31283
|
-
statSync as
|
|
31284
|
-
realpathSync as
|
|
32733
|
+
statSync as statSync5,
|
|
32734
|
+
realpathSync as realpathSync5
|
|
31285
32735
|
} from "node:fs";
|
|
31286
|
-
import { dirname as
|
|
32736
|
+
import { dirname as dirname6, resolve as resolve7 } from "node:path";
|
|
31287
32737
|
import { homedir as homedir2 } from "node:os";
|
|
31288
|
-
import { randomUUID as
|
|
32738
|
+
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
31289
32739
|
function agySettingsPath() {
|
|
31290
|
-
return
|
|
31291
|
-
process.env.MARIONETTE_AGY_SETTINGS ??
|
|
32740
|
+
return resolve7(
|
|
32741
|
+
process.env.MARIONETTE_AGY_SETTINGS ?? resolve7(homedir2(), ".gemini/antigravity-cli/settings.json")
|
|
31292
32742
|
);
|
|
31293
32743
|
}
|
|
31294
32744
|
function trustAgyWorkspace(root, settingsPath = agySettingsPath()) {
|
|
31295
|
-
root =
|
|
31296
|
-
if (!
|
|
31297
|
-
|
|
32745
|
+
root = realpathSync5(root);
|
|
32746
|
+
if (!statSync5(root).isDirectory()) throw new Error("AGY trust requires a directory");
|
|
32747
|
+
mkdirSync6(dirname6(settingsPath), { recursive: true, mode: 448 });
|
|
31298
32748
|
const lock = settingsPath + ".marionette.lock";
|
|
31299
32749
|
let fd;
|
|
31300
32750
|
try {
|
|
31301
|
-
fd =
|
|
32751
|
+
fd = openSync2(lock, "wx", 384);
|
|
31302
32752
|
} catch {
|
|
31303
32753
|
throw new Error(`AGY settings are being edited. Retry after checking ${lock}`);
|
|
31304
32754
|
}
|
|
31305
|
-
const temp = settingsPath + "." +
|
|
32755
|
+
const temp = settingsPath + "." + randomUUID7() + ".tmp";
|
|
31306
32756
|
try {
|
|
31307
|
-
const before =
|
|
32757
|
+
const before = existsSync4(settingsPath) ? readFileSync5(settingsPath, "utf8") : null;
|
|
31308
32758
|
const settings = before === null ? {} : JSON.parse(before);
|
|
31309
32759
|
if (!settings || typeof settings !== "object" || Array.isArray(settings))
|
|
31310
32760
|
throw new Error("AGY settings must be a JSON object");
|
|
@@ -31313,118 +32763,23 @@ function trustAgyWorkspace(root, settingsPath = agySettingsPath()) {
|
|
|
31313
32763
|
throw new Error("AGY trustedWorkspaces must be a string array");
|
|
31314
32764
|
if (trusted.includes(root)) return { changed: false, root, settingsPath };
|
|
31315
32765
|
settings.trustedWorkspaces = [...trusted, root];
|
|
31316
|
-
const mode = before === null ? 384 :
|
|
31317
|
-
|
|
31318
|
-
const current =
|
|
32766
|
+
const mode = before === null ? 384 : statSync5(settingsPath).mode & 511;
|
|
32767
|
+
writeFileSync4(temp, JSON.stringify(settings, null, 2) + "\n", { flag: "wx", mode });
|
|
32768
|
+
const current = existsSync4(settingsPath) ? readFileSync5(settingsPath, "utf8") : null;
|
|
31319
32769
|
if (current !== before) throw new Error("AGY settings changed during registration; retry");
|
|
31320
32770
|
renameSync(temp, settingsPath);
|
|
31321
32771
|
return { changed: true, root, settingsPath };
|
|
31322
32772
|
} finally {
|
|
31323
|
-
if (
|
|
31324
|
-
|
|
32773
|
+
if (existsSync4(temp)) unlinkSync(temp);
|
|
32774
|
+
closeSync2(fd);
|
|
31325
32775
|
unlinkSync(lock);
|
|
31326
32776
|
}
|
|
31327
32777
|
}
|
|
31328
32778
|
|
|
31329
32779
|
// src/supervisor.ts
|
|
31330
|
-
import { randomBytes as randomBytes3, randomUUID as
|
|
31331
|
-
import { existsSync as
|
|
31332
|
-
import { dirname as
|
|
31333
|
-
|
|
31334
|
-
// src/worktrees.ts
|
|
31335
|
-
import { execFile } from "node:child_process";
|
|
31336
|
-
import { promisify } from "node:util";
|
|
31337
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync5, realpathSync as realpathSync4, statSync as statSync4 } from "node:fs";
|
|
31338
|
-
import { dirname as dirname5, relative as relative3, resolve as resolve6 } from "node:path";
|
|
31339
|
-
var exec = promisify(execFile);
|
|
31340
|
-
async function git(cwd, args2) {
|
|
31341
|
-
const env = { ...process.env };
|
|
31342
|
-
for (const key of Object.keys(env)) if (key.startsWith("GIT_")) delete env[key];
|
|
31343
|
-
env.GIT_TERMINAL_PROMPT = "0";
|
|
31344
|
-
try {
|
|
31345
|
-
const result = await exec("git", ["-c", "core.hooksPath=/dev/null", "-C", cwd, ...args2], {
|
|
31346
|
-
env,
|
|
31347
|
-
timeout: 12e4,
|
|
31348
|
-
maxBuffer: 4 * 1024 * 1024
|
|
31349
|
-
});
|
|
31350
|
-
return result.stdout.trimEnd();
|
|
31351
|
-
} catch (error) {
|
|
31352
|
-
const e = error;
|
|
31353
|
-
throw new AppError(
|
|
31354
|
-
"worktree_git",
|
|
31355
|
-
`Git worktree operation failed: ${e.stderr?.trim() || e.message}`
|
|
31356
|
-
);
|
|
31357
|
-
}
|
|
31358
|
-
}
|
|
31359
|
-
async function planWorktree(task, stateRoot) {
|
|
31360
|
-
const repositoryRoot = realpathSync4(await git(task.cwd, ["rev-parse", "--show-toplevel"]));
|
|
31361
|
-
const commonDir = realpathSync4(
|
|
31362
|
-
await git(task.cwd, ["rev-parse", "--path-format=absolute", "--git-common-dir"])
|
|
31363
|
-
);
|
|
31364
|
-
const baseRef = task.execution?.mode === "worktree" ? task.execution.baseRef ?? "HEAD" : "HEAD";
|
|
31365
|
-
const baseCommit = await git(task.cwd, [
|
|
31366
|
-
"rev-parse",
|
|
31367
|
-
"--verify",
|
|
31368
|
-
"--end-of-options",
|
|
31369
|
-
`${baseRef}^{commit}`
|
|
31370
|
-
]);
|
|
31371
|
-
if (!inside(repositoryRoot, task.cwd))
|
|
31372
|
-
throw new AppError("worktree_scope", "Task directory is outside its Git repository");
|
|
31373
|
-
const path = safePath(stateRoot, `worktrees/${task.projectId}/${task.id}`);
|
|
31374
|
-
return {
|
|
31375
|
-
state: "planned",
|
|
31376
|
-
repositoryRoot,
|
|
31377
|
-
commonDir,
|
|
31378
|
-
sourceCwd: task.cwd,
|
|
31379
|
-
path,
|
|
31380
|
-
cwd: resolve6(path, relative3(repositoryRoot, task.cwd)),
|
|
31381
|
-
branch: `marionette/${task.id}`,
|
|
31382
|
-
baseCommit
|
|
31383
|
-
};
|
|
31384
|
-
}
|
|
31385
|
-
async function validateWorktree(w, requireClean = false) {
|
|
31386
|
-
const listing = await git(w.repositoryRoot, ["worktree", "list", "--porcelain", "-z"]);
|
|
31387
|
-
const records = listing.split("\0\0").map((record2) => record2.split("\0"));
|
|
31388
|
-
const record = records.find((fields) => fields.includes(`worktree ${w.path}`));
|
|
31389
|
-
if (!record || !record.includes(`branch refs/heads/${w.branch}`) || record.some((field) => /^(locked|prunable)( |$)/.test(field)) || !existsSync3(w.path) || realpathSync4(w.path) !== w.path)
|
|
31390
|
-
throw new AppError(
|
|
31391
|
-
"worktree_identity",
|
|
31392
|
-
"Managed worktree is missing, incomplete, or has changed identity. Inspect it before retrying; existing work is preserved."
|
|
31393
|
-
);
|
|
31394
|
-
const commonDir = realpathSync4(
|
|
31395
|
-
await git(w.path, ["rev-parse", "--path-format=absolute", "--git-common-dir"])
|
|
31396
|
-
);
|
|
31397
|
-
const root = realpathSync4(await git(w.path, ["rev-parse", "--show-toplevel"]));
|
|
31398
|
-
const branch = await git(w.path, ["symbolic-ref", "HEAD"]);
|
|
31399
|
-
if (commonDir !== w.commonDir || root !== w.path || branch !== `refs/heads/${w.branch}`)
|
|
31400
|
-
throw new AppError(
|
|
31401
|
-
"worktree_identity",
|
|
31402
|
-
"Managed worktree repository or branch changed; refusing to reuse it."
|
|
31403
|
-
);
|
|
31404
|
-
if (requireClean) {
|
|
31405
|
-
const head = await git(w.path, ["rev-parse", "HEAD"]);
|
|
31406
|
-
const status = await git(w.path, ["status", "--porcelain", "--untracked-files=all"]);
|
|
31407
|
-
if (head !== w.baseCommit || status)
|
|
31408
|
-
throw new AppError(
|
|
31409
|
-
"worktree_incomplete",
|
|
31410
|
-
"Worktree creation was interrupted and its checkout is not clean at the pinned base. Inspect it before retrying; no files were reset."
|
|
31411
|
-
);
|
|
31412
|
-
}
|
|
31413
|
-
const cwd = safePath(w.path, relative3(w.path, w.cwd), true);
|
|
31414
|
-
if (!statSync4(cwd).isDirectory())
|
|
31415
|
-
throw new AppError(
|
|
31416
|
-
"worktree_cwd",
|
|
31417
|
-
"Task working directory does not exist in the selected Git revision"
|
|
31418
|
-
);
|
|
31419
|
-
return cwd;
|
|
31420
|
-
}
|
|
31421
|
-
async function createWorktree(w) {
|
|
31422
|
-
mkdirSync5(dirname5(w.path), { recursive: true, mode: 448 });
|
|
31423
|
-
await git(w.repositoryRoot, ["worktree", "add", "-b", w.branch, "--", w.path, w.baseCommit]);
|
|
31424
|
-
return validateWorktree(w, true);
|
|
31425
|
-
}
|
|
31426
|
-
|
|
31427
|
-
// src/supervisor.ts
|
|
32780
|
+
import { randomBytes as randomBytes3, randomUUID as randomUUID8 } from "node:crypto";
|
|
32781
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6, realpathSync as realpathSync6 } from "node:fs";
|
|
32782
|
+
import { dirname as dirname7, resolve as resolve8 } from "node:path";
|
|
31428
32783
|
var settled = (status) => status === "idle" || status === "done";
|
|
31429
32784
|
var quote = (s) => "'" + s.replaceAll("'", "'\\''") + "'";
|
|
31430
32785
|
function inputScreen(text2) {
|
|
@@ -31447,9 +32802,26 @@ var Supervisor = class {
|
|
|
31447
32802
|
timer;
|
|
31448
32803
|
busy = /* @__PURE__ */ new Set();
|
|
31449
32804
|
stopped = false;
|
|
32805
|
+
layouts = /* @__PURE__ */ new Map();
|
|
32806
|
+
async withLayout(projectId, action) {
|
|
32807
|
+
const previous = this.layouts.get(projectId) ?? Promise.resolve();
|
|
32808
|
+
let release;
|
|
32809
|
+
const pending = new Promise((resolve13) => {
|
|
32810
|
+
release = resolve13;
|
|
32811
|
+
});
|
|
32812
|
+
this.layouts.set(projectId, pending);
|
|
32813
|
+
await previous;
|
|
32814
|
+
try {
|
|
32815
|
+
return await action();
|
|
32816
|
+
} finally {
|
|
32817
|
+
release();
|
|
32818
|
+
if (this.layouts.get(projectId) === pending) this.layouts.delete(projectId);
|
|
32819
|
+
}
|
|
32820
|
+
}
|
|
31450
32821
|
start() {
|
|
31451
32822
|
this.recover();
|
|
31452
32823
|
this.service.continuation.recover();
|
|
32824
|
+
this.service.cleanup.recover();
|
|
31453
32825
|
this.timer = setInterval(() => this.tick(), this.pollMs);
|
|
31454
32826
|
this.tick();
|
|
31455
32827
|
}
|
|
@@ -31457,6 +32829,7 @@ var Supervisor = class {
|
|
|
31457
32829
|
this.stopped = true;
|
|
31458
32830
|
if (this.timer) clearInterval(this.timer);
|
|
31459
32831
|
await this.service.continuation.stop();
|
|
32832
|
+
await this.service.cleanup.stop();
|
|
31460
32833
|
while (this.busy.size) await new Promise((r) => setTimeout(r, 50));
|
|
31461
32834
|
}
|
|
31462
32835
|
recover() {
|
|
@@ -31532,9 +32905,10 @@ var Supervisor = class {
|
|
|
31532
32905
|
if (this.stopped) return;
|
|
31533
32906
|
this.service.orchestration.refreshEvidence();
|
|
31534
32907
|
this.service.continuation.tick();
|
|
32908
|
+
this.service.cleanup.tick();
|
|
31535
32909
|
const s = this.service, all = s.store.all("task");
|
|
31536
32910
|
for (const t of all)
|
|
31537
|
-
if (t.runId && t.status !== "queued" && !this.busy.has(t.id) && (!terminalStates.has(t.status) || this.run(t).phase !== "stopped"))
|
|
32911
|
+
if (t.runId && !this.service.cleanup.active(t.id) && !["closing", "closed", "uncertain"].includes(this.run(t).cleanup?.state ?? "") && t.status !== "queued" && !this.busy.has(t.id) && (!terminalStates.has(t.status) || this.run(t).phase !== "stopped"))
|
|
31538
32912
|
this.launch(t.id, () => this.monitor(t.id));
|
|
31539
32913
|
for (const t of s.store.all("task").filter(
|
|
31540
32914
|
(t2) => t2.status === "waiting" || t2.resumePending && t2.status === "blocked" && t2.blockKind === "missing-report"
|
|
@@ -31551,7 +32925,9 @@ var Supervisor = class {
|
|
|
31551
32925
|
}
|
|
31552
32926
|
for (const project of s.store.all("project")) {
|
|
31553
32927
|
const tasks = s.tasks(project.id);
|
|
31554
|
-
for (const task of tasks.filter(
|
|
32928
|
+
for (const task of tasks.filter(
|
|
32929
|
+
(t) => t.status === "queued" && !t.archiveId && !this.service.cleanup.active(t.id) && !this.busy.has(t.id)
|
|
32930
|
+
)) {
|
|
31555
32931
|
const dependencies = task.dependencies.map((id) => s.task(id));
|
|
31556
32932
|
let waitReason = dependencies.some((d) => d.status !== "completed") ? "Waiting for dependencies to complete" : void 0;
|
|
31557
32933
|
if (!waitReason) waitReason = s.orchestration.capacity(task, (t) => !!this.holds(t));
|
|
@@ -31575,10 +32951,10 @@ var Supervisor = class {
|
|
|
31575
32951
|
return !terminalStates.has(t.status) || t.runId && this.run(t)?.phase !== "stopped";
|
|
31576
32952
|
}
|
|
31577
32953
|
overlap(a, b) {
|
|
31578
|
-
const root = (t) => t.worktree?.cwd ?? (t.execution?.mode === "worktree" ?
|
|
32954
|
+
const root = (t) => t.worktree?.cwd ?? (t.execution?.mode === "worktree" ? resolve8(dirname7(this.service.store.path), "worktrees", t.projectId, t.id) : t.cwd);
|
|
31579
32955
|
const path = (t, owned) => {
|
|
31580
32956
|
const cwd = root(t);
|
|
31581
|
-
return t.execution?.mode === "worktree" && !
|
|
32957
|
+
return t.execution?.mode === "worktree" && !existsSync5(cwd) ? resolve8(cwd, owned) : safePath(cwd, owned);
|
|
31582
32958
|
};
|
|
31583
32959
|
return a.ownership.some(
|
|
31584
32960
|
(x) => b.ownership.some((y) => {
|
|
@@ -31684,7 +33060,7 @@ Report format: {"revision":${t.revision},"type":"complete","summary":"What chang
|
|
|
31684
33060
|
try {
|
|
31685
33061
|
if (t.execution?.mode === "worktree") {
|
|
31686
33062
|
if (!t.worktree) {
|
|
31687
|
-
const plan = await planWorktree(t,
|
|
33063
|
+
const plan = await planWorktree(t, realpathSync6(dirname7(s.store.path)));
|
|
31688
33064
|
if (s.task(id).status !== "preparing") return;
|
|
31689
33065
|
t = s.updateTask(t, { worktree: plan }, "Planned isolated Git worktree");
|
|
31690
33066
|
}
|
|
@@ -31748,7 +33124,7 @@ ${JSON.stringify(children.map((child) => ({ id: child.id, title: child.title, st
|
|
|
31748
33124
|
}
|
|
31749
33125
|
const token = randomBytes3(32).toString("hex");
|
|
31750
33126
|
const run = {
|
|
31751
|
-
id:
|
|
33127
|
+
id: randomUUID8(),
|
|
31752
33128
|
taskId: id,
|
|
31753
33129
|
attempt: t.attempt + 1,
|
|
31754
33130
|
revision: t.revision,
|
|
@@ -31775,23 +33151,38 @@ ${JSON.stringify(children.map((child) => ({ id: child.id, title: child.title, st
|
|
|
31775
33151
|
t = s.updateTask(s.task(id), { runId: run.id, attempt: run.attempt });
|
|
31776
33152
|
});
|
|
31777
33153
|
try {
|
|
31778
|
-
|
|
31779
|
-
|
|
31780
|
-
|
|
31781
|
-
|
|
31782
|
-
|
|
31783
|
-
|
|
31784
|
-
|
|
31785
|
-
|
|
31786
|
-
|
|
31787
|
-
|
|
31788
|
-
|
|
31789
|
-
|
|
31790
|
-
|
|
31791
|
-
|
|
31792
|
-
|
|
33154
|
+
await this.withLayout(p.id, async () => {
|
|
33155
|
+
const taskIds = new Set(s.tasks(p.id).map((task) => task.id));
|
|
33156
|
+
const runs = s.store.all("run").filter((r) => taskIds.has(r.taskId) && !s.cleanup.active(r.taskId));
|
|
33157
|
+
run.creation = await planWorkerPane(h, p.workspaceId, runs);
|
|
33158
|
+
run.terminalScope = "pane";
|
|
33159
|
+
this.saveRun(run);
|
|
33160
|
+
const options = {
|
|
33161
|
+
workspace_id: p.workspaceId,
|
|
33162
|
+
cwd: t.cwd,
|
|
33163
|
+
focus: false,
|
|
33164
|
+
env: { MARIONETTE_WORKER_TOKEN: token, MARIONETTE_TASK_ID: id, MARIONETTE_URL: this.url }
|
|
33165
|
+
};
|
|
33166
|
+
const splitting = run.creation.mode === "pane";
|
|
33167
|
+
const created = await h.call(
|
|
33168
|
+
splitting ? "pane.split" : "tab.create",
|
|
33169
|
+
splitting ? {
|
|
33170
|
+
...options,
|
|
33171
|
+
target_pane_id: run.creation.targetPaneId,
|
|
33172
|
+
direction: run.creation.direction
|
|
33173
|
+
} : { ...options, label: run.agentName }
|
|
33174
|
+
);
|
|
33175
|
+
const pane = splitting ? created.pane : created.root_pane;
|
|
33176
|
+
if (!pane?.pane_id || !pane.terminal_id || !pane.tab_id || pane.workspace_id !== p.workspaceId || splitting && (pane.tab_id !== run.creation.tabId || run.creation.beforePaneIds.includes(pane.pane_id)))
|
|
33177
|
+
throw new Error("Herdr did not return the expected new scoped pane");
|
|
33178
|
+
Object.assign(run, {
|
|
33179
|
+
paneId: pane.pane_id,
|
|
33180
|
+
terminalId: pane.terminal_id,
|
|
33181
|
+
tabId: pane.tab_id,
|
|
33182
|
+
phase: "starting"
|
|
33183
|
+
});
|
|
33184
|
+
this.saveRun(run);
|
|
31793
33185
|
});
|
|
31794
|
-
this.saveRun(run);
|
|
31795
33186
|
for (let attempt = 0; ; attempt++) {
|
|
31796
33187
|
try {
|
|
31797
33188
|
await h.call(
|
|
@@ -31808,7 +33199,7 @@ ${JSON.stringify(children.map((child) => ({ id: child.id, title: child.title, st
|
|
|
31808
33199
|
break;
|
|
31809
33200
|
} catch (error) {
|
|
31810
33201
|
if (/not an available shell/.test(String(error)) && attempt < 10) {
|
|
31811
|
-
await new Promise((
|
|
33202
|
+
await new Promise((resolve13) => setTimeout(resolve13, 500));
|
|
31812
33203
|
continue;
|
|
31813
33204
|
}
|
|
31814
33205
|
if (["agent_not_ready", "agent_not_found"].includes(error.code)) break;
|
|
@@ -31822,7 +33213,7 @@ ${JSON.stringify(children.map((child) => ({ id: child.id, title: child.title, st
|
|
|
31822
33213
|
} catch (error) {
|
|
31823
33214
|
if (!["agent_not_ready", "agent_not_found"].includes(error.code))
|
|
31824
33215
|
throw error;
|
|
31825
|
-
await new Promise((
|
|
33216
|
+
await new Promise((resolve13) => setTimeout(resolve13, 500));
|
|
31826
33217
|
continue;
|
|
31827
33218
|
}
|
|
31828
33219
|
const read = await h.call("pane.read", {
|
|
@@ -31838,7 +33229,7 @@ ${JSON.stringify(children.map((child) => ({ id: child.id, title: child.title, st
|
|
|
31838
33229
|
if (a.agent === run.kind && settled(a.agent_status) && !a.launch_pending && a.interactive_ready !== false)
|
|
31839
33230
|
break;
|
|
31840
33231
|
a = void 0;
|
|
31841
|
-
await new Promise((
|
|
33232
|
+
await new Promise((resolve13) => setTimeout(resolve13, 500));
|
|
31842
33233
|
}
|
|
31843
33234
|
if (!a) throw new AppError("startup_timeout", "Agent did not become ready within 30 seconds");
|
|
31844
33235
|
run.nativeSession = a.agent_session?.value;
|
|
@@ -32171,7 +33562,7 @@ Ownership remains ${t.ownership.join(", ")}. Current acceptance checks: ${JSON.s
|
|
|
32171
33562
|
if (!fileDigest) throw new Error("Artifact is not a regular file");
|
|
32172
33563
|
if (!check.allowUnchanged && fileDigest === r.baseline[check.path])
|
|
32173
33564
|
throw new Error("Artifact is unchanged from before dispatch");
|
|
32174
|
-
if (check.contains !== void 0 && !
|
|
33565
|
+
if (check.contains !== void 0 && !readFileSync6(path, "utf8").includes(check.contains))
|
|
32175
33566
|
throw new Error("Artifact does not contain the expected content");
|
|
32176
33567
|
if (check.sha256 && check.sha256 !== fileDigest)
|
|
32177
33568
|
throw new Error("Artifact SHA-256 does not match");
|
|
@@ -32211,14 +33602,14 @@ ${result.output}`;
|
|
|
32211
33602
|
};
|
|
32212
33603
|
|
|
32213
33604
|
// src/version.ts
|
|
32214
|
-
var VERSION = "0.2.
|
|
33605
|
+
var VERSION = "0.2.2";
|
|
32215
33606
|
var SETUP_VERSION = 2;
|
|
32216
33607
|
|
|
32217
33608
|
// src/server.ts
|
|
32218
33609
|
async function serve(home2, port) {
|
|
32219
|
-
const config = initConfig(home2, port), lock =
|
|
32220
|
-
if (
|
|
32221
|
-
const old = JSON.parse(
|
|
33610
|
+
const config = initConfig(home2, port), lock = resolve9(home2, "supervisor.lock"), lockId = randomUUID9();
|
|
33611
|
+
if (existsSync6(lock)) {
|
|
33612
|
+
const old = JSON.parse(readFileSync7(lock, "utf8"));
|
|
32222
33613
|
try {
|
|
32223
33614
|
process.kill(old.pid, 0);
|
|
32224
33615
|
throw new AppError(
|
|
@@ -32231,13 +33622,13 @@ async function serve(home2, port) {
|
|
|
32231
33622
|
unlinkSync2(lock);
|
|
32232
33623
|
}
|
|
32233
33624
|
}
|
|
32234
|
-
|
|
33625
|
+
writeFileSync5(lock, JSON.stringify({ pid: process.pid, id: lockId }), {
|
|
32235
33626
|
flag: "wx",
|
|
32236
33627
|
mode: 384
|
|
32237
33628
|
});
|
|
32238
|
-
const store = new Store(
|
|
32239
|
-
const projectRoot =
|
|
32240
|
-
const cliPath =
|
|
33629
|
+
const store = new Store(resolve9(home2, "state.sqlite")), service = new Service(store);
|
|
33630
|
+
const projectRoot = resolve9(dirname8(fileURLToPath(import.meta.url)), "..");
|
|
33631
|
+
const cliPath = resolve9(projectRoot, "dist/cli.js");
|
|
32241
33632
|
const url = `http://127.0.0.1:${config.port}`;
|
|
32242
33633
|
const supervisor = new Supervisor(service, url, cliPath);
|
|
32243
33634
|
const app = (0, import_express.default)();
|
|
@@ -32312,7 +33703,7 @@ async function serve(home2, port) {
|
|
|
32312
33703
|
res.json({ stopping: true });
|
|
32313
33704
|
setTimeout(() => void shutdown(), 20);
|
|
32314
33705
|
});
|
|
32315
|
-
app.use(import_express.default.static(
|
|
33706
|
+
app.use(import_express.default.static(resolve9(projectRoot, "public"), { index: "index.html", maxAge: 0 }));
|
|
32316
33707
|
app.use(
|
|
32317
33708
|
(error, req, res, _next) => {
|
|
32318
33709
|
const status = error instanceof AppError ? error.status : error instanceof ZodError ? 400 : 500;
|
|
@@ -32342,7 +33733,7 @@ async function serve(home2, port) {
|
|
|
32342
33733
|
server.closeIdleConnections();
|
|
32343
33734
|
});
|
|
32344
33735
|
store.close();
|
|
32345
|
-
if (
|
|
33736
|
+
if (existsSync6(lock) && JSON.parse(readFileSync7(lock, "utf8")).id === lockId) unlinkSync2(lock);
|
|
32346
33737
|
}
|
|
32347
33738
|
process.once("SIGTERM", () => void shutdown());
|
|
32348
33739
|
process.once("SIGINT", () => void shutdown());
|
|
@@ -32354,51 +33745,51 @@ async function serve(home2, port) {
|
|
|
32354
33745
|
}
|
|
32355
33746
|
|
|
32356
33747
|
// src/runtime.ts
|
|
32357
|
-
import { createHash as
|
|
33748
|
+
import { createHash as createHash3, randomUUID as randomUUID10 } from "node:crypto";
|
|
32358
33749
|
import {
|
|
32359
33750
|
cpSync,
|
|
32360
|
-
existsSync as
|
|
32361
|
-
mkdirSync as
|
|
32362
|
-
readFileSync as
|
|
33751
|
+
existsSync as existsSync7,
|
|
33752
|
+
mkdirSync as mkdirSync7,
|
|
33753
|
+
readFileSync as readFileSync8,
|
|
32363
33754
|
readdirSync,
|
|
32364
33755
|
renameSync as renameSync2,
|
|
32365
33756
|
rmSync,
|
|
32366
|
-
writeFileSync as
|
|
33757
|
+
writeFileSync as writeFileSync6
|
|
32367
33758
|
} from "node:fs";
|
|
32368
|
-
import { dirname as
|
|
33759
|
+
import { dirname as dirname9, resolve as resolve10, relative as relative4 } from "node:path";
|
|
32369
33760
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
32370
|
-
var packageRoot =
|
|
33761
|
+
var packageRoot = resolve10(dirname9(fileURLToPath2(import.meta.url)), "..");
|
|
32371
33762
|
function installRuntime(home2, source = packageRoot) {
|
|
32372
33763
|
const files = ["dist/cli.js", "dist/mcp.js", "package.json"];
|
|
32373
|
-
if (
|
|
33764
|
+
if (existsSync7(resolve10(source, "THIRD_PARTY_NOTICES.md"))) files.push("THIRD_PARTY_NOTICES.md");
|
|
32374
33765
|
function walk(dir) {
|
|
32375
|
-
for (const entry of readdirSync(
|
|
33766
|
+
for (const entry of readdirSync(resolve10(source, dir), { withFileTypes: true })) {
|
|
32376
33767
|
const path = dir + "/" + entry.name;
|
|
32377
33768
|
entry.isDirectory() ? walk(path) : files.push(path);
|
|
32378
33769
|
}
|
|
32379
33770
|
}
|
|
32380
|
-
if (!
|
|
33771
|
+
if (!existsSync7(resolve10(source, "dist/cli.js")) || !existsSync7(resolve10(source, "public/index.html")))
|
|
32381
33772
|
throw new Error("Build Marionette first with npm run build");
|
|
32382
33773
|
walk("public");
|
|
32383
|
-
const digest2 =
|
|
32384
|
-
for (const file of files.sort()) digest2.update(file).update(
|
|
32385
|
-
const version = JSON.parse(
|
|
32386
|
-
const target =
|
|
33774
|
+
const digest2 = createHash3("sha256");
|
|
33775
|
+
for (const file of files.sort()) digest2.update(file).update(readFileSync8(resolve10(source, file)));
|
|
33776
|
+
const version = JSON.parse(readFileSync8(resolve10(source, "package.json"), "utf8")).version;
|
|
33777
|
+
const target = resolve10(home2, "runtimes", version + "-" + digest2.digest("hex").slice(0, 16));
|
|
32387
33778
|
if (relative4(source, target) === "") return target;
|
|
32388
|
-
if (
|
|
32389
|
-
|
|
32390
|
-
const temp = target + "." +
|
|
32391
|
-
|
|
33779
|
+
if (existsSync7(resolve10(target, ".complete"))) return target;
|
|
33780
|
+
mkdirSync7(dirname9(target), { recursive: true, mode: 448 });
|
|
33781
|
+
const temp = target + "." + randomUUID10();
|
|
33782
|
+
mkdirSync7(temp, { mode: 448 });
|
|
32392
33783
|
try {
|
|
32393
33784
|
for (const file of files) {
|
|
32394
|
-
|
|
32395
|
-
cpSync(
|
|
33785
|
+
mkdirSync7(dirname9(resolve10(temp, file)), { recursive: true });
|
|
33786
|
+
cpSync(resolve10(source, file), resolve10(temp, file));
|
|
32396
33787
|
}
|
|
32397
|
-
|
|
33788
|
+
writeFileSync6(resolve10(temp, ".complete"), "1\n");
|
|
32398
33789
|
try {
|
|
32399
33790
|
renameSync2(temp, target);
|
|
32400
33791
|
} catch (e) {
|
|
32401
|
-
if (!
|
|
33792
|
+
if (!existsSync7(resolve10(target, ".complete"))) throw e;
|
|
32402
33793
|
}
|
|
32403
33794
|
} finally {
|
|
32404
33795
|
rmSync(temp, { recursive: true, force: true });
|
|
@@ -32408,20 +33799,20 @@ function installRuntime(home2, source = packageRoot) {
|
|
|
32408
33799
|
|
|
32409
33800
|
// src/setup.ts
|
|
32410
33801
|
import {
|
|
32411
|
-
existsSync as
|
|
32412
|
-
mkdirSync as
|
|
32413
|
-
readFileSync as
|
|
32414
|
-
writeFileSync as
|
|
33802
|
+
existsSync as existsSync8,
|
|
33803
|
+
mkdirSync as mkdirSync8,
|
|
33804
|
+
readFileSync as readFileSync9,
|
|
33805
|
+
writeFileSync as writeFileSync7,
|
|
32415
33806
|
renameSync as renameSync3,
|
|
32416
33807
|
unlinkSync as unlinkSync3,
|
|
32417
|
-
openSync as
|
|
32418
|
-
closeSync as
|
|
32419
|
-
realpathSync as
|
|
32420
|
-
statSync as
|
|
33808
|
+
openSync as openSync3,
|
|
33809
|
+
closeSync as closeSync3,
|
|
33810
|
+
realpathSync as realpathSync7,
|
|
33811
|
+
statSync as statSync6
|
|
32421
33812
|
} from "node:fs";
|
|
32422
|
-
import { basename, dirname as
|
|
33813
|
+
import { basename, dirname as dirname10, resolve as resolve11 } from "node:path";
|
|
32423
33814
|
import { homedir as homedir3 } from "node:os";
|
|
32424
|
-
import { createHash as
|
|
33815
|
+
import { createHash as createHash4, randomUUID as randomUUID11 } from "node:crypto";
|
|
32425
33816
|
import { createInterface } from "node:readline/promises";
|
|
32426
33817
|
import { execFile as execFile2, spawn as spawn3 } from "node:child_process";
|
|
32427
33818
|
import { promisify as promisify2 } from "node:util";
|
|
@@ -32445,22 +33836,22 @@ var setupSchema = external_exports.object({
|
|
|
32445
33836
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
32446
33837
|
var quote2 = (s) => "'" + s.replace(/'/g, "'\\''") + "'";
|
|
32447
33838
|
function privateJson(path, data) {
|
|
32448
|
-
|
|
32449
|
-
const temp = path + "." +
|
|
33839
|
+
mkdirSync8(dirname10(path), { recursive: true, mode: 448 });
|
|
33840
|
+
const temp = path + "." + randomUUID11() + ".tmp";
|
|
32450
33841
|
try {
|
|
32451
|
-
|
|
33842
|
+
writeFileSync7(temp, JSON.stringify(data, null, 2) + "\n", { mode: 384, flag: "wx" });
|
|
32452
33843
|
renameSync3(temp, path);
|
|
32453
33844
|
} finally {
|
|
32454
|
-
if (
|
|
33845
|
+
if (existsSync8(temp)) unlinkSync3(temp);
|
|
32455
33846
|
}
|
|
32456
33847
|
}
|
|
32457
33848
|
function setupPlan(input) {
|
|
32458
33849
|
const supplied = setupSchema.partial().parse(input);
|
|
32459
|
-
const root =
|
|
32460
|
-
if (!
|
|
32461
|
-
const hash2 =
|
|
32462
|
-
const bindingPath =
|
|
32463
|
-
const binding =
|
|
33850
|
+
const root = realpathSync7(resolve11(supplied.project ?? process.cwd()));
|
|
33851
|
+
if (!statSync6(root).isDirectory()) throw new Error("Project must be an existing directory");
|
|
33852
|
+
const hash2 = createHash4("sha256").update(root).digest("hex").slice(0, 10);
|
|
33853
|
+
const bindingPath = resolve11(root, ".marionette/project.json");
|
|
33854
|
+
const binding = existsSync8(bindingPath) ? JSON.parse(readFileSync9(bindingPath, "utf8")) : null;
|
|
32464
33855
|
const options = setupSchema.parse({
|
|
32465
33856
|
lead: binding?.lead,
|
|
32466
33857
|
leadName: binding?.leadName,
|
|
@@ -32469,13 +33860,13 @@ function setupPlan(input) {
|
|
|
32469
33860
|
mcp: binding?.mcp,
|
|
32470
33861
|
...supplied
|
|
32471
33862
|
});
|
|
32472
|
-
const legacy =
|
|
32473
|
-
const home2 =
|
|
32474
|
-
options.home ?? process.env.MARIONETTE_HOME ?? binding?.home ?? (
|
|
33863
|
+
const legacy = resolve11(root, ".marionette");
|
|
33864
|
+
const home2 = resolve11(
|
|
33865
|
+
options.home ?? process.env.MARIONETTE_HOME ?? binding?.home ?? (existsSync8(resolve11(legacy, "config.json")) ? legacy : resolve11(process.env.XDG_DATA_HOME ?? resolve11(homedir3(), ".local/share"), "marionette"))
|
|
32475
33866
|
);
|
|
32476
33867
|
const session = options.session ?? binding?.session ?? `marionette-${hash2}`;
|
|
32477
|
-
const socket =
|
|
32478
|
-
options.socket ?? binding?.socket ??
|
|
33868
|
+
const socket = resolve11(
|
|
33869
|
+
options.socket ?? binding?.socket ?? resolve11(homedir3(), ".config/herdr/sessions", session, "herdr.sock")
|
|
32479
33870
|
);
|
|
32480
33871
|
return {
|
|
32481
33872
|
...options,
|
|
@@ -32546,11 +33937,11 @@ async function ensureHerdr(plan) {
|
|
|
32546
33937
|
return h;
|
|
32547
33938
|
} catch {
|
|
32548
33939
|
}
|
|
32549
|
-
if (plan.socket !==
|
|
33940
|
+
if (plan.socket !== resolve11(homedir3(), ".config/herdr/sessions", plan.session, "herdr.sock"))
|
|
32550
33941
|
throw new Error(
|
|
32551
33942
|
`Cannot connect to supplied socket ${plan.socket}; start its Herdr session first`
|
|
32552
33943
|
);
|
|
32553
|
-
const log =
|
|
33944
|
+
const log = openSync3(resolve11(plan.home, "herdr.log"), "a", 384);
|
|
32554
33945
|
const child = spawn3("herdr", ["--session", plan.session, "server"], {
|
|
32555
33946
|
detached: true,
|
|
32556
33947
|
stdio: ["ignore", log, log]
|
|
@@ -32559,7 +33950,7 @@ async function ensureHerdr(plan) {
|
|
|
32559
33950
|
child.on("error", (e) => {
|
|
32560
33951
|
error = e;
|
|
32561
33952
|
});
|
|
32562
|
-
|
|
33953
|
+
closeSync3(log);
|
|
32563
33954
|
child.unref();
|
|
32564
33955
|
for (let n = 0; n < 100; n++) {
|
|
32565
33956
|
if (error) throw error;
|
|
@@ -32570,14 +33961,14 @@ async function ensureHerdr(plan) {
|
|
|
32570
33961
|
}
|
|
32571
33962
|
await sleep(100);
|
|
32572
33963
|
}
|
|
32573
|
-
throw new Error(`Herdr session did not start; inspect ${
|
|
33964
|
+
throw new Error(`Herdr session did not start; inspect ${resolve11(plan.home, "herdr.log")}`);
|
|
32574
33965
|
}
|
|
32575
33966
|
function mcpCommand(agent, name, runtime, home2) {
|
|
32576
33967
|
const binary = agent === "codex-desktop" ? "codex" : agent;
|
|
32577
33968
|
const serverArgs = [
|
|
32578
33969
|
process.execPath,
|
|
32579
33970
|
"--no-warnings",
|
|
32580
|
-
|
|
33971
|
+
resolve11(runtime, "dist/mcp.js"),
|
|
32581
33972
|
"--home",
|
|
32582
33973
|
home2
|
|
32583
33974
|
];
|
|
@@ -32590,7 +33981,7 @@ function mcpCommand(agent, name, runtime, home2) {
|
|
|
32590
33981
|
};
|
|
32591
33982
|
}
|
|
32592
33983
|
async function installMcp(command2, name, home2) {
|
|
32593
|
-
const receipt =
|
|
33984
|
+
const receipt = resolve11(home2, "clients", command2.binary + ".json");
|
|
32594
33985
|
let found;
|
|
32595
33986
|
try {
|
|
32596
33987
|
const output = (await exec2(
|
|
@@ -32605,14 +33996,14 @@ async function installMcp(command2, name, home2) {
|
|
|
32605
33996
|
}
|
|
32606
33997
|
if (found) {
|
|
32607
33998
|
if (found.includes(command2.server.args[1]) && found.includes(home2)) return "already-configured";
|
|
32608
|
-
const old =
|
|
33999
|
+
const old = existsSync8(receipt) ? JSON.parse(readFileSync9(receipt, "utf8")) : null;
|
|
32609
34000
|
if (!old || old.name !== name || !found.includes(old.runtime) || !found.includes(home2))
|
|
32610
34001
|
throw new Error(
|
|
32611
34002
|
`MCP server ${name} already has another configuration. Use --mcp print to review it.`
|
|
32612
34003
|
);
|
|
32613
34004
|
}
|
|
32614
34005
|
await exec2(command2.binary, command2.args, { timeout: 3e4 });
|
|
32615
|
-
privateJson(receipt, { name, runtime:
|
|
34006
|
+
privateJson(receipt, { name, runtime: dirname10(dirname10(command2.server.args[1])) });
|
|
32616
34007
|
return "installed";
|
|
32617
34008
|
}
|
|
32618
34009
|
function leadPrompt(projectId, leadName, leasePath) {
|
|
@@ -32631,18 +34022,18 @@ async function runSetup(input) {
|
|
|
32631
34022
|
}).catch(() => {
|
|
32632
34023
|
throw new Error(`Install the selected lead CLI first, or use --mcp print.`);
|
|
32633
34024
|
});
|
|
32634
|
-
|
|
32635
|
-
const lockPath =
|
|
34025
|
+
mkdirSync8(p.home, { recursive: true, mode: 448 });
|
|
34026
|
+
const lockPath = resolve11(p.home, "setup.lock");
|
|
32636
34027
|
let lock;
|
|
32637
34028
|
try {
|
|
32638
|
-
lock =
|
|
32639
|
-
|
|
34029
|
+
lock = openSync3(lockPath, "wx", 384);
|
|
34030
|
+
writeFileSync7(lock, JSON.stringify({ pid: process.pid }));
|
|
32640
34031
|
} catch {
|
|
32641
34032
|
throw new Error(`Another setup may be running. Check ${lockPath} before retrying.`);
|
|
32642
34033
|
}
|
|
32643
34034
|
try {
|
|
32644
34035
|
const runtime = installRuntime(p.home);
|
|
32645
|
-
if (!
|
|
34036
|
+
if (!existsSync8(resolve11(p.home, "config.json")))
|
|
32646
34037
|
initConfig(p.home, await availablePort(p.port));
|
|
32647
34038
|
const config = loadConfig(p.home);
|
|
32648
34039
|
if (p.port && config.port !== p.port)
|
|
@@ -32651,7 +34042,7 @@ async function runSetup(input) {
|
|
|
32651
34042
|
);
|
|
32652
34043
|
await exec2(
|
|
32653
34044
|
process.execPath,
|
|
32654
|
-
["--no-warnings",
|
|
34045
|
+
["--no-warnings", resolve11(runtime, "dist/cli.js"), "start", "--home", p.home],
|
|
32655
34046
|
{ timeout: 15e3 }
|
|
32656
34047
|
);
|
|
32657
34048
|
const health = await (await fetch(`http://127.0.0.1:${config.port}/health`, { signal: AbortSignal.timeout(3e3) })).json();
|
|
@@ -32672,9 +34063,9 @@ async function runSetup(input) {
|
|
|
32672
34063
|
}
|
|
32673
34064
|
await h.call("workspace.get", { workspace_id: workspaceId });
|
|
32674
34065
|
privateJson(
|
|
32675
|
-
|
|
34066
|
+
resolve11(
|
|
32676
34067
|
p.home,
|
|
32677
|
-
"setup-project-" +
|
|
34068
|
+
"setup-project-" + createHash4("sha256").update(p.root).digest("hex").slice(0, 10) + ".json"
|
|
32678
34069
|
),
|
|
32679
34070
|
{ root: p.root, session: p.session, socket: p.socket, workspace: workspaceId }
|
|
32680
34071
|
);
|
|
@@ -32686,8 +34077,8 @@ async function runSetup(input) {
|
|
|
32686
34077
|
workspaceId,
|
|
32687
34078
|
trustAgyWorkspaces: p.trustAgy
|
|
32688
34079
|
});
|
|
32689
|
-
const leasePath =
|
|
32690
|
-
let lease =
|
|
34080
|
+
const leasePath = resolve11(p.home, "leads", project.id + ".json");
|
|
34081
|
+
let lease = existsSync8(leasePath) ? credentialsSchema.parse(JSON.parse(readFileSync9(leasePath, "utf8"))) : null;
|
|
32691
34082
|
const briefing = await call(p.home, "project.briefing", { projectId: project.id });
|
|
32692
34083
|
const same = briefing.lead?.owner === p.leadName && briefing.lead?.agent === p.lead;
|
|
32693
34084
|
if (briefing.lead && !(same && lease && lease.epoch === briefing.lead.epoch && lease.owner === briefing.lead.owner) && !p.takeover)
|
|
@@ -32724,15 +34115,15 @@ async function runSetup(input) {
|
|
|
32724
34115
|
mcp: p.mcp
|
|
32725
34116
|
};
|
|
32726
34117
|
privateJson(p.bindingPath, binding);
|
|
32727
|
-
const ignore =
|
|
32728
|
-
if (!
|
|
34118
|
+
const ignore = resolve11(dirname10(p.bindingPath), ".gitignore");
|
|
34119
|
+
if (!existsSync8(ignore)) writeFileSync7(ignore, "*\n", { flag: "wx" });
|
|
32729
34120
|
const trust = p.trustAgy ? trustAgyWorkspace(p.root) : { changed: false, disabled: true };
|
|
32730
34121
|
const mcpName = "marionette-" + config.id.slice(0, 8);
|
|
32731
34122
|
const command2 = mcpCommand(p.lead, mcpName, runtime, p.home);
|
|
32732
34123
|
const mcpStatus = p.mcp === "install" ? await installMcp(command2, mcpName, p.home) : p.mcp;
|
|
32733
34124
|
const prompt = leadPrompt(project.id, p.leadName, leasePath);
|
|
32734
|
-
const promptPath =
|
|
32735
|
-
|
|
34125
|
+
const promptPath = resolve11(p.home, "leads", project.id + ".md");
|
|
34126
|
+
writeFileSync7(promptPath, prompt + "\n", { mode: 384 });
|
|
32736
34127
|
return {
|
|
32737
34128
|
ok: true,
|
|
32738
34129
|
home: p.home,
|
|
@@ -32749,19 +34140,19 @@ async function runSetup(input) {
|
|
|
32749
34140
|
prompt
|
|
32750
34141
|
};
|
|
32751
34142
|
} finally {
|
|
32752
|
-
|
|
34143
|
+
closeSync3(lock);
|
|
32753
34144
|
unlinkSync3(lockPath);
|
|
32754
34145
|
}
|
|
32755
34146
|
}
|
|
32756
34147
|
async function launchLead(project, printOnly, profileId) {
|
|
32757
|
-
while (!
|
|
32758
|
-
if (
|
|
34148
|
+
while (!existsSync8(resolve11(project, ".marionette/project.json"))) {
|
|
34149
|
+
if (dirname10(project) === project)
|
|
32759
34150
|
throw new Error("No configured Marionette project found. Run marionette setup first.");
|
|
32760
|
-
project =
|
|
34151
|
+
project = dirname10(project);
|
|
32761
34152
|
}
|
|
32762
|
-
const binding = JSON.parse(
|
|
34153
|
+
const binding = JSON.parse(readFileSync9(resolve11(project, ".marionette/project.json"), "utf8"));
|
|
32763
34154
|
const brief = await call(binding.home, "project.briefing", { projectId: binding.projectId });
|
|
32764
|
-
const lease = credentialsSchema.parse(JSON.parse(
|
|
34155
|
+
const lease = credentialsSchema.parse(JSON.parse(readFileSync9(binding.leasePath, "utf8")));
|
|
32765
34156
|
if (!brief.lead || brief.lead.owner !== lease.owner || brief.lead.epoch !== lease.epoch)
|
|
32766
34157
|
throw new Error(
|
|
32767
34158
|
"The saved lead no longer controls this project. Refresh setup with an explicit handover or takeover."
|
|
@@ -32810,7 +34201,7 @@ var print = (v) => console.log(JSON.stringify(v, null, 2));
|
|
|
32810
34201
|
async function main() {
|
|
32811
34202
|
const cmd = args[0] ?? "help";
|
|
32812
34203
|
if (cmd === "--version" || cmd === "version") {
|
|
32813
|
-
console.log(JSON.parse(
|
|
34204
|
+
console.log(JSON.parse(readFileSync10(resolve12(packageRoot, "package.json"), "utf8")).version);
|
|
32814
34205
|
return;
|
|
32815
34206
|
}
|
|
32816
34207
|
if (cmd === "setup" || cmd === "init") {
|
|
@@ -32868,7 +34259,7 @@ async function main() {
|
|
|
32868
34259
|
n++;
|
|
32869
34260
|
}
|
|
32870
34261
|
}
|
|
32871
|
-
let input = flag("config") ? JSON.parse(
|
|
34262
|
+
let input = flag("config") ? JSON.parse(readFileSync10(flag("config"), "utf8")) : {};
|
|
32872
34263
|
for (const key of ["project", "home", "name", "session", "socket", "workspace", "lead", "mcp"])
|
|
32873
34264
|
if (flag(key)) input[key] = flag(key);
|
|
32874
34265
|
if (flag("lead-name")) input.leadName = flag("lead-name");
|
|
@@ -32907,7 +34298,7 @@ ${result.mcp.status === "print" ? "\nMCP install command:\n" + result.mcp.shell
|
|
|
32907
34298
|
}
|
|
32908
34299
|
if (cmd === "lead") {
|
|
32909
34300
|
await launchLead(
|
|
32910
|
-
|
|
34301
|
+
resolve12(flag("project") ?? process.cwd()),
|
|
32911
34302
|
args.includes("--print"),
|
|
32912
34303
|
flag("profile")
|
|
32913
34304
|
);
|
|
@@ -32921,7 +34312,7 @@ ${result.mcp.status === "print" ? "\nMCP install command:\n" + result.mcp.shell
|
|
|
32921
34312
|
} = process.env;
|
|
32922
34313
|
if (!url || !taskId || !token)
|
|
32923
34314
|
throw new Error("worker-report must run in a Marionette-created worker pane");
|
|
32924
|
-
const input = JSON.parse(
|
|
34315
|
+
const input = JSON.parse(readFileSync10(flag("file") ?? 0, "utf8"));
|
|
32925
34316
|
const res = await fetch(
|
|
32926
34317
|
`${url}/api/worker/${encodeURIComponent(taskId)}${cmd === "worker-call" ? "/call" : ""}`,
|
|
32927
34318
|
{
|
|
@@ -32955,16 +34346,16 @@ ${result.mcp.status === "print" ? "\nMCP install command:\n" + result.mcp.shell
|
|
|
32955
34346
|
} catch (e) {
|
|
32956
34347
|
if (!String(e).includes("fetch failed") && !String(e).includes("TimeoutError")) throw e;
|
|
32957
34348
|
}
|
|
32958
|
-
const cli =
|
|
32959
|
-
if (!
|
|
34349
|
+
const cli = resolve12(installRuntime(home), "dist/cli.js");
|
|
34350
|
+
if (!existsSync9(cli))
|
|
32960
34351
|
throw new Error("Run npm run build before starting the background supervisor");
|
|
32961
|
-
const log =
|
|
34352
|
+
const log = openSync4(resolve12(home, "supervisor.log"), "a", 384);
|
|
32962
34353
|
const child = spawn4(process.execPath, ["--no-warnings", cli, "serve", "--home", home], {
|
|
32963
34354
|
detached: true,
|
|
32964
34355
|
stdio: ["ignore", log, log],
|
|
32965
34356
|
env: process.env
|
|
32966
34357
|
});
|
|
32967
|
-
|
|
34358
|
+
closeSync4(log);
|
|
32968
34359
|
child.unref();
|
|
32969
34360
|
for (let i = 0; i < 50; i++) {
|
|
32970
34361
|
await new Promise((r) => setTimeout(r, 100));
|
|
@@ -32977,7 +34368,7 @@ ${result.mcp.status === "print" ? "\nMCP install command:\n" + result.mcp.shell
|
|
|
32977
34368
|
} catch {
|
|
32978
34369
|
}
|
|
32979
34370
|
}
|
|
32980
|
-
throw new Error(`Supervisor did not start. Read ${
|
|
34371
|
+
throw new Error(`Supervisor did not start. Read ${resolve12(home, "supervisor.log")}`);
|
|
32981
34372
|
}
|
|
32982
34373
|
if (cmd === "stop") {
|
|
32983
34374
|
const c = loadConfig(home), url = `http://127.0.0.1:${c.port}`;
|
|
@@ -33011,7 +34402,7 @@ ${result.mcp.status === "print" ? "\nMCP install command:\n" + result.mcp.shell
|
|
|
33011
34402
|
return;
|
|
33012
34403
|
}
|
|
33013
34404
|
if (cmd === "mcp-config") {
|
|
33014
|
-
const server =
|
|
34405
|
+
const server = resolve12(installRuntime(home), "dist/mcp.js");
|
|
33015
34406
|
console.log(
|
|
33016
34407
|
`[mcp_servers.marionette]
|
|
33017
34408
|
command = ${JSON.stringify(process.execPath)}
|
|
@@ -33023,11 +34414,11 @@ args = ["--no-warnings", ${JSON.stringify(server)}, "--home", ${JSON.stringify(h
|
|
|
33023
34414
|
if (cmd === "call") {
|
|
33024
34415
|
const action = args[1];
|
|
33025
34416
|
if (!action) throw new Error("Provide an action; see help");
|
|
33026
|
-
const input = flag("file") ? JSON.parse(
|
|
33027
|
-
if (flag("lease")) input.lease = JSON.parse(
|
|
34417
|
+
const input = flag("file") ? JSON.parse(readFileSync10(flag("file"), "utf8")) : flag("json") ? JSON.parse(flag("json")) : {};
|
|
34418
|
+
if (flag("lease")) input.lease = JSON.parse(readFileSync10(flag("lease"), "utf8"));
|
|
33028
34419
|
const result = await call(home, action, input);
|
|
33029
34420
|
if (flag("save-lease") && result.lease) {
|
|
33030
|
-
|
|
34421
|
+
writeFileSync8(flag("save-lease"), JSON.stringify(result.lease, null, 2) + "\n", {
|
|
33031
34422
|
mode: 384
|
|
33032
34423
|
});
|
|
33033
34424
|
const { lease, ...rest } = result;
|