@corenel/sidecar 0.3.2 → 0.4.1
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/LICENSE +93 -0
- package/dist/cli.js +1016 -384
- package/package.json +7 -4
package/dist/cli.js
CHANGED
|
@@ -1184,8 +1184,8 @@ var require_stringifyString = __commonJS({
|
|
|
1184
1184
|
case "u":
|
|
1185
1185
|
{
|
|
1186
1186
|
str8 += json.slice(start, i2);
|
|
1187
|
-
const
|
|
1188
|
-
switch (
|
|
1187
|
+
const code2 = json.substr(i2 + 2, 4);
|
|
1188
|
+
switch (code2) {
|
|
1189
1189
|
case "0000":
|
|
1190
1190
|
str8 += "\\0";
|
|
1191
1191
|
break;
|
|
@@ -1211,8 +1211,8 @@ var require_stringifyString = __commonJS({
|
|
|
1211
1211
|
str8 += "\\P";
|
|
1212
1212
|
break;
|
|
1213
1213
|
default:
|
|
1214
|
-
if (
|
|
1215
|
-
str8 += "\\x" +
|
|
1214
|
+
if (code2.substr(0, 2) === "00")
|
|
1215
|
+
str8 += "\\x" + code2.substr(2);
|
|
1216
1216
|
else
|
|
1217
1217
|
str8 += json.substr(i2, 6);
|
|
1218
1218
|
}
|
|
@@ -3711,22 +3711,22 @@ var require_errors = __commonJS({
|
|
|
3711
3711
|
"../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/dist/errors.js"(exports) {
|
|
3712
3712
|
"use strict";
|
|
3713
3713
|
var YAMLError = class extends Error {
|
|
3714
|
-
constructor(name, pos,
|
|
3714
|
+
constructor(name, pos, code2, message) {
|
|
3715
3715
|
super();
|
|
3716
3716
|
this.name = name;
|
|
3717
|
-
this.code =
|
|
3717
|
+
this.code = code2;
|
|
3718
3718
|
this.message = message;
|
|
3719
3719
|
this.pos = pos;
|
|
3720
3720
|
}
|
|
3721
3721
|
};
|
|
3722
3722
|
var YAMLParseError = class extends YAMLError {
|
|
3723
|
-
constructor(pos,
|
|
3724
|
-
super("YAMLParseError", pos,
|
|
3723
|
+
constructor(pos, code2, message) {
|
|
3724
|
+
super("YAMLParseError", pos, code2, message);
|
|
3725
3725
|
}
|
|
3726
3726
|
};
|
|
3727
3727
|
var YAMLWarning = class extends YAMLError {
|
|
3728
|
-
constructor(pos,
|
|
3729
|
-
super("YAMLWarning", pos,
|
|
3728
|
+
constructor(pos, code2, message) {
|
|
3729
|
+
super("YAMLWarning", pos, code2, message);
|
|
3730
3730
|
}
|
|
3731
3731
|
};
|
|
3732
3732
|
var prettifyError = (src, lc) => (error) => {
|
|
@@ -4632,7 +4632,7 @@ var require_resolve_flow_scalar = __commonJS({
|
|
|
4632
4632
|
const { offset, type, source, end } = scalar;
|
|
4633
4633
|
let _type;
|
|
4634
4634
|
let value;
|
|
4635
|
-
const _onError = (rel,
|
|
4635
|
+
const _onError = (rel, code2, msg) => onError(offset + rel, code2, msg);
|
|
4636
4636
|
switch (type) {
|
|
4637
4637
|
case "scalar":
|
|
4638
4638
|
_type = Scalar.Scalar.PLAIN;
|
|
@@ -4827,9 +4827,9 @@ var require_resolve_flow_scalar = __commonJS({
|
|
|
4827
4827
|
function parseCharCode(source, offset, length, onError) {
|
|
4828
4828
|
const cc = source.substr(offset, length);
|
|
4829
4829
|
const ok = cc.length === length && /^[0-9a-fA-F]+$/.test(cc);
|
|
4830
|
-
const
|
|
4830
|
+
const code2 = ok ? parseInt(cc, 16) : NaN;
|
|
4831
4831
|
try {
|
|
4832
|
-
return String.fromCodePoint(
|
|
4832
|
+
return String.fromCodePoint(code2);
|
|
4833
4833
|
} catch {
|
|
4834
4834
|
const raw = source.substr(offset - 2, length + 2);
|
|
4835
4835
|
onError(offset - 2, "BAD_DQ_ESCAPE", `Invalid escape sequence ${raw}`);
|
|
@@ -5151,12 +5151,12 @@ var require_composer = __commonJS({
|
|
|
5151
5151
|
this.prelude = [];
|
|
5152
5152
|
this.errors = [];
|
|
5153
5153
|
this.warnings = [];
|
|
5154
|
-
this.onError = (source,
|
|
5154
|
+
this.onError = (source, code2, message, warning) => {
|
|
5155
5155
|
const pos = getErrorPos(source);
|
|
5156
5156
|
if (warning)
|
|
5157
|
-
this.warnings.push(new errors.YAMLWarning(pos,
|
|
5157
|
+
this.warnings.push(new errors.YAMLWarning(pos, code2, message));
|
|
5158
5158
|
else
|
|
5159
|
-
this.errors.push(new errors.YAMLParseError(pos,
|
|
5159
|
+
this.errors.push(new errors.YAMLParseError(pos, code2, message));
|
|
5160
5160
|
};
|
|
5161
5161
|
this.directives = new directives.Directives({ version: options.version || "1.2" });
|
|
5162
5162
|
this.options = options;
|
|
@@ -5318,12 +5318,12 @@ var require_cst_scalar = __commonJS({
|
|
|
5318
5318
|
var stringifyString = require_stringifyString();
|
|
5319
5319
|
function resolveAsScalar(token, strict = true, onError) {
|
|
5320
5320
|
if (token) {
|
|
5321
|
-
const _onError = (pos,
|
|
5321
|
+
const _onError = (pos, code2, message) => {
|
|
5322
5322
|
const offset = typeof pos === "number" ? pos : Array.isArray(pos) ? pos[0] : pos.offset;
|
|
5323
5323
|
if (onError)
|
|
5324
|
-
onError(offset,
|
|
5324
|
+
onError(offset, code2, message);
|
|
5325
5325
|
else
|
|
5326
|
-
throw new errors.YAMLParseError([offset, offset + 1],
|
|
5326
|
+
throw new errors.YAMLParseError([offset, offset + 1], code2, message);
|
|
5327
5327
|
};
|
|
5328
5328
|
switch (token.type) {
|
|
5329
5329
|
case "scalar":
|
|
@@ -6904,14 +6904,14 @@ var require_parser = __commonJS({
|
|
|
6904
6904
|
case "scalar":
|
|
6905
6905
|
case "single-quoted-scalar":
|
|
6906
6906
|
case "double-quoted-scalar": {
|
|
6907
|
-
const
|
|
6907
|
+
const fs15 = this.flowScalar(this.type);
|
|
6908
6908
|
if (atNextItem || it2.value) {
|
|
6909
|
-
map.items.push({ start, key:
|
|
6909
|
+
map.items.push({ start, key: fs15, sep: [] });
|
|
6910
6910
|
this.onKeyLine = true;
|
|
6911
6911
|
} else if (it2.sep) {
|
|
6912
|
-
this.stack.push(
|
|
6912
|
+
this.stack.push(fs15);
|
|
6913
6913
|
} else {
|
|
6914
|
-
Object.assign(it2, { key:
|
|
6914
|
+
Object.assign(it2, { key: fs15, sep: [] });
|
|
6915
6915
|
this.onKeyLine = true;
|
|
6916
6916
|
}
|
|
6917
6917
|
return;
|
|
@@ -7039,13 +7039,13 @@ var require_parser = __commonJS({
|
|
|
7039
7039
|
case "scalar":
|
|
7040
7040
|
case "single-quoted-scalar":
|
|
7041
7041
|
case "double-quoted-scalar": {
|
|
7042
|
-
const
|
|
7042
|
+
const fs15 = this.flowScalar(this.type);
|
|
7043
7043
|
if (!it2 || it2.value)
|
|
7044
|
-
fc.items.push({ start: [], key:
|
|
7044
|
+
fc.items.push({ start: [], key: fs15, sep: [] });
|
|
7045
7045
|
else if (it2.sep)
|
|
7046
|
-
this.stack.push(
|
|
7046
|
+
this.stack.push(fs15);
|
|
7047
7047
|
else
|
|
7048
|
-
Object.assign(it2, { key:
|
|
7048
|
+
Object.assign(it2, { key: fs15, sep: [] });
|
|
7049
7049
|
return;
|
|
7050
7050
|
}
|
|
7051
7051
|
case "flow-map-end":
|
|
@@ -12248,11 +12248,11 @@ var require_compiler = __commonJS({
|
|
|
12248
12248
|
_proto._popBuffer = function _popBuffer() {
|
|
12249
12249
|
this.buffer = this.bufferStack.pop();
|
|
12250
12250
|
};
|
|
12251
|
-
_proto._emit = function _emit(
|
|
12252
|
-
this.codebuf.push(
|
|
12251
|
+
_proto._emit = function _emit(code2) {
|
|
12252
|
+
this.codebuf.push(code2);
|
|
12253
12253
|
};
|
|
12254
|
-
_proto._emitLine = function _emitLine(
|
|
12255
|
-
this._emit(
|
|
12254
|
+
_proto._emitLine = function _emitLine(code2) {
|
|
12255
|
+
this._emit(code2 + "\n");
|
|
12256
12256
|
};
|
|
12257
12257
|
_proto._emitLines = function _emitLines() {
|
|
12258
12258
|
var _this = this;
|
|
@@ -13268,7 +13268,7 @@ var require_filters = __commonJS({
|
|
|
13268
13268
|
return r2.copySafeness(str8, res);
|
|
13269
13269
|
}
|
|
13270
13270
|
_exports.indent = indent;
|
|
13271
|
-
function
|
|
13271
|
+
function join16(arr, del2, attr) {
|
|
13272
13272
|
del2 = del2 || "";
|
|
13273
13273
|
if (attr) {
|
|
13274
13274
|
arr = lib.map(arr, function(v2) {
|
|
@@ -13277,7 +13277,7 @@ var require_filters = __commonJS({
|
|
|
13277
13277
|
}
|
|
13278
13278
|
return arr.join(del2);
|
|
13279
13279
|
}
|
|
13280
|
-
_exports.join =
|
|
13280
|
+
_exports.join = join16;
|
|
13281
13281
|
function last(arr) {
|
|
13282
13282
|
return arr[arr.length - 1];
|
|
13283
13283
|
}
|
|
@@ -13711,7 +13711,7 @@ var require_node_loaders = __commonJS({
|
|
|
13711
13711
|
};
|
|
13712
13712
|
return _setPrototypeOf(o2, p2);
|
|
13713
13713
|
}
|
|
13714
|
-
var
|
|
13714
|
+
var fs15 = __require("fs");
|
|
13715
13715
|
var path = __require("path");
|
|
13716
13716
|
var Loader2 = require_loader();
|
|
13717
13717
|
var _require = require_precompiled_loader();
|
|
@@ -13740,7 +13740,7 @@ var require_node_loaders = __commonJS({
|
|
|
13740
13740
|
} catch (e2) {
|
|
13741
13741
|
throw new Error("watch requires chokidar to be installed");
|
|
13742
13742
|
}
|
|
13743
|
-
var paths = _this.searchPaths.filter(
|
|
13743
|
+
var paths = _this.searchPaths.filter(fs15.existsSync);
|
|
13744
13744
|
var watcher = chokidar.watch(paths);
|
|
13745
13745
|
watcher.on("all", function(event, fullname) {
|
|
13746
13746
|
fullname = path.resolve(fullname);
|
|
@@ -13761,7 +13761,7 @@ var require_node_loaders = __commonJS({
|
|
|
13761
13761
|
for (var i2 = 0; i2 < paths.length; i2++) {
|
|
13762
13762
|
var basePath = path.resolve(paths[i2]);
|
|
13763
13763
|
var p2 = path.resolve(paths[i2], name);
|
|
13764
|
-
if (p2.indexOf(basePath) === 0 &&
|
|
13764
|
+
if (p2.indexOf(basePath) === 0 && fs15.existsSync(p2)) {
|
|
13765
13765
|
fullpath = p2;
|
|
13766
13766
|
break;
|
|
13767
13767
|
}
|
|
@@ -13771,7 +13771,7 @@ var require_node_loaders = __commonJS({
|
|
|
13771
13771
|
}
|
|
13772
13772
|
this.pathsToNames[fullpath] = name;
|
|
13773
13773
|
var source = {
|
|
13774
|
-
src:
|
|
13774
|
+
src: fs15.readFileSync(fullpath, "utf-8"),
|
|
13775
13775
|
path: fullpath,
|
|
13776
13776
|
noCache: this.noCache
|
|
13777
13777
|
};
|
|
@@ -13823,7 +13823,7 @@ var require_node_loaders = __commonJS({
|
|
|
13823
13823
|
}
|
|
13824
13824
|
this.pathsToNames[fullpath] = name;
|
|
13825
13825
|
var source = {
|
|
13826
|
-
src:
|
|
13826
|
+
src: fs15.readFileSync(fullpath, "utf-8"),
|
|
13827
13827
|
path: fullpath,
|
|
13828
13828
|
noCache: this.noCache
|
|
13829
13829
|
};
|
|
@@ -14580,7 +14580,7 @@ var require_precompile_global = __commonJS({
|
|
|
14580
14580
|
var require_precompile = __commonJS({
|
|
14581
14581
|
"../../node_modules/.pnpm/nunjucks@3.2.4/node_modules/nunjucks/src/precompile.js"(exports, module) {
|
|
14582
14582
|
"use strict";
|
|
14583
|
-
var
|
|
14583
|
+
var fs15 = __require("fs");
|
|
14584
14584
|
var path = __require("path");
|
|
14585
14585
|
var _require = require_lib();
|
|
14586
14586
|
var _prettifyError = _require._prettifyError;
|
|
@@ -14613,14 +14613,14 @@ var require_precompile = __commonJS({
|
|
|
14613
14613
|
if (opts.isString) {
|
|
14614
14614
|
return precompileString(input, opts);
|
|
14615
14615
|
}
|
|
14616
|
-
var pathStats =
|
|
14616
|
+
var pathStats = fs15.existsSync(input) && fs15.statSync(input);
|
|
14617
14617
|
var precompiled = [];
|
|
14618
14618
|
var templates = [];
|
|
14619
14619
|
function addTemplates(dir) {
|
|
14620
|
-
|
|
14620
|
+
fs15.readdirSync(dir).forEach(function(file) {
|
|
14621
14621
|
var filepath = path.join(dir, file);
|
|
14622
14622
|
var subpath = filepath.substr(path.join(input, "/").length);
|
|
14623
|
-
var stat2 =
|
|
14623
|
+
var stat2 = fs15.statSync(filepath);
|
|
14624
14624
|
if (stat2 && stat2.isDirectory()) {
|
|
14625
14625
|
subpath += "/";
|
|
14626
14626
|
if (!match(subpath, opts.exclude)) {
|
|
@@ -14632,13 +14632,13 @@ var require_precompile = __commonJS({
|
|
|
14632
14632
|
});
|
|
14633
14633
|
}
|
|
14634
14634
|
if (pathStats.isFile()) {
|
|
14635
|
-
precompiled.push(_precompile(
|
|
14635
|
+
precompiled.push(_precompile(fs15.readFileSync(input, "utf-8"), opts.name || input, env));
|
|
14636
14636
|
} else if (pathStats.isDirectory()) {
|
|
14637
14637
|
addTemplates(input);
|
|
14638
14638
|
for (var i2 = 0; i2 < templates.length; i2++) {
|
|
14639
14639
|
var name = templates[i2].replace(path.join(input, "/"), "");
|
|
14640
14640
|
try {
|
|
14641
|
-
precompiled.push(_precompile(
|
|
14641
|
+
precompiled.push(_precompile(fs15.readFileSync(templates[i2], "utf-8"), name, env));
|
|
14642
14642
|
} catch (e2) {
|
|
14643
14643
|
if (opts.force) {
|
|
14644
14644
|
console.error(e2);
|
|
@@ -18032,8 +18032,8 @@ var require_lib3 = __commonJS({
|
|
|
18032
18032
|
stream.end();
|
|
18033
18033
|
}
|
|
18034
18034
|
}
|
|
18035
|
-
fetch3.isRedirect = function(
|
|
18036
|
-
return
|
|
18035
|
+
fetch3.isRedirect = function(code2) {
|
|
18036
|
+
return code2 === 301 || code2 === 302 || code2 === 303 || code2 === 307 || code2 === 308;
|
|
18037
18037
|
};
|
|
18038
18038
|
fetch3.Promise = global.Promise;
|
|
18039
18039
|
module.exports = exports = fetch3;
|
|
@@ -21357,7 +21357,7 @@ __export(fileFromPath_exports, {
|
|
|
21357
21357
|
fileFromPathSync: () => fileFromPathSync,
|
|
21358
21358
|
isFile: () => isFile
|
|
21359
21359
|
});
|
|
21360
|
-
import { statSync, createReadStream, promises as
|
|
21360
|
+
import { statSync, createReadStream, promises as fs9 } from "fs";
|
|
21361
21361
|
import { basename as basename2 } from "path";
|
|
21362
21362
|
function createFileFromPath(path, { mtimeMs, size }, filenameOrOptions, options = {}) {
|
|
21363
21363
|
let filename;
|
|
@@ -21380,7 +21380,7 @@ function fileFromPathSync(path, filenameOrOptions, options = {}) {
|
|
|
21380
21380
|
return createFileFromPath(path, stats, filenameOrOptions, options);
|
|
21381
21381
|
}
|
|
21382
21382
|
async function fileFromPath2(path, filenameOrOptions, options) {
|
|
21383
|
-
const stats = await
|
|
21383
|
+
const stats = await fs9.stat(path);
|
|
21384
21384
|
return createFileFromPath(path, stats, filenameOrOptions, options);
|
|
21385
21385
|
}
|
|
21386
21386
|
var import_node_domexception, __classPrivateFieldSet4, __classPrivateFieldGet5, _FileFromPath_path, _FileFromPath_start, MESSAGE, FileFromPath;
|
|
@@ -21421,7 +21421,7 @@ var init_fileFromPath = __esm({
|
|
|
21421
21421
|
});
|
|
21422
21422
|
}
|
|
21423
21423
|
async *stream() {
|
|
21424
|
-
const { mtimeMs } = await
|
|
21424
|
+
const { mtimeMs } = await fs9.stat(__classPrivateFieldGet5(this, _FileFromPath_path, "f"));
|
|
21425
21425
|
if (mtimeMs > this.lastModified) {
|
|
21426
21426
|
throw new import_node_domexception.default(MESSAGE, "NotReadableError");
|
|
21427
21427
|
}
|
|
@@ -21440,7 +21440,7 @@ var init_fileFromPath = __esm({
|
|
|
21440
21440
|
});
|
|
21441
21441
|
|
|
21442
21442
|
// src/cli.ts
|
|
21443
|
-
import { join as
|
|
21443
|
+
import { join as join15 } from "node:path";
|
|
21444
21444
|
import { readFileSync } from "node:fs";
|
|
21445
21445
|
import { spawn as spawn9 } from "node:child_process";
|
|
21446
21446
|
|
|
@@ -21692,6 +21692,7 @@ var SidecarServer = class {
|
|
|
21692
21692
|
case "session-destroy":
|
|
21693
21693
|
case "session-commit":
|
|
21694
21694
|
case "session-snapshots":
|
|
21695
|
+
case "session-snapshot-usage":
|
|
21695
21696
|
case "session-watch":
|
|
21696
21697
|
case "session-unwatch":
|
|
21697
21698
|
if (!this.authed) {
|
|
@@ -21877,6 +21878,11 @@ var SidecarServer = class {
|
|
|
21877
21878
|
this.ch.send({ t: "session-snapshots-result", id, snapshots });
|
|
21878
21879
|
return;
|
|
21879
21880
|
}
|
|
21881
|
+
case "session-snapshot-usage": {
|
|
21882
|
+
const usage = sessions.snapshotUsage ? await sessions.snapshotUsage() : { usedBytes: 0, budgetBytes: 0, count: 0 };
|
|
21883
|
+
this.ch.send({ t: "session-snapshot-usage-result", id, usage });
|
|
21884
|
+
return;
|
|
21885
|
+
}
|
|
21880
21886
|
case "session-watch": {
|
|
21881
21887
|
if (this.sessionWatch.has(m2.sessionId)) {
|
|
21882
21888
|
this.ch.send({ t: "session-ok", id });
|
|
@@ -22035,7 +22041,11 @@ var SidecarServer = class {
|
|
|
22035
22041
|
}, {
|
|
22036
22042
|
...m2.root ? { root: m2.root } : {},
|
|
22037
22043
|
...m2.modeId ? { modeId: m2.modeId } : {},
|
|
22038
|
-
...m2.resume ? { resume: m2.resume } : {}
|
|
22044
|
+
...m2.resume ? { resume: m2.resume } : {},
|
|
22045
|
+
/* Passed through untouched. The daemon does not decide WHICH servers a
|
|
22046
|
+
* user has -- the client does -- and the ACP client drops them if the
|
|
22047
|
+
* agent never advertised mcpCapabilities.http. */
|
|
22048
|
+
...m2.mcpServers?.length ? { mcpServers: m2.mcpServers } : {}
|
|
22039
22049
|
});
|
|
22040
22050
|
this.ch.send({ t: "acp-started", id: m2.id, ...started });
|
|
22041
22051
|
} catch (err) {
|
|
@@ -22331,10 +22341,18 @@ var SidecarServer = class {
|
|
|
22331
22341
|
ask = (questions) => this.hostCall("ask", "ask", [questions]).then((v2) => v2, () => null);
|
|
22332
22342
|
};
|
|
22333
22343
|
|
|
22334
|
-
//
|
|
22344
|
+
// ../harness/sidecar/wsChannel.ts
|
|
22335
22345
|
function wsChannel(ws) {
|
|
22336
22346
|
let handler = null;
|
|
22337
22347
|
let closeHandler = null;
|
|
22348
|
+
const pending = [];
|
|
22349
|
+
const flushPending = () => {
|
|
22350
|
+
if (ws.readyState !== ws.OPEN) return;
|
|
22351
|
+
while (pending.length > 0) {
|
|
22352
|
+
ws.send(JSON.stringify(pending.shift()));
|
|
22353
|
+
}
|
|
22354
|
+
};
|
|
22355
|
+
ws.on("open", flushPending);
|
|
22338
22356
|
ws.on("message", (data) => {
|
|
22339
22357
|
if (!handler) return;
|
|
22340
22358
|
try {
|
|
@@ -22348,7 +22366,11 @@ function wsChannel(ws) {
|
|
|
22348
22366
|
});
|
|
22349
22367
|
return {
|
|
22350
22368
|
send(msg) {
|
|
22351
|
-
if (ws.readyState === ws.OPEN)
|
|
22369
|
+
if (ws.readyState === ws.OPEN) {
|
|
22370
|
+
ws.send(JSON.stringify(msg));
|
|
22371
|
+
} else if (ws.readyState === ws.CONNECTING) {
|
|
22372
|
+
pending.push(msg);
|
|
22373
|
+
}
|
|
22352
22374
|
},
|
|
22353
22375
|
onMessage(h2) {
|
|
22354
22376
|
handler = h2;
|
|
@@ -22489,16 +22511,79 @@ keeps serving your browser, so the restart would look successful and change noth
|
|
|
22489
22511
|
}
|
|
22490
22512
|
}
|
|
22491
22513
|
|
|
22492
|
-
// src/
|
|
22514
|
+
// src/pidFile.ts
|
|
22493
22515
|
import { promises as fs } from "node:fs";
|
|
22516
|
+
import { join } from "node:path";
|
|
22517
|
+
function pidPath(dir) {
|
|
22518
|
+
return join(dir, "daemon.pid");
|
|
22519
|
+
}
|
|
22520
|
+
function isAlive(pid, kill = process.kill) {
|
|
22521
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
22522
|
+
try {
|
|
22523
|
+
kill(pid, 0);
|
|
22524
|
+
return true;
|
|
22525
|
+
} catch (e2) {
|
|
22526
|
+
return e2?.code === "EPERM";
|
|
22527
|
+
}
|
|
22528
|
+
}
|
|
22529
|
+
async function readPidFile(dir) {
|
|
22530
|
+
let raw;
|
|
22531
|
+
try {
|
|
22532
|
+
raw = await fs.readFile(pidPath(dir), "utf8");
|
|
22533
|
+
} catch {
|
|
22534
|
+
return null;
|
|
22535
|
+
}
|
|
22536
|
+
try {
|
|
22537
|
+
const o2 = JSON.parse(raw);
|
|
22538
|
+
if (!o2 || typeof o2 !== "object") return null;
|
|
22539
|
+
const r2 = o2;
|
|
22540
|
+
if (typeof r2.pid !== "number" || typeof r2.port !== "number") return null;
|
|
22541
|
+
return { pid: r2.pid, port: r2.port, startedAt: typeof r2.startedAt === "number" ? r2.startedAt : 0 };
|
|
22542
|
+
} catch {
|
|
22543
|
+
return null;
|
|
22544
|
+
}
|
|
22545
|
+
}
|
|
22546
|
+
async function assertStateDirFree(dir, now2 = Date.now()) {
|
|
22547
|
+
const rec5 = await readPidFile(dir);
|
|
22548
|
+
if (!rec5 || !isAlive(rec5.pid)) return;
|
|
22549
|
+
const mins = rec5.startedAt ? Math.max(0, Math.round((now2 - rec5.startedAt) / 6e4)) : null;
|
|
22550
|
+
const age = mins === null ? "" : ` (started ${mins}m ago)`;
|
|
22551
|
+
throw new Error(
|
|
22552
|
+
`another sidecar is already using this state dir \u2014 pid ${rec5.pid} on port ${rec5.port}${age}.
|
|
22553
|
+
Two daemons sharing a state dir share their IDENTITY: they read the same daemon.id and
|
|
22554
|
+
advertise the same sidecarId, so a paired browser sees one sidecar, one sync baseline and
|
|
22555
|
+
one working folder for what are really several machines' worth of state.
|
|
22556
|
+
Run this one with its own --state-dir <dir>, or stop the other first.`
|
|
22557
|
+
);
|
|
22558
|
+
}
|
|
22559
|
+
async function writePidFile(dir, port, pid = process.pid, now2 = Date.now()) {
|
|
22560
|
+
await fs.mkdir(dir, { recursive: true });
|
|
22561
|
+
const rec5 = { pid, port, startedAt: now2 };
|
|
22562
|
+
await fs.writeFile(pidPath(dir), `${JSON.stringify(rec5)}
|
|
22563
|
+
`, { mode: 384 });
|
|
22564
|
+
}
|
|
22565
|
+
async function clearPidFile(dir, pid = process.pid) {
|
|
22566
|
+
const rec5 = await readPidFile(dir);
|
|
22567
|
+
if (!rec5 || rec5.pid !== pid) return;
|
|
22568
|
+
try {
|
|
22569
|
+
await fs.rm(pidPath(dir), { force: true });
|
|
22570
|
+
} catch {
|
|
22571
|
+
}
|
|
22572
|
+
}
|
|
22573
|
+
|
|
22574
|
+
// src/token.ts
|
|
22575
|
+
import { promises as fs2 } from "node:fs";
|
|
22494
22576
|
import { randomBytes } from "node:crypto";
|
|
22495
22577
|
import { homedir } from "node:os";
|
|
22496
|
-
import { join } from "node:path";
|
|
22578
|
+
import { join as join2 } from "node:path";
|
|
22497
22579
|
function corenelDir() {
|
|
22498
|
-
return
|
|
22580
|
+
return join2(homedir(), stateDirName());
|
|
22581
|
+
}
|
|
22582
|
+
function sidecarSubdir(stateDir = corenelDir()) {
|
|
22583
|
+
return join2(stateDir, "sidecar");
|
|
22499
22584
|
}
|
|
22500
22585
|
function tokenPath() {
|
|
22501
|
-
return
|
|
22586
|
+
return join2(sidecarSubdir(), "daemon.token");
|
|
22502
22587
|
}
|
|
22503
22588
|
function isEnoent(err) {
|
|
22504
22589
|
return typeof err === "object" && err !== null && err.code === "ENOENT";
|
|
@@ -22506,16 +22591,16 @@ function isEnoent(err) {
|
|
|
22506
22591
|
async function loadOrCreateToken() {
|
|
22507
22592
|
const path = tokenPath();
|
|
22508
22593
|
try {
|
|
22509
|
-
const existing = (await
|
|
22594
|
+
const existing = (await fs2.readFile(path, "utf8")).trim();
|
|
22510
22595
|
if (existing) return existing;
|
|
22511
22596
|
} catch (err) {
|
|
22512
22597
|
if (!isEnoent(err)) throw err;
|
|
22513
22598
|
}
|
|
22514
|
-
await
|
|
22599
|
+
await fs2.mkdir(sidecarSubdir(), { recursive: true });
|
|
22515
22600
|
const token = randomBytes(32).toString("hex");
|
|
22516
|
-
await
|
|
22601
|
+
await fs2.writeFile(path, token + "\n", { mode: 384 });
|
|
22517
22602
|
try {
|
|
22518
|
-
await
|
|
22603
|
+
await fs2.chmod(path, 384);
|
|
22519
22604
|
} catch {
|
|
22520
22605
|
}
|
|
22521
22606
|
return token;
|
|
@@ -23124,10 +23209,10 @@ function makeFileMirrorSource(deps) {
|
|
|
23124
23209
|
};
|
|
23125
23210
|
const readText = async (path) => {
|
|
23126
23211
|
if (!path) return "";
|
|
23127
|
-
const
|
|
23128
|
-
if (!
|
|
23212
|
+
const fs15 = await deps.files();
|
|
23213
|
+
if (!fs15) return "";
|
|
23129
23214
|
try {
|
|
23130
|
-
return await
|
|
23215
|
+
return await fs15.read(path);
|
|
23131
23216
|
} catch {
|
|
23132
23217
|
return "";
|
|
23133
23218
|
}
|
|
@@ -23155,28 +23240,28 @@ function makeFileMirrorSource(deps) {
|
|
|
23155
23240
|
}
|
|
23156
23241
|
|
|
23157
23242
|
// src/auth/store.ts
|
|
23158
|
-
import { promises as
|
|
23243
|
+
import { promises as fs3 } from "node:fs";
|
|
23159
23244
|
import { homedir as homedir2 } from "node:os";
|
|
23160
|
-
import { join as
|
|
23245
|
+
import { join as join3 } from "node:path";
|
|
23161
23246
|
var TOKEN_ENV = "CORENEL_TOKEN";
|
|
23162
23247
|
function authDir(stateDir) {
|
|
23163
|
-
return stateDir ??
|
|
23248
|
+
return stateDir ?? join3(homedir2(), stateDirName());
|
|
23164
23249
|
}
|
|
23165
23250
|
function credentialPath(stateDir) {
|
|
23166
|
-
return
|
|
23251
|
+
return join3(authDir(stateDir), "auth.json");
|
|
23167
23252
|
}
|
|
23168
23253
|
async function saveCredential(cred, stateDir) {
|
|
23169
23254
|
const path = credentialPath(stateDir);
|
|
23170
|
-
await
|
|
23171
|
-
await
|
|
23255
|
+
await fs3.mkdir(authDir(stateDir), { recursive: true });
|
|
23256
|
+
await fs3.writeFile(path, JSON.stringify(cred, null, 2) + "\n", { mode: 384 });
|
|
23172
23257
|
try {
|
|
23173
|
-
await
|
|
23258
|
+
await fs3.chmod(path, 384);
|
|
23174
23259
|
} catch {
|
|
23175
23260
|
}
|
|
23176
23261
|
}
|
|
23177
23262
|
async function loadCredential(stateDir) {
|
|
23178
23263
|
try {
|
|
23179
|
-
const raw = await
|
|
23264
|
+
const raw = await fs3.readFile(credentialPath(stateDir), "utf8");
|
|
23180
23265
|
const parsed = JSON.parse(raw);
|
|
23181
23266
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
23182
23267
|
const c2 = parsed;
|
|
@@ -23195,7 +23280,7 @@ async function loadCredential(stateDir) {
|
|
|
23195
23280
|
}
|
|
23196
23281
|
}
|
|
23197
23282
|
async function clearCredential(stateDir) {
|
|
23198
|
-
await
|
|
23283
|
+
await fs3.rm(credentialPath(stateDir), { force: true });
|
|
23199
23284
|
}
|
|
23200
23285
|
async function resolveCredential(env = process.env, stateDir, now2 = Date.now) {
|
|
23201
23286
|
const fromEnv = env[TOKEN_ENV]?.trim();
|
|
@@ -23247,7 +23332,7 @@ function makeControlPlane(deps) {
|
|
|
23247
23332
|
}
|
|
23248
23333
|
|
|
23249
23334
|
// src/tools.ts
|
|
23250
|
-
import { promises as
|
|
23335
|
+
import { promises as fs4 } from "node:fs";
|
|
23251
23336
|
import { spawn } from "node:child_process";
|
|
23252
23337
|
import { resolve, relative, isAbsolute, sep, dirname } from "node:path";
|
|
23253
23338
|
function withinRoot(root, abs) {
|
|
@@ -23264,11 +23349,11 @@ function confine(roots, rootArg, p2) {
|
|
|
23264
23349
|
return abs;
|
|
23265
23350
|
}
|
|
23266
23351
|
async function assertRealWithinRoot(roots, abs) {
|
|
23267
|
-
const realRoots = await Promise.all(roots.map((r2) =>
|
|
23352
|
+
const realRoots = await Promise.all(roots.map((r2) => fs4.realpath(r2)));
|
|
23268
23353
|
let probe = abs;
|
|
23269
23354
|
for (; ; ) {
|
|
23270
23355
|
try {
|
|
23271
|
-
const real = await
|
|
23356
|
+
const real = await fs4.realpath(probe);
|
|
23272
23357
|
if (!realRoots.some((rr2) => withinRoot(rr2, real))) throw new Error(`path escapes the sidecar roots via symlink: ${abs}`);
|
|
23273
23358
|
return;
|
|
23274
23359
|
} catch (e2) {
|
|
@@ -23296,11 +23381,11 @@ function nodeFsTools(roots) {
|
|
|
23296
23381
|
async run(args) {
|
|
23297
23382
|
const abs = confine(roots, args.root, String(args.path ?? ""));
|
|
23298
23383
|
await assertRealWithinRoot(roots, abs);
|
|
23299
|
-
const st2 = await
|
|
23384
|
+
const st2 = await fs4.stat(abs);
|
|
23300
23385
|
if (st2.size > MAX_READ_BYTES) {
|
|
23301
23386
|
throw new Error(`file too large to read (${st2.size} bytes; max ${MAX_READ_BYTES})`);
|
|
23302
23387
|
}
|
|
23303
|
-
return await
|
|
23388
|
+
return await fs4.readFile(abs, "utf8");
|
|
23304
23389
|
}
|
|
23305
23390
|
},
|
|
23306
23391
|
{
|
|
@@ -23320,9 +23405,9 @@ function nodeFsTools(roots) {
|
|
|
23320
23405
|
mutates: true,
|
|
23321
23406
|
async run(args) {
|
|
23322
23407
|
const abs = confine(roots, args.root, String(args.path ?? ""));
|
|
23323
|
-
await
|
|
23408
|
+
await fs4.mkdir(dirname(abs), { recursive: true });
|
|
23324
23409
|
await assertRealWithinRoot(roots, abs);
|
|
23325
|
-
await
|
|
23410
|
+
await fs4.writeFile(abs, String(args.content ?? ""), "utf8");
|
|
23326
23411
|
return `wrote ${args.path}`;
|
|
23327
23412
|
}
|
|
23328
23413
|
},
|
|
@@ -23339,11 +23424,11 @@ function nodeFsTools(roots) {
|
|
|
23339
23424
|
async run(args) {
|
|
23340
23425
|
const abs = confine(roots, args.root, String(args.path ?? ""));
|
|
23341
23426
|
await assertRealWithinRoot(roots, abs);
|
|
23342
|
-
const st2 = await
|
|
23427
|
+
const st2 = await fs4.stat(abs);
|
|
23343
23428
|
if (st2.size > MAX_READ_BYTES) {
|
|
23344
23429
|
throw new Error(`file too large to read (${st2.size} bytes; max ${MAX_READ_BYTES})`);
|
|
23345
23430
|
}
|
|
23346
|
-
return (await
|
|
23431
|
+
return (await fs4.readFile(abs)).toString("base64");
|
|
23347
23432
|
}
|
|
23348
23433
|
},
|
|
23349
23434
|
{
|
|
@@ -23358,7 +23443,7 @@ function nodeFsTools(roots) {
|
|
|
23358
23443
|
async run(args) {
|
|
23359
23444
|
const abs = confine(roots, args.root, String(args.path ?? "."));
|
|
23360
23445
|
await assertRealWithinRoot(roots, abs);
|
|
23361
|
-
const entries = await
|
|
23446
|
+
const entries = await fs4.readdir(abs, { withFileTypes: true });
|
|
23362
23447
|
return entries.map((e2) => e2.isDirectory() ? `${e2.name}/` : e2.name).join("\n");
|
|
23363
23448
|
}
|
|
23364
23449
|
},
|
|
@@ -23377,7 +23462,7 @@ function nodeFsTools(roots) {
|
|
|
23377
23462
|
const abs = confine(roots, args.root, String(args.path ?? ""));
|
|
23378
23463
|
await assertRealWithinRoot(roots, abs);
|
|
23379
23464
|
if (roots.some((r2) => resolve(r2) === abs)) throw new Error("refusing to delete a sidecar root itself");
|
|
23380
|
-
await
|
|
23465
|
+
await fs4.rm(abs, { recursive: true, force: true });
|
|
23381
23466
|
return `deleted ${args.path}`;
|
|
23382
23467
|
}
|
|
23383
23468
|
}
|
|
@@ -23440,9 +23525,9 @@ function runShell(command, cwd, timeout) {
|
|
|
23440
23525
|
clearTimeout(timer);
|
|
23441
23526
|
resolveOut(`Error: failed to start command: ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
23442
23527
|
});
|
|
23443
|
-
child.on("close", (
|
|
23528
|
+
child.on("close", (code2, signal) => {
|
|
23444
23529
|
clearTimeout(timer);
|
|
23445
|
-
const status = timedOut ? `timed out after ${timeout}ms (killed)` : `exit ${
|
|
23530
|
+
const status = timedOut ? `timed out after ${timeout}ms (killed)` : `exit ${code2 ?? "null"}${signal ? ` (signal ${signal})` : ""}`;
|
|
23446
23531
|
const body = out.trim() || "(no output)";
|
|
23447
23532
|
resolveOut(`${status}
|
|
23448
23533
|
${truncated ? "[output truncated]\n" : ""}${body}`);
|
|
@@ -23516,20 +23601,20 @@ function createNodeProcs(opts) {
|
|
|
23516
23601
|
opts.log?.(`proc ${info.procId} spawn: ${line}`);
|
|
23517
23602
|
child.stdout.on("data", (b2) => push2(entry, "out", b2.toString("utf8")));
|
|
23518
23603
|
child.stderr.on("data", (b2) => push2(entry, "err", b2.toString("utf8")));
|
|
23519
|
-
const settle = (
|
|
23604
|
+
const settle = (code2, signal, failure) => {
|
|
23520
23605
|
if (!entry.info.running) return;
|
|
23521
23606
|
if (failure) push2(entry, "err", failure);
|
|
23522
23607
|
entry.info.running = false;
|
|
23523
23608
|
entry.info.endedAt = Date.now();
|
|
23524
|
-
entry.info.exitCode =
|
|
23609
|
+
entry.info.exitCode = code2;
|
|
23525
23610
|
if (signal) entry.info.signal = signal;
|
|
23526
23611
|
entry.child = null;
|
|
23527
|
-
opts.log?.(`proc ${entry.info.procId} exit ${
|
|
23612
|
+
opts.log?.(`proc ${entry.info.procId} exit ${code2 ?? "null"}${signal ? ` (${signal})` : ""}`);
|
|
23528
23613
|
emit({ procId: entry.info.procId, kind: "exit", info: { ...entry.info } });
|
|
23529
23614
|
evictExited();
|
|
23530
23615
|
};
|
|
23531
23616
|
child.on("error", (err) => settle(null, null, `failed to start: ${err instanceof Error ? err.message : String(err)}`));
|
|
23532
|
-
child.on("close", (
|
|
23617
|
+
child.on("close", (code2, signal) => settle(code2, signal));
|
|
23533
23618
|
return { ...info };
|
|
23534
23619
|
},
|
|
23535
23620
|
list() {
|
|
@@ -23605,22 +23690,22 @@ function isNotFound(e2) {
|
|
|
23605
23690
|
const msg = String(e2?.message ?? e2);
|
|
23606
23691
|
return name === "NotFoundError" || /not\s*found|no such|enoent/i.test(msg);
|
|
23607
23692
|
}
|
|
23608
|
-
function fileServicePackageStore(
|
|
23609
|
-
const remove = (path) =>
|
|
23693
|
+
function fileServicePackageStore(fs15) {
|
|
23694
|
+
const remove = (path) => fs15.remove(path).catch((e2) => {
|
|
23610
23695
|
if (!isNotFound(e2)) throw e2;
|
|
23611
23696
|
});
|
|
23612
23697
|
return {
|
|
23613
|
-
writeFile: (path, content) =>
|
|
23698
|
+
writeFile: (path, content) => fs15.write(path, content),
|
|
23614
23699
|
// Present only when the backend supports binary — core feature-detects it,
|
|
23615
23700
|
// and advertising a method that throws is worse than not advertising it.
|
|
23616
|
-
...
|
|
23701
|
+
...fs15.writeBytes ? { writeBytes: (path, bytes) => fs15.writeBytes(path, bytes) } : {},
|
|
23617
23702
|
removeDir: remove,
|
|
23618
23703
|
removeFile: remove,
|
|
23619
|
-
readFile: (path) =>
|
|
23620
|
-
...
|
|
23704
|
+
readFile: (path) => fs15.read(path).catch(() => null),
|
|
23705
|
+
...fs15.readBytes ? { readBytes: (path) => fs15.readBytes(path) } : {},
|
|
23621
23706
|
readdir: async (path) => {
|
|
23622
23707
|
try {
|
|
23623
|
-
const nodes =
|
|
23708
|
+
const nodes = fs15.listDir ? await fs15.listDir(path) : await fs15.list(path);
|
|
23624
23709
|
return nodes.map((n2) => n2.name);
|
|
23625
23710
|
} catch {
|
|
23626
23711
|
return [];
|
|
@@ -23722,9 +23807,9 @@ var SessionPackages = class {
|
|
|
23722
23807
|
return !!this.deps.installer;
|
|
23723
23808
|
}
|
|
23724
23809
|
async filesOrThrow(sessionId) {
|
|
23725
|
-
const
|
|
23726
|
-
if (!
|
|
23727
|
-
return
|
|
23810
|
+
const fs15 = await this.deps.filesFor(sessionId);
|
|
23811
|
+
if (!fs15) throw new Error(`no such session: ${sessionId}`);
|
|
23812
|
+
return fs15;
|
|
23728
23813
|
}
|
|
23729
23814
|
installerOrThrow() {
|
|
23730
23815
|
const i2 = this.deps.installer;
|
|
@@ -23732,13 +23817,13 @@ var SessionPackages = class {
|
|
|
23732
23817
|
return i2;
|
|
23733
23818
|
}
|
|
23734
23819
|
async readLock(sessionId) {
|
|
23735
|
-
const
|
|
23736
|
-
const text = await
|
|
23820
|
+
const fs15 = await this.filesOrThrow(sessionId);
|
|
23821
|
+
const text = await fs15.read(LOCKFILE_PATH).catch(() => null);
|
|
23737
23822
|
return (text != null ? parseLock(text) : null) ?? emptyLock();
|
|
23738
23823
|
}
|
|
23739
23824
|
async writeLock(sessionId, lock) {
|
|
23740
|
-
const
|
|
23741
|
-
await
|
|
23825
|
+
const fs15 = await this.filesOrThrow(sessionId);
|
|
23826
|
+
await fs15.write(LOCKFILE_PATH, serializeLock(lock));
|
|
23742
23827
|
}
|
|
23743
23828
|
/**
|
|
23744
23829
|
* Install one package into the session and pin what resolved.
|
|
@@ -23749,11 +23834,11 @@ var SessionPackages = class {
|
|
|
23749
23834
|
*/
|
|
23750
23835
|
async install(sessionId, ref, type) {
|
|
23751
23836
|
const installer = this.installerOrThrow();
|
|
23752
|
-
const
|
|
23837
|
+
const fs15 = await this.filesOrThrow(sessionId);
|
|
23753
23838
|
const installed = await installer.install({
|
|
23754
23839
|
ref,
|
|
23755
23840
|
root: "",
|
|
23756
|
-
store: fileServicePackageStore(
|
|
23841
|
+
store: fileServicePackageStore(fs15),
|
|
23757
23842
|
...type ? { type } : {}
|
|
23758
23843
|
});
|
|
23759
23844
|
try {
|
|
@@ -23771,8 +23856,8 @@ var SessionPackages = class {
|
|
|
23771
23856
|
}
|
|
23772
23857
|
async uninstall(sessionId, ref) {
|
|
23773
23858
|
const installer = this.installerOrThrow();
|
|
23774
|
-
const
|
|
23775
|
-
await installer.uninstall({ ref, root: "", store: fileServicePackageStore(
|
|
23859
|
+
const fs15 = await this.filesOrThrow(sessionId);
|
|
23860
|
+
await installer.uninstall({ ref, root: "", store: fileServicePackageStore(fs15) });
|
|
23776
23861
|
const name = ref.replace(/@[^@/]*$/, "") || ref;
|
|
23777
23862
|
try {
|
|
23778
23863
|
await this.writeLock(sessionId, withoutPackage(await this.readLock(sessionId), name));
|
|
@@ -23789,9 +23874,9 @@ var SessionPackages = class {
|
|
|
23789
23874
|
*/
|
|
23790
23875
|
async installFromLock(sessionId) {
|
|
23791
23876
|
const installer = this.installerOrThrow();
|
|
23792
|
-
const
|
|
23877
|
+
const fs15 = await this.filesOrThrow(sessionId);
|
|
23793
23878
|
const lock = await this.readLock(sessionId);
|
|
23794
|
-
const store = fileServicePackageStore(
|
|
23879
|
+
const store = fileServicePackageStore(fs15);
|
|
23795
23880
|
const done = [];
|
|
23796
23881
|
for (const pin of lock.packages) {
|
|
23797
23882
|
done.push(await installer.install({ ref: `${pin.name}@${pin.version}`, root: "", store }));
|
|
@@ -24050,9 +24135,36 @@ var SessionManager = class {
|
|
|
24050
24135
|
import { isAbsolute as isAbsolute3, relative as relative3, resolve as resolve3 } from "node:path";
|
|
24051
24136
|
|
|
24052
24137
|
// ../tools-node/fileService.ts
|
|
24053
|
-
import { promises as
|
|
24054
|
-
import { join as
|
|
24055
|
-
var SKIP = /* @__PURE__ */ new Set([
|
|
24138
|
+
import { promises as fs5 } from "node:fs";
|
|
24139
|
+
import { join as join4, resolve as resolve2, relative as relative2, dirname as dirname2, sep as sep2, basename, isAbsolute as isAbsolute2 } from "node:path";
|
|
24140
|
+
var SKIP = /* @__PURE__ */ new Set([
|
|
24141
|
+
"node_modules",
|
|
24142
|
+
".git",
|
|
24143
|
+
"dist",
|
|
24144
|
+
"build",
|
|
24145
|
+
".next",
|
|
24146
|
+
".cache",
|
|
24147
|
+
".turbo",
|
|
24148
|
+
"dist-corenel",
|
|
24149
|
+
"test-results",
|
|
24150
|
+
"playwright-report"
|
|
24151
|
+
]);
|
|
24152
|
+
var SKIP_PATHS = /* @__PURE__ */ new Set([".claude/skills"]);
|
|
24153
|
+
var COVERAGE_REPORT_MARKERS = /* @__PURE__ */ new Set([
|
|
24154
|
+
"coverage-final.json",
|
|
24155
|
+
"lcov.info",
|
|
24156
|
+
"clover.xml",
|
|
24157
|
+
"coverage-summary.json",
|
|
24158
|
+
"prettify.js"
|
|
24159
|
+
]);
|
|
24160
|
+
async function looksLikeGeneratedCoverage(absDir) {
|
|
24161
|
+
try {
|
|
24162
|
+
const names = await fs5.readdir(absDir);
|
|
24163
|
+
return names.some((n2) => COVERAGE_REPORT_MARKERS.has(n2));
|
|
24164
|
+
} catch {
|
|
24165
|
+
return false;
|
|
24166
|
+
}
|
|
24167
|
+
}
|
|
24056
24168
|
function escapesRoot(rel, platformSep = sep2) {
|
|
24057
24169
|
if (rel === "") return false;
|
|
24058
24170
|
if (rel === ".." || rel.startsWith(".." + platformSep)) return true;
|
|
@@ -24090,11 +24202,11 @@ var NodeFileService = class {
|
|
|
24090
24202
|
* root pointing outside it. Resolve the real path (or nearest existing ancestor,
|
|
24091
24203
|
* for a not-yet-created write target) and assert it stays within the real root. */
|
|
24092
24204
|
async assertReal(a2) {
|
|
24093
|
-
const realRoot = await
|
|
24205
|
+
const realRoot = await fs5.realpath(this.root);
|
|
24094
24206
|
let probe = a2;
|
|
24095
24207
|
for (; ; ) {
|
|
24096
24208
|
try {
|
|
24097
|
-
const real = await
|
|
24209
|
+
const real = await fs5.realpath(probe);
|
|
24098
24210
|
const rel = relative2(realRoot, real);
|
|
24099
24211
|
if (escapesRoot(rel)) {
|
|
24100
24212
|
throw new Error(`path escapes the workspace root via symlink: ${a2}`);
|
|
@@ -24118,19 +24230,21 @@ var NodeFileService = class {
|
|
|
24118
24230
|
const walk = async (absDir) => {
|
|
24119
24231
|
let entries;
|
|
24120
24232
|
try {
|
|
24121
|
-
entries = await
|
|
24233
|
+
entries = await fs5.readdir(absDir, { withFileTypes: true });
|
|
24122
24234
|
} catch {
|
|
24123
24235
|
return [];
|
|
24124
24236
|
}
|
|
24125
24237
|
const out = [];
|
|
24126
24238
|
for (const e2 of entries.sort((a2, b2) => a2.name.localeCompare(b2.name))) {
|
|
24127
24239
|
if (SKIP.has(e2.name)) continue;
|
|
24128
|
-
const absChild =
|
|
24240
|
+
const absChild = join4(absDir, e2.name);
|
|
24129
24241
|
const path = normalizeFilePath(relative2(this.root, absChild));
|
|
24242
|
+
if (e2.isDirectory() && SKIP_PATHS.has(path)) continue;
|
|
24243
|
+
if (e2.isDirectory() && e2.name === "coverage" && await looksLikeGeneratedCoverage(absChild)) continue;
|
|
24130
24244
|
if (e2.isDirectory()) {
|
|
24131
24245
|
out.push({ name: e2.name, path, kind: "directory", children: await walk(absChild) });
|
|
24132
24246
|
} else {
|
|
24133
|
-
const st2 = await
|
|
24247
|
+
const st2 = await fs5.stat(absChild).catch(() => null);
|
|
24134
24248
|
out.push({ name: e2.name, path, kind: "file", size: st2?.size, ext: extOf(e2.name) });
|
|
24135
24249
|
}
|
|
24136
24250
|
}
|
|
@@ -24139,11 +24253,11 @@ var NodeFileService = class {
|
|
|
24139
24253
|
return walk(this.abs(dir));
|
|
24140
24254
|
}
|
|
24141
24255
|
async read(path) {
|
|
24142
|
-
return
|
|
24256
|
+
return fs5.readFile(await this.realAbs(path), "utf8");
|
|
24143
24257
|
}
|
|
24144
24258
|
async stat(path) {
|
|
24145
24259
|
try {
|
|
24146
|
-
const st2 = await
|
|
24260
|
+
const st2 = await fs5.stat(this.abs(path));
|
|
24147
24261
|
return { path: normalizeFilePath(path), kind: st2.isDirectory() ? "directory" : "file", size: st2.size, ext: extOf(path) };
|
|
24148
24262
|
} catch {
|
|
24149
24263
|
return null;
|
|
@@ -24151,38 +24265,38 @@ var NodeFileService = class {
|
|
|
24151
24265
|
}
|
|
24152
24266
|
async write(path, content) {
|
|
24153
24267
|
const a2 = this.abs(path);
|
|
24154
|
-
await
|
|
24268
|
+
await fs5.mkdir(dirname2(a2), { recursive: true });
|
|
24155
24269
|
await this.assertReal(a2);
|
|
24156
|
-
await
|
|
24270
|
+
await fs5.writeFile(a2, content, "utf8");
|
|
24157
24271
|
}
|
|
24158
24272
|
async writeBytes(path, bytes) {
|
|
24159
24273
|
const a2 = this.abs(path);
|
|
24160
|
-
await
|
|
24274
|
+
await fs5.mkdir(dirname2(a2), { recursive: true });
|
|
24161
24275
|
await this.assertReal(a2);
|
|
24162
|
-
await
|
|
24276
|
+
await fs5.writeFile(a2, bytes);
|
|
24163
24277
|
}
|
|
24164
24278
|
async create(path, content = "") {
|
|
24165
24279
|
const a2 = this.abs(path);
|
|
24166
|
-
await
|
|
24280
|
+
await fs5.mkdir(dirname2(a2), { recursive: true });
|
|
24167
24281
|
await this.assertReal(a2);
|
|
24168
24282
|
try {
|
|
24169
|
-
await
|
|
24283
|
+
await fs5.writeFile(a2, content, { flag: "wx" });
|
|
24170
24284
|
} catch (e2) {
|
|
24171
24285
|
if (e2.code === "EEXIST") throw new Error(`already exists: ${path}`);
|
|
24172
24286
|
throw e2;
|
|
24173
24287
|
}
|
|
24174
24288
|
}
|
|
24175
24289
|
async remove(path) {
|
|
24176
|
-
await
|
|
24290
|
+
await fs5.rm(await this.realAbs(path), { recursive: true, force: true });
|
|
24177
24291
|
}
|
|
24178
24292
|
async rename(from, to) {
|
|
24179
24293
|
const t2 = this.abs(to);
|
|
24180
|
-
await
|
|
24294
|
+
await fs5.mkdir(dirname2(t2), { recursive: true });
|
|
24181
24295
|
await this.assertReal(t2);
|
|
24182
|
-
await
|
|
24296
|
+
await fs5.rename(await this.realAbs(from), t2);
|
|
24183
24297
|
}
|
|
24184
24298
|
async mkdir(path) {
|
|
24185
|
-
await
|
|
24299
|
+
await fs5.mkdir(this.abs(path), { recursive: true });
|
|
24186
24300
|
}
|
|
24187
24301
|
async search(query) {
|
|
24188
24302
|
const q2 = query.toLowerCase();
|
|
@@ -24382,13 +24496,13 @@ function makeMsbStreamer(bin, wrap) {
|
|
|
24382
24496
|
const done = new Promise((r2) => {
|
|
24383
24497
|
resolveDone = r2;
|
|
24384
24498
|
});
|
|
24385
|
-
const settle = (
|
|
24499
|
+
const settle = (code2) => {
|
|
24386
24500
|
if (settled) return;
|
|
24387
24501
|
settled = true;
|
|
24388
24502
|
for (const l2 of out.flush()) onLine(l2, "out");
|
|
24389
24503
|
for (const l2 of err.flush()) onLine(l2, "err");
|
|
24390
24504
|
clearTimeout(timer);
|
|
24391
|
-
resolveDone({ code, stdout: "", stderr: stderrText });
|
|
24505
|
+
resolveDone({ code: code2, stdout: "", stderr: stderrText });
|
|
24392
24506
|
};
|
|
24393
24507
|
const timer = opts?.timeoutMs === void 0 ? void 0 : setTimeout(() => {
|
|
24394
24508
|
child.kill("SIGKILL");
|
|
@@ -24408,7 +24522,7 @@ function makeMsbStreamer(bin, wrap) {
|
|
|
24408
24522
|
${e2 instanceof Error ? e2.message : String(e2)}`;
|
|
24409
24523
|
settle(-1);
|
|
24410
24524
|
});
|
|
24411
|
-
child.on("close", (
|
|
24525
|
+
child.on("close", (code2) => settle(code2 ?? -1));
|
|
24412
24526
|
return {
|
|
24413
24527
|
stop: () => {
|
|
24414
24528
|
if (!settled) child.kill("SIGTERM");
|
|
@@ -24451,13 +24565,41 @@ msb timed out after ${opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS2}ms` });
|
|
|
24451
24565
|
settle({ code: -1, stdout, stderr: `${stderr}
|
|
24452
24566
|
${e2 instanceof Error ? e2.message : String(e2)}` });
|
|
24453
24567
|
});
|
|
24454
|
-
child.on("close", (
|
|
24568
|
+
child.on("close", (code2) => {
|
|
24455
24569
|
clearTimeout(timer);
|
|
24456
|
-
settle({ code:
|
|
24570
|
+
settle({ code: code2 ?? -1, stdout, stderr });
|
|
24457
24571
|
});
|
|
24458
24572
|
});
|
|
24459
24573
|
}
|
|
24460
24574
|
|
|
24575
|
+
// src/session/snapshotBudget.ts
|
|
24576
|
+
var DEFAULT_SNAPSHOT_BUDGET_BYTES = 32 * 1024 ** 3;
|
|
24577
|
+
function parseByteSize(spelling) {
|
|
24578
|
+
const s2 = spelling.trim();
|
|
24579
|
+
const m2 = /^(\d+(?:\.\d+)?)\s*([KMGT]?)i?B?$/i.exec(s2);
|
|
24580
|
+
if (!m2) throw new Error(`not a size: "${spelling}" (expected e.g. "8G", "512M", or a bare byte count)`);
|
|
24581
|
+
const n2 = Number(m2[1]);
|
|
24582
|
+
const unit = (m2[2] ?? "").toUpperCase();
|
|
24583
|
+
const mult = unit === "K" ? 1024 : unit === "M" ? 1024 ** 2 : unit === "G" ? 1024 ** 3 : unit === "T" ? 1024 ** 4 : 1;
|
|
24584
|
+
return Math.round(n2 * mult);
|
|
24585
|
+
}
|
|
24586
|
+
function formatByteSize(bytes) {
|
|
24587
|
+
const units = [[1024 ** 4, "T"], [1024 ** 3, "G"], [1024 ** 2, "M"], [1024, "K"]];
|
|
24588
|
+
for (const [mult, suffix] of units) {
|
|
24589
|
+
if (bytes >= mult) return `${(bytes / mult).toFixed(1)}${suffix}`;
|
|
24590
|
+
}
|
|
24591
|
+
return `${bytes}B`;
|
|
24592
|
+
}
|
|
24593
|
+
function checkSnapshotBudget(check) {
|
|
24594
|
+
const { usedBytes, estimateBytes, budgetBytes } = check;
|
|
24595
|
+
const projected = usedBytes + estimateBytes;
|
|
24596
|
+
if (projected <= budgetBytes) return { ok: true };
|
|
24597
|
+
return {
|
|
24598
|
+
ok: false,
|
|
24599
|
+
message: `refusing to capture: session snapshots already use ${formatByteSize(usedBytes)} of a ${formatByteSize(budgetBytes)} budget, and this capture could add up to ${formatByteSize(estimateBytes)} more. Free space by removing snapshots you no longer need (\`msb snapshot list\`, \`msb snapshot remove <name>\`), or raise the ceiling with --snapshot-budget <size>.`
|
|
24600
|
+
};
|
|
24601
|
+
}
|
|
24602
|
+
|
|
24461
24603
|
// src/session/microvmBackend.ts
|
|
24462
24604
|
var GUEST_WORKDIR = "/workspace";
|
|
24463
24605
|
var seq2 = 0;
|
|
@@ -24578,6 +24720,14 @@ function makeMicrovmBackend(deps) {
|
|
|
24578
24720
|
}
|
|
24579
24721
|
return r2.stdout;
|
|
24580
24722
|
};
|
|
24723
|
+
const listSnapshotsRaw = async () => {
|
|
24724
|
+
const r2 = await deps.run(snapshotListArgs());
|
|
24725
|
+
if (r2.code !== 0) {
|
|
24726
|
+
const detail = (r2.stderr || r2.stdout).trim().split("\n").slice(-2).join("; ");
|
|
24727
|
+
throw new Error(`list snapshots failed (msb exit ${r2.code}): ${detail || "no output"}`);
|
|
24728
|
+
}
|
|
24729
|
+
return parseSnapshotList(r2.stdout);
|
|
24730
|
+
};
|
|
24581
24731
|
let lastReconcile = 0;
|
|
24582
24732
|
let reconciling = false;
|
|
24583
24733
|
const RECONCILE_MS = 1e4;
|
|
@@ -24592,9 +24742,9 @@ function makeMicrovmBackend(deps) {
|
|
|
24592
24742
|
if (!live.length) return;
|
|
24593
24743
|
const r2 = await deps.run(listArgs());
|
|
24594
24744
|
if (r2.code !== 0) return;
|
|
24595
|
-
const
|
|
24745
|
+
const present2 = new Set(parseSandboxList(r2.stdout).map((row) => row.name));
|
|
24596
24746
|
for (const s2 of live) {
|
|
24597
|
-
if (
|
|
24747
|
+
if (present2.has(sandboxName(s2.id))) continue;
|
|
24598
24748
|
if (at2 - s2.createdAt < RECONCILE_MS) continue;
|
|
24599
24749
|
sessions.delete(s2.id);
|
|
24600
24750
|
deps.log?.(`session ${s2.id} ended outside this daemon (sandbox ${sandboxName(s2.id)} is gone) \u2014 dropped`);
|
|
@@ -24905,7 +25055,13 @@ function makeMicrovmBackend(deps) {
|
|
|
24905
25055
|
workdir,
|
|
24906
25056
|
createdAt: Number.isFinite(stamped) && stamped > 0 ? stamped : row.createdAt ?? now2(),
|
|
24907
25057
|
...owner ? { owner } : {},
|
|
24908
|
-
...repoUrl && repoCommit ? { source: { url: repoUrl, commit: repoCommit, ...repoRef ? { ref: repoRef } : {} } } : {}
|
|
25058
|
+
...repoUrl && repoCommit ? { source: { url: repoUrl, commit: repoCommit, ...repoRef ? { ref: repoRef } : {} } } : {},
|
|
25059
|
+
/* THE ONLY PLACE THIS IS EVER SET TRUE. `create()` never touches it,
|
|
25060
|
+
* so a card can tell "this daemon booted it" from "this daemon
|
|
25061
|
+
* found it already running" — procs.ts's own words: a running
|
|
25062
|
+
* process nobody can see is worse than one that died, and adoption
|
|
25063
|
+
* used to be invisible past this log line. */
|
|
25064
|
+
adopted: true
|
|
24909
25065
|
});
|
|
24910
25066
|
adopted.push(id);
|
|
24911
25067
|
}
|
|
@@ -24944,6 +25100,12 @@ function makeMicrovmBackend(deps) {
|
|
|
24944
25100
|
`session ${id} must be paused before it can be captured (it is "${s2.status}"): pause first and check the drain result, or the snapshot may be torn`
|
|
24945
25101
|
);
|
|
24946
25102
|
}
|
|
25103
|
+
const existing = await listSnapshotsRaw();
|
|
25104
|
+
const usedBytes = existing.reduce((sum, snap) => sum + (snap.sizeBytes ?? 0), 0);
|
|
25105
|
+
const budgetBytes = deps.snapshotBudgetBytes ?? DEFAULT_SNAPSHOT_BUDGET_BYTES;
|
|
25106
|
+
const estimateBytes = parseByteSize(limits.rootDisk);
|
|
25107
|
+
const budget = checkSnapshotBudget({ usedBytes, estimateBytes, budgetBytes });
|
|
25108
|
+
if (!budget.ok) throw new Error(budget.message);
|
|
24947
25109
|
const snapName = name ?? `${sandboxName(id)}-${now2()}-${++snapSeq}`;
|
|
24948
25110
|
await msb([
|
|
24949
25111
|
"snapshot",
|
|
@@ -24975,12 +25137,19 @@ function makeMicrovmBackend(deps) {
|
|
|
24975
25137
|
* memory would show nothing after a restart while the artifacts sat on
|
|
24976
25138
|
* disk. */
|
|
24977
25139
|
async snapshots() {
|
|
24978
|
-
|
|
24979
|
-
|
|
24980
|
-
|
|
24981
|
-
|
|
24982
|
-
|
|
24983
|
-
|
|
25140
|
+
return listSnapshotsRaw();
|
|
25141
|
+
},
|
|
25142
|
+
/** Current usage against the refuse-at-budget ceiling, BEFORE a capture
|
|
25143
|
+
* ever hits it — a budget you only learn about at the moment it stops you
|
|
25144
|
+
* is a bad budget. Same arithmetic `commit()` checks against, so a caller
|
|
25145
|
+
* watching this can see a refusal coming. */
|
|
25146
|
+
async snapshotUsage() {
|
|
25147
|
+
const existing = await listSnapshotsRaw();
|
|
25148
|
+
return {
|
|
25149
|
+
usedBytes: existing.reduce((sum, snap) => sum + (snap.sizeBytes ?? 0), 0),
|
|
25150
|
+
budgetBytes: deps.snapshotBudgetBytes ?? DEFAULT_SNAPSHOT_BUDGET_BYTES,
|
|
25151
|
+
count: existing.length
|
|
25152
|
+
};
|
|
24984
25153
|
}
|
|
24985
25154
|
};
|
|
24986
25155
|
}
|
|
@@ -25046,11 +25215,11 @@ function makeGuestRunner(deps) {
|
|
|
25046
25215
|
onEvent(e2);
|
|
25047
25216
|
}
|
|
25048
25217
|
};
|
|
25049
|
-
const finish = (
|
|
25218
|
+
const finish = (code2, stderr) => {
|
|
25050
25219
|
if (skipped) deps.log?.(`session ${sessionId}: ${skipped} unparseable line(s) from the guest were skipped`);
|
|
25051
|
-
if (
|
|
25220
|
+
if (code2 !== 0) {
|
|
25052
25221
|
const why = stderr.trim().split(NEWLINE).slice(-3).join("; ");
|
|
25053
|
-
return { text: finalText(seen), stopReason: `error: guest exit ${
|
|
25222
|
+
return { text: finalText(seen), stopReason: `error: guest exit ${code2}${why ? ` \u2014 ${why}` : ""}` };
|
|
25054
25223
|
}
|
|
25055
25224
|
return { text: finalText(seen), stopReason: "done" };
|
|
25056
25225
|
};
|
|
@@ -25091,7 +25260,10 @@ function toInfo(h2, backend) {
|
|
|
25091
25260
|
// Read from the HANDLE, so it survives every later list() — and, on the
|
|
25092
25261
|
// microVM backend, a daemon restart, since the sandbox carries it in its
|
|
25093
25262
|
// own labels.
|
|
25094
|
-
...h2.source ? { source: h2.source } : {}
|
|
25263
|
+
...h2.source ? { source: h2.source } : {},
|
|
25264
|
+
// Same reasoning as `source`: a client cannot derive "this daemon found
|
|
25265
|
+
// it running rather than booted it" from anything else it is given.
|
|
25266
|
+
...h2.adopted ? { adopted: true } : {}
|
|
25095
25267
|
};
|
|
25096
25268
|
}
|
|
25097
25269
|
function makeSessionsPort(deps) {
|
|
@@ -25167,6 +25339,9 @@ function makeSessionsPort(deps) {
|
|
|
25167
25339
|
...s2.sizeBytes !== void 0 ? { sizeBytes: s2.sizeBytes } : {}
|
|
25168
25340
|
}))
|
|
25169
25341
|
} : {},
|
|
25342
|
+
...backend.snapshotUsage ? {
|
|
25343
|
+
snapshotUsage: () => backend.snapshotUsage()
|
|
25344
|
+
} : {},
|
|
25170
25345
|
...backend.metrics ? {
|
|
25171
25346
|
watchMetrics: (id, onSample) => {
|
|
25172
25347
|
const st2 = backend.metrics(id, onSample);
|
|
@@ -25183,18 +25358,21 @@ function makeSessionsPort(deps) {
|
|
|
25183
25358
|
}
|
|
25184
25359
|
|
|
25185
25360
|
// ../tools-node/auth.ts
|
|
25186
|
-
import { promises as
|
|
25361
|
+
import { promises as fs6 } from "node:fs";
|
|
25187
25362
|
import { homedir as homedir3 } from "node:os";
|
|
25188
|
-
import { join as
|
|
25189
|
-
function corenelDir2() {
|
|
25190
|
-
return
|
|
25363
|
+
import { join as join5 } from "node:path";
|
|
25364
|
+
function corenelDir2(base = homedir3()) {
|
|
25365
|
+
return join5(base, stateDirName());
|
|
25366
|
+
}
|
|
25367
|
+
function sidecarDir(base = homedir3()) {
|
|
25368
|
+
return join5(corenelDir2(base), "sidecar");
|
|
25191
25369
|
}
|
|
25192
|
-
function authTokenPath() {
|
|
25193
|
-
return
|
|
25370
|
+
function authTokenPath(base = homedir3()) {
|
|
25371
|
+
return join5(corenelDir2(base), "token");
|
|
25194
25372
|
}
|
|
25195
25373
|
async function readAuthToken() {
|
|
25196
25374
|
try {
|
|
25197
|
-
const t2 = (await
|
|
25375
|
+
const t2 = (await fs6.readFile(authTokenPath(), "utf8")).trim();
|
|
25198
25376
|
return t2 || null;
|
|
25199
25377
|
} catch {
|
|
25200
25378
|
return null;
|
|
@@ -26959,12 +27137,12 @@ var TemplateProcessingStage = class {
|
|
|
26959
27137
|
*/
|
|
26960
27138
|
async loadPackageResource(context, packagePath, resourcePath, prefix) {
|
|
26961
27139
|
try {
|
|
26962
|
-
const
|
|
27140
|
+
const fs15 = context.fileSystem;
|
|
26963
27141
|
const possibleExtensions = ["", ".prmd", ".md", ".txt"];
|
|
26964
27142
|
for (const ext of possibleExtensions) {
|
|
26965
27143
|
const filePath = resolvePackageFile(packagePath, resourcePath + ext);
|
|
26966
|
-
if (await
|
|
26967
|
-
const contentData = await
|
|
27144
|
+
if (await fs15.exists(filePath)) {
|
|
27145
|
+
const contentData = await fs15.readFile(filePath);
|
|
26968
27146
|
if (isPrompdFile(filePath)) {
|
|
26969
27147
|
const parser2 = new PrompdParser();
|
|
26970
27148
|
try {
|
|
@@ -26998,16 +27176,16 @@ var TemplateProcessingStage = class {
|
|
|
26998
27176
|
return content;
|
|
26999
27177
|
}
|
|
27000
27178
|
try {
|
|
27001
|
-
const
|
|
27179
|
+
const fs15 = context.fileSystem;
|
|
27002
27180
|
const parser2 = new PrompdParser();
|
|
27003
27181
|
let parentFile = null;
|
|
27004
|
-
if (isPrompdFile(parentPath) && await
|
|
27182
|
+
if (isPrompdFile(parentPath) && await fs15.exists(parentPath)) {
|
|
27005
27183
|
parentFile = parentPath;
|
|
27006
|
-
} else if (await
|
|
27007
|
-
const files = (await
|
|
27184
|
+
} else if (await fs15.exists(parentPath) && await fs15.isDirectory(parentPath)) {
|
|
27185
|
+
const files = (await fs15.readdir(parentPath)).filter((f2) => isPrompdFile(f2));
|
|
27008
27186
|
if (files.length > 0) {
|
|
27009
27187
|
const mainFile = files.find((f2) => f2 === "main.prmd" || f2 === "main.md");
|
|
27010
|
-
parentFile =
|
|
27188
|
+
parentFile = fs15.join(parentPath, mainFile || files[0]);
|
|
27011
27189
|
}
|
|
27012
27190
|
}
|
|
27013
27191
|
if (!parentFile) {
|
|
@@ -27021,14 +27199,14 @@ var TemplateProcessingStage = class {
|
|
|
27021
27199
|
});
|
|
27022
27200
|
return content;
|
|
27023
27201
|
}
|
|
27024
|
-
const parentFileContent = await
|
|
27202
|
+
const parentFileContent = await fs15.readFile(parentFile);
|
|
27025
27203
|
const parentData = parser2.parseContent(parentFileContent);
|
|
27026
|
-
await this.validateParentFileReferences(context, parentData.metadata,
|
|
27204
|
+
await this.validateParentFileReferences(context, parentData.metadata, fs15, parentFile);
|
|
27027
27205
|
if (context.hasErrors()) {
|
|
27028
27206
|
return content;
|
|
27029
27207
|
}
|
|
27030
27208
|
if (parentData.content) {
|
|
27031
|
-
const parentDir =
|
|
27209
|
+
const parentDir = fs15.dirname(parentFile);
|
|
27032
27210
|
parentData.content = this.resolveIncludePaths(parentData.content, parentDir);
|
|
27033
27211
|
}
|
|
27034
27212
|
const overrides = context.metadata?.override || {};
|
|
@@ -27579,10 +27757,10 @@ ${content}` : parentData.content;
|
|
|
27579
27757
|
* Called during inheritance processing because AssetExtractionStage only runs on
|
|
27580
27758
|
* the child file's metadata — the parent is only parsed, never fully compiled.
|
|
27581
27759
|
*/
|
|
27582
|
-
async validateParentFileReferences(context, metadata,
|
|
27760
|
+
async validateParentFileReferences(context, metadata, fs15, parentFile) {
|
|
27583
27761
|
if (!metadata) return;
|
|
27584
27762
|
const metadataAsRecord = metadata;
|
|
27585
|
-
const parentDir =
|
|
27763
|
+
const parentDir = fs15.dirname(parentFile);
|
|
27586
27764
|
const fileFields = ["system", "task", "user", "assistant", "response", "output", "context"];
|
|
27587
27765
|
for (const field of fileFields) {
|
|
27588
27766
|
const fieldValue = metadataAsRecord[field];
|
|
@@ -27590,8 +27768,8 @@ ${content}` : parentData.content;
|
|
|
27590
27768
|
const refs = Array.isArray(fieldValue) ? fieldValue.filter((v2) => typeof v2 === "string") : typeof fieldValue === "string" ? [fieldValue] : [];
|
|
27591
27769
|
for (const ref of refs) {
|
|
27592
27770
|
if (!ref.startsWith("./") && !ref.startsWith("../")) continue;
|
|
27593
|
-
const resolvedPath =
|
|
27594
|
-
const exists = await Promise.resolve(
|
|
27771
|
+
const resolvedPath = fs15.resolve(parentDir, ref);
|
|
27772
|
+
const exists = await Promise.resolve(fs15.exists(resolvedPath));
|
|
27595
27773
|
if (!exists) {
|
|
27596
27774
|
const location = context.findLocation(/inherits:/);
|
|
27597
27775
|
context.addDiagnostic({
|
|
@@ -27968,6 +28146,90 @@ var PrompdCompiler = class {
|
|
|
27968
28146
|
var parser = new PrompdParser();
|
|
27969
28147
|
var compiler = new PrompdCompiler();
|
|
27970
28148
|
|
|
28149
|
+
// ../tools-node/migrateHome.ts
|
|
28150
|
+
import { promises as fs7 } from "node:fs";
|
|
28151
|
+
import { homedir as homedir4 } from "node:os";
|
|
28152
|
+
import { join as join6, dirname as dirname3 } from "node:path";
|
|
28153
|
+
var SIDECAR_FILES = [
|
|
28154
|
+
"daemon.id",
|
|
28155
|
+
"daemon.token",
|
|
28156
|
+
"hosts.json",
|
|
28157
|
+
"sidecar.crt",
|
|
28158
|
+
"sidecar.key",
|
|
28159
|
+
"sidecar.sans.json"
|
|
28160
|
+
];
|
|
28161
|
+
var SIDECAR_DIRS = ["hosts"];
|
|
28162
|
+
async function isDir(p2) {
|
|
28163
|
+
return fs7.stat(p2).then((s2) => s2.isDirectory()).catch(() => false);
|
|
28164
|
+
}
|
|
28165
|
+
async function present(p2) {
|
|
28166
|
+
return fs7.stat(p2).then(() => true).catch(() => false);
|
|
28167
|
+
}
|
|
28168
|
+
function code(e2) {
|
|
28169
|
+
return e2?.code;
|
|
28170
|
+
}
|
|
28171
|
+
async function moveEntry(from, to, report) {
|
|
28172
|
+
if (!await present(from)) return;
|
|
28173
|
+
if (await present(to)) {
|
|
28174
|
+
report.skipped.push({ path: to, reason: "destination already exists" });
|
|
28175
|
+
return;
|
|
28176
|
+
}
|
|
28177
|
+
await fs7.mkdir(dirname3(to), { recursive: true });
|
|
28178
|
+
try {
|
|
28179
|
+
await fs7.rename(from, to);
|
|
28180
|
+
report.moved.push(to);
|
|
28181
|
+
} catch (e2) {
|
|
28182
|
+
const c2 = code(e2);
|
|
28183
|
+
if (c2 === "EEXIST" || c2 === "ENOTEMPTY") {
|
|
28184
|
+
report.skipped.push({ path: to, reason: "moved concurrently" });
|
|
28185
|
+
return;
|
|
28186
|
+
}
|
|
28187
|
+
if (c2 === "ENOENT") {
|
|
28188
|
+
report.skipped.push({ path: to, reason: "moved concurrently" });
|
|
28189
|
+
return;
|
|
28190
|
+
}
|
|
28191
|
+
if (c2 === "EXDEV") {
|
|
28192
|
+
await fs7.cp(from, to, { recursive: true });
|
|
28193
|
+
await fs7.rm(from, { recursive: true, force: true });
|
|
28194
|
+
report.moved.push(to);
|
|
28195
|
+
return;
|
|
28196
|
+
}
|
|
28197
|
+
throw e2;
|
|
28198
|
+
}
|
|
28199
|
+
}
|
|
28200
|
+
async function mergeDir(from, to, report) {
|
|
28201
|
+
const entries = await fs7.readdir(from, { withFileTypes: true }).catch(() => []);
|
|
28202
|
+
for (const e2 of entries) {
|
|
28203
|
+
const src = join6(from, e2.name);
|
|
28204
|
+
const dst = join6(to, e2.name);
|
|
28205
|
+
if (e2.isDirectory() && await isDir(dst)) await mergeDir(src, dst, report);
|
|
28206
|
+
else await moveEntry(src, dst, report);
|
|
28207
|
+
}
|
|
28208
|
+
await fs7.rmdir(from).catch(() => {
|
|
28209
|
+
});
|
|
28210
|
+
}
|
|
28211
|
+
async function migrateHomeLayout(base = homedir4()) {
|
|
28212
|
+
const report = { moved: [], skipped: [] };
|
|
28213
|
+
const home = corenelDir2(base);
|
|
28214
|
+
if (!await present(home)) return report;
|
|
28215
|
+
const config = join6(home, "config");
|
|
28216
|
+
if (await isDir(config)) await mergeDir(config, home, report);
|
|
28217
|
+
const side = sidecarDir(base);
|
|
28218
|
+
for (const name of SIDECAR_FILES) {
|
|
28219
|
+
await moveEntry(join6(home, name), join6(side, name), report);
|
|
28220
|
+
}
|
|
28221
|
+
for (const name of SIDECAR_DIRS) {
|
|
28222
|
+
const src = join6(home, name);
|
|
28223
|
+
if (!await isDir(src)) continue;
|
|
28224
|
+
const dst = join6(side, name);
|
|
28225
|
+
if (await isDir(dst)) await mergeDir(src, dst, report);
|
|
28226
|
+
else await moveEntry(src, dst, report);
|
|
28227
|
+
}
|
|
28228
|
+
if (report.moved.length) await fs7.chmod(home, 448).catch(() => {
|
|
28229
|
+
});
|
|
28230
|
+
return report;
|
|
28231
|
+
}
|
|
28232
|
+
|
|
27971
28233
|
// src/session/wiring.ts
|
|
27972
28234
|
async function probeMsb(run) {
|
|
27973
28235
|
const r2 = await run(["--version"], { timeoutMs: 3e4 });
|
|
@@ -28012,6 +28274,7 @@ async function wireSessions(opts) {
|
|
|
28012
28274
|
image: opts.image,
|
|
28013
28275
|
// Safe to apply everywhere: it only rewrites drive-letter paths.
|
|
28014
28276
|
hostPath: wslHostPath,
|
|
28277
|
+
...opts.snapshotBudgetBytes !== void 0 ? { snapshotBudgetBytes: opts.snapshotBudgetBytes } : {},
|
|
28015
28278
|
...opts.log ? { log: opts.log } : {}
|
|
28016
28279
|
});
|
|
28017
28280
|
const manager = new SessionManager({
|
|
@@ -28035,10 +28298,120 @@ async function wireSessions(opts) {
|
|
|
28035
28298
|
return { manager, backend, port: makeSessionsPort({ manager, backend, guestEnv, ...portExtras(opts) }) };
|
|
28036
28299
|
}
|
|
28037
28300
|
|
|
28301
|
+
// src/runner/owners.ts
|
|
28302
|
+
import { readdir } from "node:fs/promises";
|
|
28303
|
+
import { join as join8 } from "node:path";
|
|
28304
|
+
|
|
28305
|
+
// src/hostsDir.ts
|
|
28306
|
+
import { join as join7 } from "node:path";
|
|
28307
|
+
var HOSTS_SEG = "hosts";
|
|
28308
|
+
var MAX_SYNC_PATH_LEN = 400;
|
|
28309
|
+
var MAX_SYNC_SEGMENTS = 24;
|
|
28310
|
+
var CONTROL_CHAR_RE = new RegExp(
|
|
28311
|
+
"[\\u0000-\\u001f\\u007f-\\u009f]"
|
|
28312
|
+
);
|
|
28313
|
+
var DIR_NAME_RE = /^[a-z0-9_-]{1,32}$/;
|
|
28314
|
+
function assertSyncPath(path) {
|
|
28315
|
+
if (!path) throw new Error("sync path is empty");
|
|
28316
|
+
if (path.length > MAX_SYNC_PATH_LEN) throw new Error(`sync path too long (> ${MAX_SYNC_PATH_LEN})`);
|
|
28317
|
+
if (CONTROL_CHAR_RE.test(path)) throw new Error("sync path contains control characters");
|
|
28318
|
+
if (path.includes(":")) throw new Error("sync path contains a colon");
|
|
28319
|
+
const n2 = normalizeFilePath(path);
|
|
28320
|
+
if (!n2) throw new Error("sync path normalizes to empty (targets the root)");
|
|
28321
|
+
const segments = n2.split("/");
|
|
28322
|
+
if (segments.length > MAX_SYNC_SEGMENTS) throw new Error(`sync path has too many segments (> ${MAX_SYNC_SEGMENTS})`);
|
|
28323
|
+
if (segments.some((s2) => s2 === "." || s2 === "..")) {
|
|
28324
|
+
throw new Error('sync path contains a "." or ".." segment');
|
|
28325
|
+
}
|
|
28326
|
+
}
|
|
28327
|
+
function hostsRoot(stateDir) {
|
|
28328
|
+
return join7(stateDir, "sidecar", HOSTS_SEG);
|
|
28329
|
+
}
|
|
28330
|
+
function hostsDirFor(stateDir, dirName2) {
|
|
28331
|
+
if (!DIR_NAME_RE.test(dirName2)) throw new Error(`invalid host dir name: ${dirName2}`);
|
|
28332
|
+
return join7(hostsRoot(stateDir), dirName2);
|
|
28333
|
+
}
|
|
28334
|
+
var RAW = /* @__PURE__ */ new WeakMap();
|
|
28335
|
+
function hostFilesFor(stateDir, dirName2) {
|
|
28336
|
+
const raw = new NodeFileService(hostsDirFor(stateDir, dirName2));
|
|
28337
|
+
const wrapper = {
|
|
28338
|
+
kind: raw.kind,
|
|
28339
|
+
label: raw.label,
|
|
28340
|
+
isLocalDisk: raw.isLocalDisk,
|
|
28341
|
+
writable: raw.writable,
|
|
28342
|
+
list: (dir) => raw.list(dir),
|
|
28343
|
+
read: (path) => raw.read(path),
|
|
28344
|
+
stat: (path) => raw.stat(path),
|
|
28345
|
+
search: (query) => raw.search(query),
|
|
28346
|
+
onChange: (_cb) => raw.onChange()
|
|
28347
|
+
};
|
|
28348
|
+
RAW.set(wrapper, raw);
|
|
28349
|
+
return wrapper;
|
|
28350
|
+
}
|
|
28351
|
+
async function syncWrite(files, path, content) {
|
|
28352
|
+
assertSyncPath(path);
|
|
28353
|
+
const raw = RAW.get(files);
|
|
28354
|
+
if (!raw) throw new Error("syncWrite: not a hostFilesFor() result");
|
|
28355
|
+
await raw.write(path, content);
|
|
28356
|
+
}
|
|
28357
|
+
async function syncRemove(files, path) {
|
|
28358
|
+
assertSyncPath(path);
|
|
28359
|
+
const raw = RAW.get(files);
|
|
28360
|
+
if (!raw) throw new Error("syncRemove: not a hostFilesFor() result");
|
|
28361
|
+
await raw.remove(path);
|
|
28362
|
+
}
|
|
28363
|
+
|
|
28364
|
+
// src/runner/owners.ts
|
|
28365
|
+
var WORKSPACES_SEG = "workspaces";
|
|
28366
|
+
var WORKSPACE_ID_RE = /^[a-z0-9_-]{1,32}$/;
|
|
28367
|
+
async function workspaceRootsFor(dirName2, hostDir) {
|
|
28368
|
+
let entries;
|
|
28369
|
+
try {
|
|
28370
|
+
entries = await readdir(join8(hostDir, WORKSPACES_SEG), { withFileTypes: true });
|
|
28371
|
+
} catch {
|
|
28372
|
+
return [];
|
|
28373
|
+
}
|
|
28374
|
+
const out = [];
|
|
28375
|
+
for (const entry of entries) {
|
|
28376
|
+
if (!entry.isDirectory()) continue;
|
|
28377
|
+
if (!WORKSPACE_ID_RE.test(entry.name)) continue;
|
|
28378
|
+
out.push({
|
|
28379
|
+
owner: `${dirName2}/ws/${entry.name}`,
|
|
28380
|
+
files: new NodeFileService(join8(hostDir, WORKSPACES_SEG, entry.name)),
|
|
28381
|
+
root: "crew"
|
|
28382
|
+
});
|
|
28383
|
+
}
|
|
28384
|
+
return out;
|
|
28385
|
+
}
|
|
28386
|
+
async function listOwnerRoots(stateDir) {
|
|
28387
|
+
const roots = [
|
|
28388
|
+
{ owner: "", files: new NodeFileService(stateDir), root: "crew" }
|
|
28389
|
+
];
|
|
28390
|
+
let entries;
|
|
28391
|
+
try {
|
|
28392
|
+
entries = await readdir(hostsRoot(stateDir), { withFileTypes: true });
|
|
28393
|
+
} catch {
|
|
28394
|
+
return roots;
|
|
28395
|
+
}
|
|
28396
|
+
for (const entry of entries) {
|
|
28397
|
+
if (!entry.isDirectory()) continue;
|
|
28398
|
+
const dirName2 = entry.name;
|
|
28399
|
+
let hostDir;
|
|
28400
|
+
try {
|
|
28401
|
+
hostDir = hostsDirFor(stateDir, dirName2);
|
|
28402
|
+
} catch {
|
|
28403
|
+
continue;
|
|
28404
|
+
}
|
|
28405
|
+
roots.push({ owner: dirName2, files: new NodeFileService(hostDir), root: "crew" });
|
|
28406
|
+
roots.push(...await workspaceRootsFor(dirName2, hostDir));
|
|
28407
|
+
}
|
|
28408
|
+
return roots;
|
|
28409
|
+
}
|
|
28410
|
+
|
|
28038
28411
|
// src/cert.ts
|
|
28039
|
-
import { promises as
|
|
28412
|
+
import { promises as fs8 } from "node:fs";
|
|
28040
28413
|
import { networkInterfaces } from "node:os";
|
|
28041
|
-
import { join as
|
|
28414
|
+
import { join as join9 } from "node:path";
|
|
28042
28415
|
import selfsigned from "selfsigned";
|
|
28043
28416
|
function lanIPv4s() {
|
|
28044
28417
|
const out = [];
|
|
@@ -28049,18 +28422,18 @@ function lanIPv4s() {
|
|
|
28049
28422
|
}
|
|
28050
28423
|
return [...new Set(out)];
|
|
28051
28424
|
}
|
|
28052
|
-
var certPath = () =>
|
|
28053
|
-
var keyPath = () =>
|
|
28054
|
-
var sansPath = () =>
|
|
28425
|
+
var certPath = () => join9(sidecarSubdir(), "sidecar.crt");
|
|
28426
|
+
var keyPath = () => join9(sidecarSubdir(), "sidecar.key");
|
|
28427
|
+
var sansPath = () => join9(sidecarSubdir(), "sidecar.sans.json");
|
|
28055
28428
|
function isIp(s2) {
|
|
28056
28429
|
return /^\d{1,3}(?:\.\d{1,3}){3}$/.test(s2) || s2.includes(":");
|
|
28057
28430
|
}
|
|
28058
28431
|
async function loadOrCreateSelfSigned(extraHosts = []) {
|
|
28059
28432
|
const sans = [...new Set(["localhost", "127.0.0.1", "::1", ...lanIPv4s(), ...extraHosts].filter(Boolean))];
|
|
28060
28433
|
try {
|
|
28061
|
-
const have = JSON.parse(await
|
|
28434
|
+
const have = JSON.parse(await fs8.readFile(sansPath(), "utf8"));
|
|
28062
28435
|
if (Array.isArray(have) && sans.every((s2) => have.includes(s2))) {
|
|
28063
|
-
const [cert, key] = await Promise.all([
|
|
28436
|
+
const [cert, key] = await Promise.all([fs8.readFile(certPath(), "utf8"), fs8.readFile(keyPath(), "utf8")]);
|
|
28064
28437
|
return { cert, key, sans: have };
|
|
28065
28438
|
}
|
|
28066
28439
|
} catch {
|
|
@@ -28072,10 +28445,10 @@ async function loadOrCreateSelfSigned(extraHosts = []) {
|
|
|
28072
28445
|
algorithm: "sha256",
|
|
28073
28446
|
extensions: [{ name: "subjectAltName", altNames }]
|
|
28074
28447
|
});
|
|
28075
|
-
await
|
|
28076
|
-
await
|
|
28077
|
-
await
|
|
28078
|
-
await
|
|
28448
|
+
await fs8.mkdir(sidecarSubdir(), { recursive: true });
|
|
28449
|
+
await fs8.writeFile(certPath(), pems.cert, { mode: 384 });
|
|
28450
|
+
await fs8.writeFile(keyPath(), pems.private, { mode: 384 });
|
|
28451
|
+
await fs8.writeFile(sansPath(), JSON.stringify(sans));
|
|
28079
28452
|
return { cert: pems.cert, key: pems.private, sans };
|
|
28080
28453
|
}
|
|
28081
28454
|
|
|
@@ -28180,8 +28553,8 @@ async function startWith(bin, port, name) {
|
|
|
28180
28553
|
child.stdout?.on("data", onData);
|
|
28181
28554
|
child.stderr?.on("data", onData);
|
|
28182
28555
|
child.on("error", (e2) => finish(() => reject(e2)));
|
|
28183
|
-
child.on("exit", (
|
|
28184
|
-
`${bin} exited (code ${
|
|
28556
|
+
child.on("exit", (code2) => finish(() => reject(new Error(
|
|
28557
|
+
`${bin} exited (code ${code2}) before a share URL appeared \u2014 is the environment enabled? Run \`${bin} enable <token>\` once.
|
|
28185
28558
|
${tail(out)}`
|
|
28186
28559
|
))));
|
|
28187
28560
|
const timer = setTimeout(() => finish(() => {
|
|
@@ -28318,13 +28691,13 @@ function startWith2(bin, mode, log, onDown) {
|
|
|
28318
28691
|
child.stdout?.on("data", onData);
|
|
28319
28692
|
child.stderr?.on("data", onData);
|
|
28320
28693
|
child.on("error", (e2) => finish(() => reject(e2)));
|
|
28321
|
-
child.on("exit", (
|
|
28694
|
+
child.on("exit", (code2) => {
|
|
28322
28695
|
if (settled) {
|
|
28323
|
-
log?.(`cloudflared exited (code ${
|
|
28324
|
-
fireDown(`cloudflared exited (code ${
|
|
28696
|
+
log?.(`cloudflared exited (code ${code2}) \u2014 the tunnel is down`);
|
|
28697
|
+
fireDown(`cloudflared exited (code ${code2})`);
|
|
28325
28698
|
return;
|
|
28326
28699
|
}
|
|
28327
|
-
finish(() => reject(new Error(`${bin} exited (code ${
|
|
28700
|
+
finish(() => reject(new Error(`${bin} exited (code ${code2}) before the tunnel came up.
|
|
28328
28701
|
${tail2(out)}`)));
|
|
28329
28702
|
});
|
|
28330
28703
|
const timer = setTimeout(() => finish(() => {
|
|
@@ -28573,6 +28946,14 @@ function parseCanCall(meta) {
|
|
|
28573
28946
|
if (!Array.isArray(raw)) return [];
|
|
28574
28947
|
return raw.filter((x2) => typeof x2 === "string" && isValidAgentName(x2));
|
|
28575
28948
|
}
|
|
28949
|
+
function parseWorkRoots(raw) {
|
|
28950
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
28951
|
+
const out = {};
|
|
28952
|
+
for (const [id, dir] of Object.entries(raw)) {
|
|
28953
|
+
if (typeof dir === "string" && dir) out[id] = dir;
|
|
28954
|
+
}
|
|
28955
|
+
return Object.keys(out).length ? out : void 0;
|
|
28956
|
+
}
|
|
28576
28957
|
function parseSettings(text) {
|
|
28577
28958
|
try {
|
|
28578
28959
|
const raw = JSON.parse(text);
|
|
@@ -28580,7 +28961,13 @@ function parseSettings(text) {
|
|
|
28580
28961
|
const o2 = raw;
|
|
28581
28962
|
return {
|
|
28582
28963
|
model: typeof o2.model === "string" ? o2.model : void 0,
|
|
28583
|
-
temperature: typeof o2.temperature === "number" ? o2.temperature : void 0
|
|
28964
|
+
temperature: typeof o2.temperature === "number" ? o2.temperature : void 0,
|
|
28965
|
+
/* Carried through as-is. Validation belongs to the daemon, which is the
|
|
28966
|
+
* only side that knows its own approved roots -- checking here would be
|
|
28967
|
+
* checking against the wrong list, on the wrong machine. Non-string
|
|
28968
|
+
* values are dropped per entry rather than failing the whole file: one
|
|
28969
|
+
* bad key must not cost an agent every other machine's setting. */
|
|
28970
|
+
workRoots: parseWorkRoots(o2.workRoots)
|
|
28584
28971
|
};
|
|
28585
28972
|
}
|
|
28586
28973
|
} catch {
|
|
@@ -28891,24 +29278,75 @@ function ensureWritable(path) {
|
|
|
28891
29278
|
throw new Error(`${PROMPD_INSTALL_DIR}/ install dirs are read-only (managed by package install). Refusing to modify "${path}". Install or remove packages instead of editing them directly.`);
|
|
28892
29279
|
}
|
|
28893
29280
|
}
|
|
28894
|
-
function
|
|
28895
|
-
|
|
28896
|
-
|
|
28897
|
-
|
|
29281
|
+
function describe(n2) {
|
|
29282
|
+
return `${n2.kind === "directory" ? "[dir] " : ""}${n2.path}${n2.kind === "file" && n2.size != null ? ` (${n2.size}b)` : ""}`;
|
|
29283
|
+
}
|
|
29284
|
+
function bfsNodes(roots, maxDepth = Infinity) {
|
|
29285
|
+
const out = [];
|
|
29286
|
+
let level = roots;
|
|
29287
|
+
let depth = 1;
|
|
29288
|
+
while (level.length) {
|
|
29289
|
+
const next = [];
|
|
29290
|
+
for (const n2 of level) {
|
|
29291
|
+
out.push(n2);
|
|
29292
|
+
if (n2.kind === "directory" && n2.children?.length && depth < maxDepth) next.push(...n2.children);
|
|
29293
|
+
}
|
|
29294
|
+
level = next;
|
|
29295
|
+
depth++;
|
|
28898
29296
|
}
|
|
28899
29297
|
return out;
|
|
28900
29298
|
}
|
|
29299
|
+
var LIST_FILES_MAX_ENTRIES = 300;
|
|
29300
|
+
function selectByBreadthFirstCap(roots, maxDepth) {
|
|
29301
|
+
const bfs = bfsNodes(roots, maxDepth);
|
|
29302
|
+
return { keep: new Set(bfs.slice(0, LIST_FILES_MAX_ENTRIES)), total: bfs.length };
|
|
29303
|
+
}
|
|
29304
|
+
function describeInTreeOrder(roots, keep, maxDepth = Infinity, depth = 1, out = []) {
|
|
29305
|
+
for (const n2 of roots) {
|
|
29306
|
+
if (!keep.has(n2)) continue;
|
|
29307
|
+
out.push(describe(n2));
|
|
29308
|
+
if (n2.kind === "directory" && n2.children?.length && depth < maxDepth) {
|
|
29309
|
+
describeInTreeOrder(n2.children, keep, maxDepth, depth + 1, out);
|
|
29310
|
+
}
|
|
29311
|
+
}
|
|
29312
|
+
return out;
|
|
29313
|
+
}
|
|
29314
|
+
function truncationMarker(total, maxDepth) {
|
|
29315
|
+
const omitted = total - LIST_FILES_MAX_ENTRIES;
|
|
29316
|
+
const omittedEntries = `${omitted} more entr${omitted === 1 ? "y" : "ies"} omitted`;
|
|
29317
|
+
if (maxDepth != null && Number.isFinite(maxDepth)) {
|
|
29318
|
+
return `\u2026 truncated: ${omittedEntries}. ${total} entries were collected within depth ${maxDepth}; deeper levels were not counted, so this is not the size of the whole tree. Pass \`dir\` to narrow by subdirectory, or a larger \`depth\` to go deeper.`;
|
|
29319
|
+
}
|
|
29320
|
+
return `\u2026 truncated: ${omittedEntries} (${total} total). Pass \`dir\` to list a specific subdirectory (narrower breadth), or \`depth\` to limit how many levels deep the listing goes (narrower depth), instead of the whole tree.`;
|
|
29321
|
+
}
|
|
29322
|
+
function normalizeDepth(args) {
|
|
29323
|
+
const v2 = args["depth"];
|
|
29324
|
+
if (v2 == null) return void 0;
|
|
29325
|
+
const n2 = typeof v2 === "number" ? v2 : typeof v2 === "string" ? Number(v2) : NaN;
|
|
29326
|
+
if (!Number.isFinite(n2)) return void 0;
|
|
29327
|
+
const floored = Math.floor(n2);
|
|
29328
|
+
return floored < 1 ? 1 : floored;
|
|
29329
|
+
}
|
|
28901
29330
|
var listFilesTool = {
|
|
28902
29331
|
name: "list_files",
|
|
28903
|
-
description: "List files and directories in the workspace (the open folder or the virtual workspace).
|
|
29332
|
+
description: "List files and directories in the workspace (the open folder or the virtual workspace). Start shallow and drill in: pass `depth` to limit how many levels deep the listing goes (vertical narrowing \u2014 `depth: 1` returns just the immediate children of the target, a quick orientation call) and/or `dir` to list a specific subdirectory instead of the whole tree (horizontal narrowing). Omit `depth` for a full recursive listing. Returns paths relative to the workspace root.",
|
|
28904
29333
|
parameters: {
|
|
28905
29334
|
type: "object",
|
|
28906
|
-
properties: {
|
|
29335
|
+
properties: {
|
|
29336
|
+
dir: { type: "string", description: "Subdirectory to list (optional; omit for the root)." },
|
|
29337
|
+
depth: {
|
|
29338
|
+
type: "number",
|
|
29339
|
+
description: "How many levels deep to list (optional; omit for the full recursive tree). 1 = immediate children of `dir` (or the root) only, 2 = two levels, and so on. Values below 1 (0, negative) are treated as 1, not rejected."
|
|
29340
|
+
}
|
|
29341
|
+
}
|
|
28907
29342
|
},
|
|
28908
29343
|
async run(args, ctx) {
|
|
28909
29344
|
const files = requireFiles(ctx);
|
|
28910
29345
|
const nodes = await files.list(str(args, "dir") || void 0);
|
|
28911
|
-
const
|
|
29346
|
+
const maxDepth = normalizeDepth(args);
|
|
29347
|
+
const { keep, total } = selectByBreadthFirstCap(nodes, maxDepth);
|
|
29348
|
+
const lines = describeInTreeOrder(nodes, keep, maxDepth);
|
|
29349
|
+
if (total > LIST_FILES_MAX_ENTRIES) lines.push(truncationMarker(total, maxDepth));
|
|
28912
29350
|
return lines.length ? lines.join("\n") : "(empty)";
|
|
28913
29351
|
}
|
|
28914
29352
|
};
|
|
@@ -29647,7 +30085,7 @@ function allTools() {
|
|
|
29647
30085
|
return [...defaultTools, ...extensionTools()];
|
|
29648
30086
|
}
|
|
29649
30087
|
|
|
29650
|
-
// ../
|
|
30088
|
+
// ../harness/tools/callAgent.ts
|
|
29651
30089
|
function str5(v2) {
|
|
29652
30090
|
return typeof v2 === "string" ? v2.trim() : "";
|
|
29653
30091
|
}
|
|
@@ -29915,113 +30353,6 @@ function nodeCrewLocation(stateDir, scope, workspaceRoot) {
|
|
|
29915
30353
|
return { files: new NodeFileService(wsRoot), root: `${stateDirName()}/crew` };
|
|
29916
30354
|
}
|
|
29917
30355
|
|
|
29918
|
-
// src/runner/owners.ts
|
|
29919
|
-
import { readdir } from "node:fs/promises";
|
|
29920
|
-
import { join as join7 } from "node:path";
|
|
29921
|
-
|
|
29922
|
-
// src/hostsDir.ts
|
|
29923
|
-
import { join as join6 } from "node:path";
|
|
29924
|
-
var HOSTS_SEG = "hosts";
|
|
29925
|
-
var MAX_SYNC_PATH_LEN = 400;
|
|
29926
|
-
var MAX_SYNC_SEGMENTS = 24;
|
|
29927
|
-
var CONTROL_CHAR_RE = new RegExp(
|
|
29928
|
-
"[\\u0000-\\u001f\\u007f-\\u009f]"
|
|
29929
|
-
);
|
|
29930
|
-
var DIR_NAME_RE = /^[a-z0-9_-]{1,32}$/;
|
|
29931
|
-
function assertSyncPath(path) {
|
|
29932
|
-
if (!path) throw new Error("sync path is empty");
|
|
29933
|
-
if (path.length > MAX_SYNC_PATH_LEN) throw new Error(`sync path too long (> ${MAX_SYNC_PATH_LEN})`);
|
|
29934
|
-
if (CONTROL_CHAR_RE.test(path)) throw new Error("sync path contains control characters");
|
|
29935
|
-
if (path.includes(":")) throw new Error("sync path contains a colon");
|
|
29936
|
-
const n2 = normalizeFilePath(path);
|
|
29937
|
-
if (!n2) throw new Error("sync path normalizes to empty (targets the root)");
|
|
29938
|
-
const segments = n2.split("/");
|
|
29939
|
-
if (segments.length > MAX_SYNC_SEGMENTS) throw new Error(`sync path has too many segments (> ${MAX_SYNC_SEGMENTS})`);
|
|
29940
|
-
if (segments.some((s2) => s2 === "." || s2 === "..")) {
|
|
29941
|
-
throw new Error('sync path contains a "." or ".." segment');
|
|
29942
|
-
}
|
|
29943
|
-
}
|
|
29944
|
-
function hostsDirFor(stateDir, dirName2) {
|
|
29945
|
-
if (!DIR_NAME_RE.test(dirName2)) throw new Error(`invalid host dir name: ${dirName2}`);
|
|
29946
|
-
return join6(stateDir, HOSTS_SEG, dirName2);
|
|
29947
|
-
}
|
|
29948
|
-
var RAW = /* @__PURE__ */ new WeakMap();
|
|
29949
|
-
function hostFilesFor(stateDir, dirName2) {
|
|
29950
|
-
const raw = new NodeFileService(hostsDirFor(stateDir, dirName2));
|
|
29951
|
-
const wrapper = {
|
|
29952
|
-
kind: raw.kind,
|
|
29953
|
-
label: raw.label,
|
|
29954
|
-
isLocalDisk: raw.isLocalDisk,
|
|
29955
|
-
writable: raw.writable,
|
|
29956
|
-
list: (dir) => raw.list(dir),
|
|
29957
|
-
read: (path) => raw.read(path),
|
|
29958
|
-
stat: (path) => raw.stat(path),
|
|
29959
|
-
search: (query) => raw.search(query),
|
|
29960
|
-
onChange: (_cb) => raw.onChange()
|
|
29961
|
-
};
|
|
29962
|
-
RAW.set(wrapper, raw);
|
|
29963
|
-
return wrapper;
|
|
29964
|
-
}
|
|
29965
|
-
async function syncWrite(files, path, content) {
|
|
29966
|
-
assertSyncPath(path);
|
|
29967
|
-
const raw = RAW.get(files);
|
|
29968
|
-
if (!raw) throw new Error("syncWrite: not a hostFilesFor() result");
|
|
29969
|
-
await raw.write(path, content);
|
|
29970
|
-
}
|
|
29971
|
-
async function syncRemove(files, path) {
|
|
29972
|
-
assertSyncPath(path);
|
|
29973
|
-
const raw = RAW.get(files);
|
|
29974
|
-
if (!raw) throw new Error("syncRemove: not a hostFilesFor() result");
|
|
29975
|
-
await raw.remove(path);
|
|
29976
|
-
}
|
|
29977
|
-
|
|
29978
|
-
// src/runner/owners.ts
|
|
29979
|
-
var WORKSPACES_SEG = "workspaces";
|
|
29980
|
-
var WORKSPACE_ID_RE = /^[a-z0-9_-]{1,32}$/;
|
|
29981
|
-
async function workspaceRootsFor(dirName2, hostDir) {
|
|
29982
|
-
let entries;
|
|
29983
|
-
try {
|
|
29984
|
-
entries = await readdir(join7(hostDir, WORKSPACES_SEG), { withFileTypes: true });
|
|
29985
|
-
} catch {
|
|
29986
|
-
return [];
|
|
29987
|
-
}
|
|
29988
|
-
const out = [];
|
|
29989
|
-
for (const entry of entries) {
|
|
29990
|
-
if (!entry.isDirectory()) continue;
|
|
29991
|
-
if (!WORKSPACE_ID_RE.test(entry.name)) continue;
|
|
29992
|
-
out.push({
|
|
29993
|
-
owner: `${dirName2}/ws/${entry.name}`,
|
|
29994
|
-
files: new NodeFileService(join7(hostDir, WORKSPACES_SEG, entry.name)),
|
|
29995
|
-
root: "crew"
|
|
29996
|
-
});
|
|
29997
|
-
}
|
|
29998
|
-
return out;
|
|
29999
|
-
}
|
|
30000
|
-
async function listOwnerRoots(stateDir) {
|
|
30001
|
-
const roots = [
|
|
30002
|
-
{ owner: "", files: new NodeFileService(stateDir), root: "crew" }
|
|
30003
|
-
];
|
|
30004
|
-
let entries;
|
|
30005
|
-
try {
|
|
30006
|
-
entries = await readdir(join7(stateDir, HOSTS_SEG), { withFileTypes: true });
|
|
30007
|
-
} catch {
|
|
30008
|
-
return roots;
|
|
30009
|
-
}
|
|
30010
|
-
for (const entry of entries) {
|
|
30011
|
-
if (!entry.isDirectory()) continue;
|
|
30012
|
-
const dirName2 = entry.name;
|
|
30013
|
-
let hostDir;
|
|
30014
|
-
try {
|
|
30015
|
-
hostDir = hostsDirFor(stateDir, dirName2);
|
|
30016
|
-
} catch {
|
|
30017
|
-
continue;
|
|
30018
|
-
}
|
|
30019
|
-
roots.push({ owner: dirName2, files: new NodeFileService(hostDir), root: "crew" });
|
|
30020
|
-
roots.push(...await workspaceRootsFor(dirName2, hostDir));
|
|
30021
|
-
}
|
|
30022
|
-
return roots;
|
|
30023
|
-
}
|
|
30024
|
-
|
|
30025
30356
|
// ../../node_modules/.pnpm/openai@4.104.0_ws@8.21.0/node_modules/openai/internal/qs/formats.mjs
|
|
30026
30357
|
var default_format = "RFC3986";
|
|
30027
30358
|
var formatters = {
|
|
@@ -36773,10 +37104,10 @@ function statusOf(message) {
|
|
|
36773
37104
|
function classifyStreamError(message, status) {
|
|
36774
37105
|
if (/\baborted\b|AbortError/i.test(message)) return "fatal";
|
|
36775
37106
|
if (/\b(timed out|request timeout|gateway timeout|deadline exceeded)\b/i.test(message)) return "fatal";
|
|
36776
|
-
const
|
|
36777
|
-
if (
|
|
36778
|
-
if (FATAL_STATUS.has(
|
|
36779
|
-
if (TRANSIENT_STATUS.has(
|
|
37107
|
+
const code2 = status ?? statusOf(message);
|
|
37108
|
+
if (code2 != null) {
|
|
37109
|
+
if (FATAL_STATUS.has(code2)) return "fatal";
|
|
37110
|
+
if (TRANSIENT_STATUS.has(code2)) return "transient";
|
|
36780
37111
|
}
|
|
36781
37112
|
if (/\b(invalid[\s_-]?api[\s_-]?key|incorrect api key|unauthorized|forbidden|quota exceeded|insufficient)\b/i.test(message)) return "fatal";
|
|
36782
37113
|
return "transient";
|
|
@@ -36875,6 +37206,7 @@ var BUILTIN_NAMESPACES = {
|
|
|
36875
37206
|
ask_user: "agent",
|
|
36876
37207
|
spawn_agent: "agent",
|
|
36877
37208
|
spawn_agents: "agent",
|
|
37209
|
+
call_agent: "agent",
|
|
36878
37210
|
todo_write: "agent",
|
|
36879
37211
|
// Named here as a backstop; the sidecar's own decls also carry `namespace`,
|
|
36880
37212
|
// and an explicit field always wins (resolveNamespace). Both say the same
|
|
@@ -36893,7 +37225,17 @@ var BUILTIN_NAMESPACES = {
|
|
|
36893
37225
|
view_strategy: "agent",
|
|
36894
37226
|
run_strategy: "agent",
|
|
36895
37227
|
list_strategies: "agent",
|
|
36896
|
-
list_saved_strategies: "agent"
|
|
37228
|
+
list_saved_strategies: "agent",
|
|
37229
|
+
// Long-lived process control (proc_start/proc_stop/proc_list, see tools/procs.ts).
|
|
37230
|
+
// `shell` is its own subject, deliberately distinct from the one-shot exec's
|
|
37231
|
+
// `sidecar:shell_exec`: a process that stays running is a different risk from
|
|
37232
|
+
// one that returns, and one address lets an operator ban (or allow) both
|
|
37233
|
+
// start and stop together. proc_list is a read - it namespaces as `sidecar`
|
|
37234
|
+
// (like the other machine-state reads) so it is NOT swept up by a `shell:*`
|
|
37235
|
+
// deny; listing what is running carries none of spawn/kill's risk.
|
|
37236
|
+
proc_start: "shell",
|
|
37237
|
+
proc_stop: "shell",
|
|
37238
|
+
proc_list: "sidecar"
|
|
36897
37239
|
};
|
|
36898
37240
|
function resolveNamespace(tool) {
|
|
36899
37241
|
if (tool.namespace) return tool.namespace;
|
|
@@ -36921,6 +37263,7 @@ function toolAddress(tool) {
|
|
|
36921
37263
|
}
|
|
36922
37264
|
}
|
|
36923
37265
|
}
|
|
37266
|
+
if (ns === "shell" && (leaf === "proc_start" || leaf === "proc_stop")) leaf = "spawn";
|
|
36924
37267
|
return `${ns}:${leaf}`;
|
|
36925
37268
|
}
|
|
36926
37269
|
|
|
@@ -36932,8 +37275,12 @@ function effectiveThreshold(mode, fill = 0) {
|
|
|
36932
37275
|
const factor = fill < 0.5 ? 1 : fill < 0.7 ? 0.7 : fill < 0.85 ? 0.5 : 0.35;
|
|
36933
37276
|
return Math.max(FLOOR[mode], Math.round(BASE[mode] * factor));
|
|
36934
37277
|
}
|
|
37278
|
+
var OFFLOAD_MARKER_PREFIX = "[offloaded tool result \xB7 ";
|
|
37279
|
+
function isOffloadPointer(content) {
|
|
37280
|
+
return content.startsWith(OFFLOAD_MARKER_PREFIX);
|
|
37281
|
+
}
|
|
36935
37282
|
function formatOffloadRef(ref) {
|
|
36936
|
-
const head =
|
|
37283
|
+
const head = `${OFFLOAD_MARKER_PREFIX}${ref.toolName} \xB7 ${ref.bytes.toLocaleString()} bytes \xB7 recall the full content with recall_result({ id: "${ref.id}" })]`;
|
|
36937
37284
|
return ref.preview ? `${head}
|
|
36938
37285
|
--- preview ---
|
|
36939
37286
|
${ref.preview}${ref.bytes > ref.preview.length ? "\n\u2026(truncated)" : ""}` : head;
|
|
@@ -36956,7 +37303,9 @@ async function runAgent(args) {
|
|
|
36956
37303
|
const permission = args.permission;
|
|
36957
37304
|
const compression = args.compression;
|
|
36958
37305
|
const ctxWindow = compression?.contextWindow;
|
|
37306
|
+
const compressionStrategy = compression?.strategy ?? "eager";
|
|
36959
37307
|
let fill = 0;
|
|
37308
|
+
const deferredOffloads = [];
|
|
36960
37309
|
const maxTurns = policy.maxTurns ?? args.maxTurns ?? 8;
|
|
36961
37310
|
const signal = args.signal ?? new AbortController().signal;
|
|
36962
37311
|
const emit = (e2) => args.onEvent?.(e2);
|
|
@@ -36973,6 +37322,9 @@ async function runAgent(args) {
|
|
|
36973
37322
|
function: { name: t2.name, description: t2.description, parameters: t2.parameters }
|
|
36974
37323
|
}));
|
|
36975
37324
|
const toolByName = new Map(tools.map((t2) => [t2.name, t2]));
|
|
37325
|
+
if (compressionStrategy === "deferred" && compression && compression.mode !== "off" && toolCtx.recall) {
|
|
37326
|
+
await compactIncomingOffloads(messages, toolByName, compression, toolCtx.recall, emit);
|
|
37327
|
+
}
|
|
36976
37328
|
const usage = { promptTokens: 0, completionTokens: 0 };
|
|
36977
37329
|
let text = "";
|
|
36978
37330
|
let stopReason = "stop";
|
|
@@ -37046,6 +37398,13 @@ async function runAgent(args) {
|
|
|
37046
37398
|
stopReason = "error";
|
|
37047
37399
|
break;
|
|
37048
37400
|
}
|
|
37401
|
+
if (compressionStrategy === "deferred" && deferredOffloads.length) {
|
|
37402
|
+
for (const o2 of deferredOffloads) {
|
|
37403
|
+
const orig = messages[o2.index];
|
|
37404
|
+
if (orig.role === "tool") messages[o2.index] = { ...orig, content: formatOffloadRef(o2.ref) };
|
|
37405
|
+
}
|
|
37406
|
+
deferredOffloads.length = 0;
|
|
37407
|
+
}
|
|
37049
37408
|
usage.promptTokens += streamed.usage.promptTokens;
|
|
37050
37409
|
usage.completionTokens += streamed.usage.completionTokens;
|
|
37051
37410
|
if (ctxWindow && ctxWindow > 0 && streamed.usage.promptTokens) {
|
|
@@ -37121,14 +37480,21 @@ async function runAgent(args) {
|
|
|
37121
37480
|
const decision = await permission.check({ name: tool.name, address, mutates: tool.mutates, permission: tool.permission }, parsed);
|
|
37122
37481
|
emit({ type: "permission", tool: tool.name, decision });
|
|
37123
37482
|
if (decision === "deny") {
|
|
37124
|
-
|
|
37483
|
+
const denialMsg = args.describePermissionDenial?.(tool.name) ?? "Permission denied by the user \u2014 the action was not performed.";
|
|
37484
|
+
messages.push({ role: "tool", tool_call_id: call.id, content: denialMsg });
|
|
37125
37485
|
emit({ type: "tool-result", id: call.id, name: tool.name, result: "permission denied", isError: true });
|
|
37126
37486
|
continue;
|
|
37127
37487
|
}
|
|
37128
37488
|
}
|
|
37129
37489
|
const startedAt = Date.now();
|
|
37490
|
+
let callSettled = false;
|
|
37491
|
+
const stream = (text2) => {
|
|
37492
|
+
if (callSettled) return;
|
|
37493
|
+
emit({ type: "tool-delta", id: call.id, name: call.name, delta: text2 });
|
|
37494
|
+
};
|
|
37130
37495
|
try {
|
|
37131
|
-
const result = await tool.run(parsed, { ...toolCtx, callId: call.id });
|
|
37496
|
+
const result = await tool.run(parsed, { ...toolCtx, callId: call.id, stream });
|
|
37497
|
+
callSettled = true;
|
|
37132
37498
|
const durationMs = Date.now() - startedAt;
|
|
37133
37499
|
gstate.toolCalls++;
|
|
37134
37500
|
gstate.calledTools.add(tool.name);
|
|
@@ -37142,8 +37508,12 @@ async function runAgent(args) {
|
|
|
37142
37508
|
if (result.length > effectiveThreshold(compression.mode, fill)) {
|
|
37143
37509
|
try {
|
|
37144
37510
|
const ref = await toolCtx.recall.record(tool.name, result);
|
|
37145
|
-
out = formatOffloadRef(ref);
|
|
37146
37511
|
emit({ type: "offload", id: ref.id, name: tool.name, bytes: ref.bytes });
|
|
37512
|
+
if (compressionStrategy === "deferred") {
|
|
37513
|
+
deferredOffloads.push({ index: messages.length, ref });
|
|
37514
|
+
} else {
|
|
37515
|
+
out = formatOffloadRef(ref);
|
|
37516
|
+
}
|
|
37147
37517
|
} catch {
|
|
37148
37518
|
out = result;
|
|
37149
37519
|
}
|
|
@@ -37152,6 +37522,7 @@ async function runAgent(args) {
|
|
|
37152
37522
|
messages.push({ role: "tool", tool_call_id: call.id, content: out });
|
|
37153
37523
|
emit({ type: "tool-result", id: call.id, name: call.name, result: out, durationMs });
|
|
37154
37524
|
} catch (e2) {
|
|
37525
|
+
callSettled = true;
|
|
37155
37526
|
const errMsg = String(e2?.message || e2);
|
|
37156
37527
|
const kind2 = classifyToolError(errMsg);
|
|
37157
37528
|
const attempt = toolFails.fail(tool.name, kind2);
|
|
@@ -37175,6 +37546,30 @@ async function runAgent(args) {
|
|
|
37175
37546
|
emit({ type: "done", reason: stopReason });
|
|
37176
37547
|
return { messages, text, usage, stopReason };
|
|
37177
37548
|
}
|
|
37549
|
+
async function compactIncomingOffloads(messages, toolByName, compression, recall, emit) {
|
|
37550
|
+
const nameForCallId = /* @__PURE__ */ new Map();
|
|
37551
|
+
for (const m2 of messages) {
|
|
37552
|
+
if (m2.role === "assistant" && m2.tool_calls) {
|
|
37553
|
+
for (const c2 of m2.tool_calls) nameForCallId.set(c2.id, c2.function.name);
|
|
37554
|
+
}
|
|
37555
|
+
}
|
|
37556
|
+
for (let i2 = 0; i2 < messages.length; i2++) {
|
|
37557
|
+
const m2 = messages[i2];
|
|
37558
|
+
if (m2.role !== "tool" || typeof m2.content !== "string") continue;
|
|
37559
|
+
if (isOffloadPointer(m2.content)) continue;
|
|
37560
|
+
const toolName = nameForCallId.get(m2.tool_call_id);
|
|
37561
|
+
if (!toolName) continue;
|
|
37562
|
+
const tool = toolByName.get(toolName);
|
|
37563
|
+
if (!tool || tool.noOffload) continue;
|
|
37564
|
+
if (m2.content.length <= effectiveThreshold(compression.mode, 0)) continue;
|
|
37565
|
+
try {
|
|
37566
|
+
const ref = await recall.record(toolName, m2.content);
|
|
37567
|
+
messages[i2] = { ...m2, content: formatOffloadRef(ref) };
|
|
37568
|
+
emit({ type: "offload", id: ref.id, name: toolName, bytes: ref.bytes });
|
|
37569
|
+
} catch {
|
|
37570
|
+
}
|
|
37571
|
+
}
|
|
37572
|
+
}
|
|
37178
37573
|
function reconcileDanglingToolCalls(messages) {
|
|
37179
37574
|
let idx = -1;
|
|
37180
37575
|
for (let i2 = messages.length - 1; i2 >= 0; i2--) {
|
|
@@ -38222,7 +38617,7 @@ var NodeRunnerService = class {
|
|
|
38222
38617
|
// src/runner/core.ts
|
|
38223
38618
|
import { watch } from "node:fs";
|
|
38224
38619
|
import { readdir as readdir2, stat } from "node:fs/promises";
|
|
38225
|
-
import { join as
|
|
38620
|
+
import { join as join10, relative as relative5, sep as sep3 } from "node:path";
|
|
38226
38621
|
|
|
38227
38622
|
// src/runner/triggerConfig.ts
|
|
38228
38623
|
function inputFor(trigger, fallback) {
|
|
@@ -38702,6 +39097,7 @@ function startDaemonCore(opts) {
|
|
|
38702
39097
|
w2.on("error", (err) => log(`daemon core: watch error on "${dir}" -- ${err.message}`));
|
|
38703
39098
|
watchers.push(w2);
|
|
38704
39099
|
} catch (err) {
|
|
39100
|
+
if (err?.code === "ENOENT") continue;
|
|
38705
39101
|
log(`daemon core: could not watch "${dir}" -- ${err instanceof Error ? err.message : String(err)}`);
|
|
38706
39102
|
}
|
|
38707
39103
|
}
|
|
@@ -38731,9 +39127,9 @@ function startDaemonCore(opts) {
|
|
|
38731
39127
|
for (const entry of entries) {
|
|
38732
39128
|
if (entry.isDirectory()) {
|
|
38733
39129
|
if (isExcludedSegment(entry.name, dotDir)) continue;
|
|
38734
|
-
await walkForChanges(root,
|
|
39130
|
+
await walkForChanges(root, join10(dir, entry.name), since);
|
|
38735
39131
|
} else {
|
|
38736
|
-
const abs =
|
|
39132
|
+
const abs = join10(dir, entry.name);
|
|
38737
39133
|
let mtimeMs;
|
|
38738
39134
|
try {
|
|
38739
39135
|
mtimeMs = (await stat(abs)).mtimeMs;
|
|
@@ -39011,26 +39407,26 @@ function makeRunHub(opts = {}) {
|
|
|
39011
39407
|
}
|
|
39012
39408
|
|
|
39013
39409
|
// src/identity.ts
|
|
39014
|
-
import { promises as
|
|
39410
|
+
import { promises as fs10 } from "node:fs";
|
|
39015
39411
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
39016
39412
|
import { hostname } from "node:os";
|
|
39017
|
-
import { join as
|
|
39018
|
-
function idPath(dir =
|
|
39019
|
-
return
|
|
39413
|
+
import { join as join11 } from "node:path";
|
|
39414
|
+
function idPath(dir = sidecarSubdir()) {
|
|
39415
|
+
return join11(dir, "daemon.id");
|
|
39020
39416
|
}
|
|
39021
|
-
async function loadOrCreateSidecarId(dir =
|
|
39417
|
+
async function loadOrCreateSidecarId(dir = sidecarSubdir()) {
|
|
39022
39418
|
const path = idPath(dir);
|
|
39023
39419
|
try {
|
|
39024
|
-
const existing = (await
|
|
39420
|
+
const existing = (await fs10.readFile(path, "utf8")).trim();
|
|
39025
39421
|
if (existing) return existing;
|
|
39026
39422
|
} catch (err) {
|
|
39027
39423
|
if (!isEnoent(err)) throw err;
|
|
39028
39424
|
}
|
|
39029
|
-
await
|
|
39425
|
+
await fs10.mkdir(dir, { recursive: true });
|
|
39030
39426
|
const id = randomBytes2(16).toString("hex");
|
|
39031
|
-
await
|
|
39427
|
+
await fs10.writeFile(path, id + "\n", { mode: 384 });
|
|
39032
39428
|
try {
|
|
39033
|
-
await
|
|
39429
|
+
await fs10.chmod(path, 384);
|
|
39034
39430
|
} catch {
|
|
39035
39431
|
}
|
|
39036
39432
|
return id;
|
|
@@ -39043,9 +39439,9 @@ function sidecarName(opts) {
|
|
|
39043
39439
|
}
|
|
39044
39440
|
|
|
39045
39441
|
// src/hosts.ts
|
|
39046
|
-
import { promises as
|
|
39442
|
+
import { promises as fs11 } from "node:fs";
|
|
39047
39443
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
39048
|
-
import { join as
|
|
39444
|
+
import { join as join12 } from "node:path";
|
|
39049
39445
|
var MAX_DIR = 32;
|
|
39050
39446
|
var MAX_FIELD_LEN = 200;
|
|
39051
39447
|
var MAX_HOSTS = 200;
|
|
@@ -39079,7 +39475,7 @@ function sanitizeDirName(raw) {
|
|
|
39079
39475
|
return RESERVED_WINDOWS_NAMES.has(safe) ? `${safe}-host` : safe;
|
|
39080
39476
|
}
|
|
39081
39477
|
function hostsPath(dir) {
|
|
39082
|
-
return
|
|
39478
|
+
return join12(dir, "hosts.json");
|
|
39083
39479
|
}
|
|
39084
39480
|
function emptyManifest() {
|
|
39085
39481
|
return /* @__PURE__ */ Object.create(null);
|
|
@@ -39100,7 +39496,7 @@ function repairHostRecord(value, fallbackLastSeen) {
|
|
|
39100
39496
|
async function read(dir, fallbackLastSeen) {
|
|
39101
39497
|
let raw;
|
|
39102
39498
|
try {
|
|
39103
|
-
raw = await
|
|
39499
|
+
raw = await fs11.readFile(hostsPath(dir), "utf8");
|
|
39104
39500
|
} catch {
|
|
39105
39501
|
return emptyManifest();
|
|
39106
39502
|
}
|
|
@@ -39119,14 +39515,14 @@ async function read(dir, fallbackLastSeen) {
|
|
|
39119
39515
|
return manifest;
|
|
39120
39516
|
}
|
|
39121
39517
|
async function write(dir, manifest) {
|
|
39122
|
-
await
|
|
39518
|
+
await fs11.mkdir(dir, { recursive: true });
|
|
39123
39519
|
const target = hostsPath(dir);
|
|
39124
|
-
const tmp =
|
|
39520
|
+
const tmp = join12(dir, `.hosts.json.tmp-${randomBytes3(8).toString("hex")}`);
|
|
39125
39521
|
try {
|
|
39126
|
-
await
|
|
39127
|
-
await
|
|
39522
|
+
await fs11.writeFile(tmp, JSON.stringify(manifest, null, 2));
|
|
39523
|
+
await fs11.rename(tmp, target);
|
|
39128
39524
|
} catch (err) {
|
|
39129
|
-
await
|
|
39525
|
+
await fs11.rm(tmp, { force: true }).catch(() => void 0);
|
|
39130
39526
|
throw err;
|
|
39131
39527
|
}
|
|
39132
39528
|
}
|
|
@@ -39274,8 +39670,8 @@ function makeHostSync(opts) {
|
|
|
39274
39670
|
}
|
|
39275
39671
|
|
|
39276
39672
|
// src/hostsCli.ts
|
|
39277
|
-
import { resolve as resolve5, join as
|
|
39278
|
-
import { promises as
|
|
39673
|
+
import { resolve as resolve5, join as join13 } from "node:path";
|
|
39674
|
+
import { promises as fs12 } from "node:fs";
|
|
39279
39675
|
async function listHosts(manifest) {
|
|
39280
39676
|
const m2 = await manifest.list();
|
|
39281
39677
|
return Object.entries(m2).map(([clientId, r2]) => ({
|
|
@@ -39298,7 +39694,7 @@ async function forgetHost(stateDir, manifest, dirName2) {
|
|
|
39298
39694
|
return { ok: false, reason: e2 instanceof Error ? e2.message : String(e2) };
|
|
39299
39695
|
}
|
|
39300
39696
|
try {
|
|
39301
|
-
await
|
|
39697
|
+
await fs12.rm(dir, { recursive: true, force: true });
|
|
39302
39698
|
} catch (e2) {
|
|
39303
39699
|
return {
|
|
39304
39700
|
ok: false,
|
|
@@ -39309,10 +39705,10 @@ async function forgetHost(stateDir, manifest, dirName2) {
|
|
|
39309
39705
|
return { ok: true };
|
|
39310
39706
|
}
|
|
39311
39707
|
async function checkHostsDrift(stateDir, manifest, log) {
|
|
39312
|
-
const
|
|
39708
|
+
const hostsRoot2 = join13(stateDir, HOSTS_SEG);
|
|
39313
39709
|
let entries;
|
|
39314
39710
|
try {
|
|
39315
|
-
entries = (await
|
|
39711
|
+
entries = (await fs12.readdir(hostsRoot2, { withFileTypes: true })).filter((d2) => d2.isDirectory()).map((d2) => d2.name);
|
|
39316
39712
|
} catch {
|
|
39317
39713
|
return [];
|
|
39318
39714
|
}
|
|
@@ -39377,6 +39773,114 @@ async function runHostsCli(argv) {
|
|
|
39377
39773
|
|
|
39378
39774
|
// src/sessionsCli.ts
|
|
39379
39775
|
import { resolve as resolve6 } from "node:path";
|
|
39776
|
+
|
|
39777
|
+
// ../harness/sessions/fileRecallStore.ts
|
|
39778
|
+
var UNATTACHED_SESSION = "_unattached";
|
|
39779
|
+
|
|
39780
|
+
// ../harness/sessions/blobGc.ts
|
|
39781
|
+
var DEFAULT_UNATTACHED_GRACE_MS = 30 * 60 * 1e3;
|
|
39782
|
+
var MARKER_ID_RE = /recall_result\(\{\s*id:\s*\\?"([^"\\]+)\\?"\s*\}\)/g;
|
|
39783
|
+
async function globalReferencedIds(files, root, sessionDirs) {
|
|
39784
|
+
const ids = /* @__PURE__ */ new Set();
|
|
39785
|
+
for (const dir of sessionDirs) {
|
|
39786
|
+
if (!dir.hasSessionLog) continue;
|
|
39787
|
+
const text = await files.read(`${root}/${dir.id}/SESSION.jsonl`).catch(() => null);
|
|
39788
|
+
if (text == null) continue;
|
|
39789
|
+
for (const m2 of text.matchAll(MARKER_ID_RE)) {
|
|
39790
|
+
const id = m2[1];
|
|
39791
|
+
if (id) ids.add(id);
|
|
39792
|
+
}
|
|
39793
|
+
}
|
|
39794
|
+
return ids;
|
|
39795
|
+
}
|
|
39796
|
+
async function listSessionDirs(files, root) {
|
|
39797
|
+
const nodes = await files.list(root).catch(() => []);
|
|
39798
|
+
const flat = flattenFiles(nodes).filter((f2) => f2.path.startsWith(`${root}/`));
|
|
39799
|
+
const dirs = /* @__PURE__ */ new Map();
|
|
39800
|
+
for (const f2 of flat) {
|
|
39801
|
+
const rest = f2.path.slice(root.length + 1);
|
|
39802
|
+
const slash = rest.indexOf("/");
|
|
39803
|
+
if (slash < 0) continue;
|
|
39804
|
+
const id = rest.slice(0, slash);
|
|
39805
|
+
const isSessionLog = rest === `${id}/SESSION.jsonl`;
|
|
39806
|
+
if (!dirs.has(id)) dirs.set(id, false);
|
|
39807
|
+
if (isSessionLog) dirs.set(id, true);
|
|
39808
|
+
}
|
|
39809
|
+
return [...dirs.entries()].map(([id, hasSessionLog]) => ({ id, hasSessionLog }));
|
|
39810
|
+
}
|
|
39811
|
+
async function listBlobs(files, root, sessionId) {
|
|
39812
|
+
const dir = `${root}/${sessionId}/blobs`;
|
|
39813
|
+
const nodes = await files.list(dir).catch(() => []);
|
|
39814
|
+
const blobFiles = flattenFiles(nodes).filter((f2) => f2.path.startsWith(`${dir}/`) && f2.path.endsWith(".json"));
|
|
39815
|
+
const out = [];
|
|
39816
|
+
for (const f2 of blobFiles) {
|
|
39817
|
+
const text = await files.read(f2.path).catch(() => null);
|
|
39818
|
+
if (text == null) continue;
|
|
39819
|
+
try {
|
|
39820
|
+
const record = JSON.parse(text);
|
|
39821
|
+
if (record && typeof record.id === "string") out.push({ path: f2.path, record });
|
|
39822
|
+
} catch {
|
|
39823
|
+
}
|
|
39824
|
+
}
|
|
39825
|
+
return out;
|
|
39826
|
+
}
|
|
39827
|
+
async function planBlobGc(files, opts = {}) {
|
|
39828
|
+
const root = opts.root ?? sessionsRoot();
|
|
39829
|
+
const now2 = (opts.now ?? Date.now)();
|
|
39830
|
+
const graceMs = opts.graceMs ?? DEFAULT_UNATTACHED_GRACE_MS;
|
|
39831
|
+
const sessionDirs = await listSessionDirs(files, root);
|
|
39832
|
+
const referenced = await globalReferencedIds(files, root, sessionDirs);
|
|
39833
|
+
const reapable = [];
|
|
39834
|
+
const liveUnreferenced = [];
|
|
39835
|
+
for (const dir of sessionDirs) {
|
|
39836
|
+
const blobs = await listBlobs(files, root, dir.id);
|
|
39837
|
+
for (const { path, record } of blobs) {
|
|
39838
|
+
const isReferenced = referenced.has(record.id);
|
|
39839
|
+
const base = {
|
|
39840
|
+
sessionId: dir.id,
|
|
39841
|
+
id: record.id,
|
|
39842
|
+
path,
|
|
39843
|
+
bytes: record.bytes,
|
|
39844
|
+
toolName: record.toolName,
|
|
39845
|
+
createdAt: record.createdAt
|
|
39846
|
+
};
|
|
39847
|
+
if (isReferenced) continue;
|
|
39848
|
+
if (dir.id === UNATTACHED_SESSION) {
|
|
39849
|
+
const age = now2 - record.createdAt;
|
|
39850
|
+
if (age < graceMs) continue;
|
|
39851
|
+
reapable.push({
|
|
39852
|
+
...base,
|
|
39853
|
+
disposition: "reap",
|
|
39854
|
+
reason: `unattached, unreferenced, and ${Math.round(age / 6e4)}m old (grace is ${Math.round(graceMs / 6e4)}m)`
|
|
39855
|
+
});
|
|
39856
|
+
continue;
|
|
39857
|
+
}
|
|
39858
|
+
if (!dir.hasSessionLog) {
|
|
39859
|
+
reapable.push({ ...base, disposition: "reap", reason: "owning session directory has no SESSION.jsonl" });
|
|
39860
|
+
continue;
|
|
39861
|
+
}
|
|
39862
|
+
liveUnreferenced.push({ ...base, disposition: "report-only", reason: "unreferenced, but the owning session is live \u2014 not deleted by this tool" });
|
|
39863
|
+
}
|
|
39864
|
+
}
|
|
39865
|
+
return {
|
|
39866
|
+
reapable,
|
|
39867
|
+
liveUnreferenced,
|
|
39868
|
+
reapableBytes: reapable.reduce((sum, e2) => sum + e2.bytes, 0),
|
|
39869
|
+
liveUnreferencedBytes: liveUnreferenced.reduce((sum, e2) => sum + e2.bytes, 0)
|
|
39870
|
+
};
|
|
39871
|
+
}
|
|
39872
|
+
async function runBlobGc(files, opts = {}) {
|
|
39873
|
+
const plan = await planBlobGc(files, opts);
|
|
39874
|
+
if (!opts.delete) return { plan, removed: [] };
|
|
39875
|
+
const removed = [];
|
|
39876
|
+
for (const entry of plan.reapable) {
|
|
39877
|
+
await files.remove(entry.path).catch(() => void 0);
|
|
39878
|
+
removed.push(entry);
|
|
39879
|
+
}
|
|
39880
|
+
return { plan, removed };
|
|
39881
|
+
}
|
|
39882
|
+
|
|
39883
|
+
// src/sessionsCli.ts
|
|
39380
39884
|
async function sandboxLifecycle(run, verb, name) {
|
|
39381
39885
|
const call = async (args) => {
|
|
39382
39886
|
const r2 = await run(args).catch((e2) => ({ code: 1, stdout: "", stderr: String(e2) }));
|
|
@@ -39448,6 +39952,31 @@ function formatSession(r2) {
|
|
|
39448
39952
|
...r2.repo ? [`repo ${r2.repo}`] : []
|
|
39449
39953
|
].join("\n");
|
|
39450
39954
|
}
|
|
39955
|
+
function formatBlobGcReport(result, deleted) {
|
|
39956
|
+
const line = (e2) => ` ${e2.path} ${formatByteSize(e2.bytes)} (${e2.reason})`;
|
|
39957
|
+
const lines = [];
|
|
39958
|
+
const { plan } = result;
|
|
39959
|
+
if (!plan.reapable.length && !plan.liveUnreferenced.length) {
|
|
39960
|
+
lines.push("no unattached blobs found.");
|
|
39961
|
+
return lines.join("\n");
|
|
39962
|
+
}
|
|
39963
|
+
if (plan.reapable.length) {
|
|
39964
|
+
lines.push(deleted ? `removed ${result.removed.length} blob(s), ${formatByteSize(plan.reapableBytes)} freed:` : `would remove ${plan.reapable.length} blob(s), ${formatByteSize(plan.reapableBytes)} total:`);
|
|
39965
|
+
lines.push(...plan.reapable.map(line));
|
|
39966
|
+
} else {
|
|
39967
|
+
lines.push("nothing is reap-eligible.");
|
|
39968
|
+
}
|
|
39969
|
+
if (plan.liveUnreferenced.length) {
|
|
39970
|
+
lines.push("");
|
|
39971
|
+
lines.push(`${plan.liveUnreferenced.length} unreferenced blob(s) in live sessions, ${formatByteSize(plan.liveUnreferencedBytes)} total (not removed by this tool):`);
|
|
39972
|
+
lines.push(...plan.liveUnreferenced.map(line));
|
|
39973
|
+
}
|
|
39974
|
+
if (!deleted && plan.reapable.length) {
|
|
39975
|
+
lines.push("");
|
|
39976
|
+
lines.push("pass --delete to remove the first list. Nothing above was touched.");
|
|
39977
|
+
}
|
|
39978
|
+
return lines.join("\n");
|
|
39979
|
+
}
|
|
39451
39980
|
var DEFAULT_GUEST_IMAGE = "corenel-guest:dev";
|
|
39452
39981
|
function backendFor(deps, roots, run) {
|
|
39453
39982
|
return makeMicrovmBackend({
|
|
@@ -39619,13 +40148,28 @@ async function runSessionsCli(argv, deps) {
|
|
|
39619
40148
|
return 0;
|
|
39620
40149
|
} catch (e2) {
|
|
39621
40150
|
err(`corenel sessions ${verb}: ${e2 instanceof Error ? e2.message : String(e2)}
|
|
40151
|
+
`);
|
|
40152
|
+
return 1;
|
|
40153
|
+
}
|
|
40154
|
+
}
|
|
40155
|
+
if (verb === "gc") {
|
|
40156
|
+
const root = rootFrom(argv, process.cwd());
|
|
40157
|
+
const doDelete = argv.includes("--delete");
|
|
40158
|
+
try {
|
|
40159
|
+
const files = new NodeFileService(root);
|
|
40160
|
+
const result = await runBlobGc(files, { delete: doDelete });
|
|
40161
|
+
out(`${formatBlobGcReport(result, doDelete)}
|
|
40162
|
+
`);
|
|
40163
|
+
return 0;
|
|
40164
|
+
} catch (e2) {
|
|
40165
|
+
err(`corenel sessions gc: ${e2 instanceof Error ? e2.message : String(e2)}
|
|
39622
40166
|
`);
|
|
39623
40167
|
return 1;
|
|
39624
40168
|
}
|
|
39625
40169
|
}
|
|
39626
40170
|
err(`corenel sessions: unknown subcommand "${verb}"
|
|
39627
40171
|
usage: corenel sessions [list | show <id> | create [--root <dir>] | run <id> "<prompt>"
|
|
39628
|
-
| pause <id> | resume <id> | destroy <id>]
|
|
40172
|
+
| pause <id> | resume <id> | destroy <id> | gc [--delete]]
|
|
39629
40173
|
`);
|
|
39630
40174
|
return 1;
|
|
39631
40175
|
}
|
|
@@ -39653,7 +40197,7 @@ var FLAGS = [
|
|
|
39653
40197
|
{ flag: "--app-url", arg: "<url>", help: "App to open and pair with, path included (default https://corenel.ai; prompd pairs at https://prompd.app/studio)." },
|
|
39654
40198
|
{ flag: "--daemon", help: "Run crew agents on this machine unattended: cron, file-change and run-complete triggers." },
|
|
39655
40199
|
{ flag: "--endpoint", arg: "<url>", help: "Gateway the daemon calls for model runs (default https://api.corenel.ai/api). A bare origin gets /api appended." },
|
|
39656
|
-
{ flag: "--state-dir", arg: "<dir>", help: "Where crew agents, sessions and tokens live (defaults to the Corenel dot-dir)." },
|
|
40200
|
+
{ flag: "--state-dir", arg: "<dir>", help: "Where crew agents, sessions and tokens live (defaults to the Corenel dot-dir). REQUIRED for a second sidecar on this machine: two sharing a state dir share their identity." },
|
|
39657
40201
|
{ flag: "--default-model", arg: "<id>", help: "Model for crew agents that do not name one." },
|
|
39658
40202
|
{ flag: "--name", arg: "<label>", help: "Friendly name advertised to clients (defaults to the hostname)." },
|
|
39659
40203
|
{ flag: "--max-chain-depth", arg: "<n>", help: "Cap how far run-complete triggers may chain before suppressing (default 8)." },
|
|
@@ -39664,6 +40208,7 @@ var FLAGS = [
|
|
|
39664
40208
|
{ flag: "--isolation", arg: "<none|microvm>", help: "How a session confines a run: none (this process) or microvm (a sandbox per session, needs msb). Default none." },
|
|
39665
40209
|
{ flag: "--msb", arg: "<path>", help: "Path to the msb binary, for --isolation microvm." },
|
|
39666
40210
|
{ flag: "--guest-image", arg: "<ref>", help: "Image a session boots (default corenel-guest:dev). Pin by digest for anything snapshotted." },
|
|
40211
|
+
{ flag: "--snapshot-budget", arg: "<size>", help: "Ceiling on TOTAL bytes used by session snapshots (default 32G). A capture that would exceed it is refused, never pruned; size accepts K/M/G/T suffixes." },
|
|
39667
40212
|
{ flag: "--clone-under", arg: "<dir>", help: "Let a session clone a repository into <dir>. Repeatable. Off by default; the clone runs on THIS host with your own git credentials, which never enter the session." },
|
|
39668
40213
|
{ flag: "--keep-transcripts", help: "PERMIT session runs to keep their words, not just metadata. Off by default; each run still has to ask, and none can ask past this." },
|
|
39669
40214
|
{ flag: "--acp", arg: "<target>", help: "Proxy an external ACP agent (claude-code). Needs --root." },
|
|
@@ -39702,6 +40247,11 @@ function helpText() {
|
|
|
39702
40247
|
` ${self} --daemon --default-model gpt-4.1-mini`,
|
|
39703
40248
|
` ${self} --root ~/work --acp claude-code --acp-policy read-only`,
|
|
39704
40249
|
"",
|
|
40250
|
+
" A second sidecar on the same machine needs its own state dir \u2014 without one",
|
|
40251
|
+
" both read the same daemon.id and advertise the same identity, so a paired",
|
|
40252
|
+
" browser sees one sidecar where there are two:",
|
|
40253
|
+
` ${self} --root ~/other --port 4859 --state-dir ~/.corenel-other`,
|
|
40254
|
+
"",
|
|
39705
40255
|
"Reachable both ways: `corenel sidecar \u2026` through the CLI, and",
|
|
39706
40256
|
"`corenel-sidecar \u2026` standalone, for machines that run only this.",
|
|
39707
40257
|
"",
|
|
@@ -39792,6 +40342,7 @@ function parseArgs(argv) {
|
|
|
39792
40342
|
isolation: "none",
|
|
39793
40343
|
msbBin: process.env.MSB_BIN ?? "msb",
|
|
39794
40344
|
guestImage: "corenel-guest:dev",
|
|
40345
|
+
snapshotBudgetBytes: DEFAULT_SNAPSHOT_BUDGET_BYTES,
|
|
39795
40346
|
selfSigned: false,
|
|
39796
40347
|
open: false,
|
|
39797
40348
|
appUrl: "https://corenel.ai",
|
|
@@ -39828,7 +40379,15 @@ function parseArgs(argv) {
|
|
|
39828
40379
|
out.isolation = v2;
|
|
39829
40380
|
} else if (a2 === "--msb") out.msbBin = argv[++i2];
|
|
39830
40381
|
else if (a2 === "--guest-image") out.guestImage = argv[++i2];
|
|
39831
|
-
else if (a2 === "--
|
|
40382
|
+
else if (a2 === "--snapshot-budget") {
|
|
40383
|
+
const v2 = argv[++i2];
|
|
40384
|
+
if (v2 === void 0) throw new Error('--snapshot-budget needs a size, e.g. "32G"');
|
|
40385
|
+
try {
|
|
40386
|
+
out.snapshotBudgetBytes = parseByteSize(v2);
|
|
40387
|
+
} catch (e2) {
|
|
40388
|
+
throw new Error(`--snapshot-budget: ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
40389
|
+
}
|
|
40390
|
+
} else if (a2 === "--allow-lan") out.allowLan = true;
|
|
39832
40391
|
else if (a2 === "--tls-cert") out.tlsCert = resolve7(argv[++i2]);
|
|
39833
40392
|
else if (a2 === "--tls-key") out.tlsKey = resolve7(argv[++i2]);
|
|
39834
40393
|
else if (a2 === "--self-signed") out.selfSigned = true;
|
|
@@ -39892,7 +40451,7 @@ function assertDirectory(path, flag2) {
|
|
|
39892
40451
|
}
|
|
39893
40452
|
|
|
39894
40453
|
// src/roots.ts
|
|
39895
|
-
import { promises as
|
|
40454
|
+
import { promises as fs13 } from "node:fs";
|
|
39896
40455
|
import { resolve as resolve8 } from "node:path";
|
|
39897
40456
|
var DISABLED = "adding working directories is not enabled (no --allow-add-under)";
|
|
39898
40457
|
function createRootsController(opts) {
|
|
@@ -39906,8 +40465,8 @@ function createRootsController(opts) {
|
|
|
39906
40465
|
if (!areas.some((a2) => withinRoot(a2, abs))) {
|
|
39907
40466
|
throw new Error(`path is outside every allowed area: ${abs}`);
|
|
39908
40467
|
}
|
|
39909
|
-
const real = await
|
|
39910
|
-
const realAreas = await Promise.all(areas.map((a2) =>
|
|
40468
|
+
const real = await fs13.realpath(abs);
|
|
40469
|
+
const realAreas = await Promise.all(areas.map((a2) => fs13.realpath(a2)));
|
|
39911
40470
|
if (!realAreas.some((a2) => withinRoot(a2, real))) {
|
|
39912
40471
|
throw new Error(`path escapes the allowed area via symlink: ${abs}`);
|
|
39913
40472
|
}
|
|
@@ -39932,12 +40491,12 @@ function createRootsController(opts) {
|
|
|
39932
40491
|
if (!cloneAreas.some((a2) => withinRoot(a2, abs))) {
|
|
39933
40492
|
throw new Error(`checkout is outside every --clone-under directory: ${abs}`);
|
|
39934
40493
|
}
|
|
39935
|
-
const real = await
|
|
39936
|
-
const realAreas = await Promise.all(cloneAreas.map((a2) =>
|
|
40494
|
+
const real = await fs13.realpath(abs);
|
|
40495
|
+
const realAreas = await Promise.all(cloneAreas.map((a2) => fs13.realpath(a2)));
|
|
39937
40496
|
if (!realAreas.some((a2) => withinRoot(a2, real))) {
|
|
39938
40497
|
throw new Error(`checkout escapes its --clone-under directory via symlink: ${abs}`);
|
|
39939
40498
|
}
|
|
39940
|
-
const st2 = await
|
|
40499
|
+
const st2 = await fs13.stat(abs);
|
|
39941
40500
|
if (!st2.isDirectory()) throw new Error(`not a directory: ${abs}`);
|
|
39942
40501
|
roots.push(abs);
|
|
39943
40502
|
announce();
|
|
@@ -39947,14 +40506,14 @@ function createRootsController(opts) {
|
|
|
39947
40506
|
if (path === void 0) return [...areas];
|
|
39948
40507
|
const abs = resolve8(path);
|
|
39949
40508
|
await assertInArea(abs);
|
|
39950
|
-
const entries = await
|
|
40509
|
+
const entries = await fs13.readdir(abs, { withFileTypes: true });
|
|
39951
40510
|
return entries.filter((e2) => e2.isDirectory()).map((e2) => e2.name);
|
|
39952
40511
|
},
|
|
39953
40512
|
async add(path) {
|
|
39954
40513
|
const abs = resolve8(path);
|
|
39955
40514
|
if (roots.some((r2) => resolve8(r2) === abs)) return;
|
|
39956
40515
|
await assertInArea(abs);
|
|
39957
|
-
const st2 = await
|
|
40516
|
+
const st2 = await fs13.stat(abs);
|
|
39958
40517
|
if (!st2.isDirectory()) throw new Error(`not a directory: ${abs}`);
|
|
39959
40518
|
roots.push(abs);
|
|
39960
40519
|
announce();
|
|
@@ -39970,8 +40529,8 @@ function createRootsController(opts) {
|
|
|
39970
40529
|
|
|
39971
40530
|
// src/session/gitSource.ts
|
|
39972
40531
|
import { spawn as spawn6 } from "node:child_process";
|
|
39973
|
-
import { promises as
|
|
39974
|
-
import { join as
|
|
40532
|
+
import { promises as fs14 } from "node:fs";
|
|
40533
|
+
import { join as join14, resolve as resolve9 } from "node:path";
|
|
39975
40534
|
var DEFAULT_TIMEOUT_MS3 = 10 * 6e4;
|
|
39976
40535
|
var ALLOWED_SCHEMES = ["https://", "ssh://", "git+ssh://"];
|
|
39977
40536
|
var SCP_LIKE = /^[A-Za-z0-9_.-]+@[A-Za-z0-9_.-]+:[A-Za-z0-9_./~-]+$/;
|
|
@@ -40044,9 +40603,9 @@ git timed out after ${opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS3}ms` });
|
|
|
40044
40603
|
settle({ code: -1, stdout, stderr: `${stderr}
|
|
40045
40604
|
${e2 instanceof Error ? e2.message : String(e2)}` });
|
|
40046
40605
|
});
|
|
40047
|
-
child.on("close", (
|
|
40606
|
+
child.on("close", (code2) => {
|
|
40048
40607
|
clearTimeout(timer);
|
|
40049
|
-
settle({ code:
|
|
40608
|
+
settle({ code: code2 ?? -1, stdout, stderr });
|
|
40050
40609
|
});
|
|
40051
40610
|
});
|
|
40052
40611
|
}
|
|
@@ -40063,14 +40622,14 @@ function createGitSource(deps) {
|
|
|
40063
40622
|
if (!areas.some((a2) => withinRoot(a2, abs))) {
|
|
40064
40623
|
throw new Error(`clone destination is outside every allowed area: ${abs}`);
|
|
40065
40624
|
}
|
|
40066
|
-
const realAreas = await Promise.all(areas.map((a2) =>
|
|
40625
|
+
const realAreas = await Promise.all(areas.map((a2) => fs14.realpath(a2)));
|
|
40067
40626
|
if (!realAreas.some((a2) => withinRoot(a2, abs))) {
|
|
40068
40627
|
throw new Error(`clone destination escapes the allowed area via symlink: ${abs}`);
|
|
40069
40628
|
}
|
|
40070
40629
|
}
|
|
40071
40630
|
async function existing(dir, url) {
|
|
40072
40631
|
try {
|
|
40073
|
-
const entries = await
|
|
40632
|
+
const entries = await fs14.readdir(dir);
|
|
40074
40633
|
if (entries.length === 0) return "absent";
|
|
40075
40634
|
} catch {
|
|
40076
40635
|
return "absent";
|
|
@@ -40088,7 +40647,7 @@ function createGitSource(deps) {
|
|
|
40088
40647
|
if (!areas.length) throw new Error(DISABLED2);
|
|
40089
40648
|
assertCloneUrl(spec.url);
|
|
40090
40649
|
if (spec.ref !== void 0) assertRef(spec.ref);
|
|
40091
|
-
const dir =
|
|
40650
|
+
const dir = join14(areas[0], checkoutName(spec.url));
|
|
40092
40651
|
await assertInArea(dir);
|
|
40093
40652
|
const state = await existing(dir, spec.url);
|
|
40094
40653
|
if (state === "occupied") {
|
|
@@ -40245,8 +40804,8 @@ var JsonRpcPeer = class {
|
|
|
40245
40804
|
this.respondError(id, shape.code, shape.message);
|
|
40246
40805
|
}
|
|
40247
40806
|
}
|
|
40248
|
-
respondError(id,
|
|
40249
|
-
this.write({ jsonrpc: "2.0", id, error: { code, message } });
|
|
40807
|
+
respondError(id, code2, message) {
|
|
40808
|
+
this.write({ jsonrpc: "2.0", id, error: { code: code2, message } });
|
|
40250
40809
|
}
|
|
40251
40810
|
write(value) {
|
|
40252
40811
|
if (this.closed) return;
|
|
@@ -40286,6 +40845,10 @@ var JsonRpcPeer = class {
|
|
|
40286
40845
|
|
|
40287
40846
|
// src/acp/client.ts
|
|
40288
40847
|
var ACP_PROTOCOL_VERSION = 1;
|
|
40848
|
+
function acceptsHttpMcp(agentCapabilities) {
|
|
40849
|
+
const mcp = agentCapabilities.mcpCapabilities;
|
|
40850
|
+
return !!(mcp && typeof mcp === "object" && mcp.http === true);
|
|
40851
|
+
}
|
|
40289
40852
|
function rec2(v2) {
|
|
40290
40853
|
return typeof v2 === "object" && v2 !== null && !Array.isArray(v2) ? v2 : {};
|
|
40291
40854
|
}
|
|
@@ -40300,6 +40863,10 @@ var AcpClient = class {
|
|
|
40300
40863
|
currentMode = null;
|
|
40301
40864
|
/** Whether the agent said it can reattach to an existing session. */
|
|
40302
40865
|
canLoad = false;
|
|
40866
|
+
/* Whether the agent accepts HTTP MCP servers. Recorded at initialize, read at
|
|
40867
|
+
* session/new -- the schema defaults it to FALSE, so an agent that says
|
|
40868
|
+
* nothing gets nothing. */
|
|
40869
|
+
httpMcp = false;
|
|
40303
40870
|
disposed = false;
|
|
40304
40871
|
constructor(opts) {
|
|
40305
40872
|
this.opts = opts;
|
|
@@ -40310,8 +40877,8 @@ var AcpClient = class {
|
|
|
40310
40877
|
...opts.log ? { log: opts.log } : {}
|
|
40311
40878
|
});
|
|
40312
40879
|
opts.transport.onData((chunk) => this.peer.push(chunk));
|
|
40313
|
-
opts.transport.onExit((
|
|
40314
|
-
this.peer.close(`agent exited (code ${
|
|
40880
|
+
opts.transport.onExit((code2) => {
|
|
40881
|
+
this.peer.close(`agent exited (code ${code2 ?? "null"})`);
|
|
40315
40882
|
});
|
|
40316
40883
|
}
|
|
40317
40884
|
log(m2) {
|
|
@@ -40355,6 +40922,7 @@ var AcpClient = class {
|
|
|
40355
40922
|
}));
|
|
40356
40923
|
const caps = rec2(raw.agentCapabilities);
|
|
40357
40924
|
this.canLoad = caps.loadSession === true;
|
|
40925
|
+
this.httpMcp = acceptsHttpMcp(caps);
|
|
40358
40926
|
const methods = Array.isArray(raw.authMethods) ? raw.authMethods : [];
|
|
40359
40927
|
const version = typeof raw.protocolVersion === "number" ? raw.protocolVersion : ACP_PROTOCOL_VERSION;
|
|
40360
40928
|
if (version !== ACP_PROTOCOL_VERSION) {
|
|
@@ -40367,9 +40935,16 @@ var AcpClient = class {
|
|
|
40367
40935
|
authMethods: methods
|
|
40368
40936
|
};
|
|
40369
40937
|
}
|
|
40370
|
-
/**
|
|
40371
|
-
|
|
40372
|
-
|
|
40938
|
+
/**
|
|
40939
|
+
* `cwd` MUST be absolute per the ACP spec; the caller passes a confined root.
|
|
40940
|
+
*
|
|
40941
|
+
* `mcpServers` is filtered by what the agent said it can take: this used to
|
|
40942
|
+
* be a hardcoded `[]`, so a user's configured MCP servers never reached the
|
|
40943
|
+
* agent at all and MCP was configurable but inert.
|
|
40944
|
+
*/
|
|
40945
|
+
async newSession(cwd, mcpServers = []) {
|
|
40946
|
+
const servers = this.allowedMcp(mcpServers);
|
|
40947
|
+
const raw = rec2(await this.peer.request("session/new", { cwd, mcpServers: servers }));
|
|
40373
40948
|
const id = str7(raw.sessionId);
|
|
40374
40949
|
if (!id) throw new Error("acp: agent returned no sessionId");
|
|
40375
40950
|
this.sessionId = id;
|
|
@@ -40382,6 +40957,22 @@ var AcpClient = class {
|
|
|
40382
40957
|
this.currentMode = str7(modes.currentModeId) || null;
|
|
40383
40958
|
return id;
|
|
40384
40959
|
}
|
|
40960
|
+
/**
|
|
40961
|
+
* The MCP servers this agent will actually accept.
|
|
40962
|
+
*
|
|
40963
|
+
* Silently empty rather than throwing: an agent without the capability is a
|
|
40964
|
+
* normal thing to be pointed at, and refusing to open a session over it would
|
|
40965
|
+
* turn "no MCP here" into "no agent here". Logged once so the absence is
|
|
40966
|
+
* visible rather than mysterious.
|
|
40967
|
+
*/
|
|
40968
|
+
allowedMcp(servers) {
|
|
40969
|
+
if (!servers.length) return [];
|
|
40970
|
+
if (!this.httpMcp) {
|
|
40971
|
+
this.log(`acp: agent does not accept HTTP MCP servers \u2014 ${servers.length} not passed`);
|
|
40972
|
+
return [];
|
|
40973
|
+
}
|
|
40974
|
+
return servers;
|
|
40975
|
+
}
|
|
40385
40976
|
/** True when `session/load` is worth attempting. */
|
|
40386
40977
|
supportsLoad() {
|
|
40387
40978
|
return this.canLoad;
|
|
@@ -40393,9 +40984,9 @@ var AcpClient = class {
|
|
|
40393
40984
|
* this resolves, which is what lets a caller rebuild a transcript it lost —
|
|
40394
40985
|
* so the update handler must be wired before calling, not after.
|
|
40395
40986
|
*/
|
|
40396
|
-
async loadSession(sessionId, cwd) {
|
|
40987
|
+
async loadSession(sessionId, cwd, mcpServers = []) {
|
|
40397
40988
|
if (!this.canLoad) throw new Error("agent cannot load an existing session");
|
|
40398
|
-
await this.peer.request("session/load", { sessionId, cwd, mcpServers:
|
|
40989
|
+
await this.peer.request("session/load", { sessionId, cwd, mcpServers: this.allowedMcp(mcpServers) });
|
|
40399
40990
|
this.sessionId = sessionId;
|
|
40400
40991
|
}
|
|
40401
40992
|
availableModes() {
|
|
@@ -40490,7 +41081,7 @@ function spawnAcpTransport(target, cwd, log, env) {
|
|
|
40490
41081
|
if (!killed && child.stdin.writable) child.stdin.write(line);
|
|
40491
41082
|
},
|
|
40492
41083
|
onData: (cb) => child.stdout.on("data", (d2) => cb(d2)),
|
|
40493
|
-
onExit: (cb) => child.on("exit", (
|
|
41084
|
+
onExit: (cb) => child.on("exit", (code2) => cb(code2)),
|
|
40494
41085
|
kill: () => {
|
|
40495
41086
|
if (killed) return;
|
|
40496
41087
|
killed = true;
|
|
@@ -40789,7 +41380,7 @@ function makeAcpService(deps) {
|
|
|
40789
41380
|
return {
|
|
40790
41381
|
targets: () => targetIds(),
|
|
40791
41382
|
async start(targetId, onUpdate, opts) {
|
|
40792
|
-
const { root, modeId, resume } = opts ?? {};
|
|
41383
|
+
const { root, modeId, resume, mcpServers } = opts ?? {};
|
|
40793
41384
|
const target = resolveTarget(targetId);
|
|
40794
41385
|
if (!target) {
|
|
40795
41386
|
throw new Error(`unknown ACP target "${targetId}" (known: ${targetIds().join(", ")})`);
|
|
@@ -40823,7 +41414,7 @@ function makeAcpService(deps) {
|
|
|
40823
41414
|
let resumed = false;
|
|
40824
41415
|
if (resume && c2.supportsLoad()) {
|
|
40825
41416
|
try {
|
|
40826
|
-
await c2.loadSession(resume, cwd);
|
|
41417
|
+
await c2.loadSession(resume, cwd, mcpServers ?? []);
|
|
40827
41418
|
sessionId = resume;
|
|
40828
41419
|
resumed = true;
|
|
40829
41420
|
log(`acp: reattached to session ${resume}`);
|
|
@@ -40833,7 +41424,7 @@ function makeAcpService(deps) {
|
|
|
40833
41424
|
} else if (resume) {
|
|
40834
41425
|
log(`acp: ${target.label} cannot reattach to a session \u2014 starting a new one`);
|
|
40835
41426
|
}
|
|
40836
|
-
if (!sessionId) sessionId = await c2.newSession(cwd);
|
|
41427
|
+
if (!sessionId) sessionId = await c2.newSession(cwd, mcpServers ?? []);
|
|
40837
41428
|
if (!resumed && modeId && c2.availableModes().some((m2) => m2.id === modeId)) {
|
|
40838
41429
|
await c2.setMode(modeId);
|
|
40839
41430
|
log(`acp: mode set to ${modeId} (requested by the client)`);
|
|
@@ -41175,7 +41766,7 @@ function awaitCallback(pkce, port, timeoutMs) {
|
|
|
41175
41766
|
return;
|
|
41176
41767
|
}
|
|
41177
41768
|
const err = url.searchParams.get("error");
|
|
41178
|
-
const
|
|
41769
|
+
const code2 = url.searchParams.get("code");
|
|
41179
41770
|
const state = url.searchParams.get("state");
|
|
41180
41771
|
if (!stateMatches(pkce.state, state)) {
|
|
41181
41772
|
res.writeHead(400, { "content-type": "text/html" }).end(PAGE_FAIL);
|
|
@@ -41187,13 +41778,13 @@ function awaitCallback(pkce, port, timeoutMs) {
|
|
|
41187
41778
|
rejectResult(new Error(`authorization failed: ${err}`));
|
|
41188
41779
|
return;
|
|
41189
41780
|
}
|
|
41190
|
-
if (!
|
|
41781
|
+
if (!code2) {
|
|
41191
41782
|
res.writeHead(400, { "content-type": "text/html" }).end(PAGE_FAIL);
|
|
41192
41783
|
rejectResult(new Error("callback carried no authorization code"));
|
|
41193
41784
|
return;
|
|
41194
41785
|
}
|
|
41195
41786
|
res.writeHead(200, { "content-type": "text/html" }).end(PAGE_OK);
|
|
41196
|
-
resolveResult({ code });
|
|
41787
|
+
resolveResult({ code: code2 });
|
|
41197
41788
|
});
|
|
41198
41789
|
const timer = setTimeout(() => {
|
|
41199
41790
|
rejectResult(new Error("timed out waiting for the browser callback"));
|
|
@@ -41233,11 +41824,11 @@ async function login(deps) {
|
|
|
41233
41824
|
log("Opening your browser to sign in...");
|
|
41234
41825
|
log(url);
|
|
41235
41826
|
await deps.openBrowser(url);
|
|
41236
|
-
const { code } = await result;
|
|
41827
|
+
const { code: code2 } = await result;
|
|
41237
41828
|
const tokens = await deps.exchange({
|
|
41238
41829
|
issuer: deps.config.issuer,
|
|
41239
41830
|
clientId: deps.config.clientId,
|
|
41240
|
-
code,
|
|
41831
|
+
code: code2,
|
|
41241
41832
|
verifier: pkce.verifier,
|
|
41242
41833
|
redirectUri
|
|
41243
41834
|
});
|
|
@@ -41373,8 +41964,8 @@ async function main() {
|
|
|
41373
41964
|
if (rawArgv[0] === "login" || rawArgv[0] === "logout" || rawArgv[0] === "auth") {
|
|
41374
41965
|
const sub = rawArgv[0];
|
|
41375
41966
|
const parsed = parseArgs(rawArgv.slice(1));
|
|
41376
|
-
const
|
|
41377
|
-
process.exit(
|
|
41967
|
+
const code2 = sub === "login" ? await runLogin(parsed.stateDir) : sub === "logout" ? await runLogout(parsed.stateDir) : await runAuthStatus(parsed.stateDir);
|
|
41968
|
+
process.exit(code2);
|
|
41378
41969
|
}
|
|
41379
41970
|
if (rawArgv[0] === "acp-agent") {
|
|
41380
41971
|
await runAcpAgent(rawArgv.slice(1));
|
|
@@ -41385,9 +41976,23 @@ async function main() {
|
|
|
41385
41976
|
validateArgs(args);
|
|
41386
41977
|
const rootsController = createRootsController({ roots: args.roots, areas: args.addUnder, cloneAreas: args.cloneUnder });
|
|
41387
41978
|
const token = await loadOrCreateToken();
|
|
41388
|
-
|
|
41979
|
+
try {
|
|
41980
|
+
const note = (line) => {
|
|
41981
|
+
process.stdout.write(` ${line}
|
|
41982
|
+
`);
|
|
41983
|
+
};
|
|
41984
|
+
const migrated = await migrateHomeLayout();
|
|
41985
|
+
if (migrated.moved.length) {
|
|
41986
|
+
note(`layout: moved ${migrated.moved.length} item(s) into the current ~/${stateDirName()} layout`);
|
|
41987
|
+
}
|
|
41988
|
+
for (const left of migrated.skipped) note(`layout: left ${left.path} alone (${left.reason})`);
|
|
41989
|
+
} catch (e2) {
|
|
41990
|
+
process.stdout.write(` layout: migration skipped (${e2 instanceof Error ? e2.message : String(e2)})
|
|
41991
|
+
`);
|
|
41992
|
+
}
|
|
41993
|
+
const sidecarId = await loadOrCreateSidecarId(sidecarSubdir(args.stateDir));
|
|
41389
41994
|
const friendlyName = sidecarName({ name: args.name });
|
|
41390
|
-
const hosts = makeHostsManifest(args.stateDir);
|
|
41995
|
+
const hosts = makeHostsManifest(sidecarSubdir(args.stateDir));
|
|
41391
41996
|
if (args.tlsCert && !args.tlsKey || !args.tlsCert && args.tlsKey) {
|
|
41392
41997
|
throw new Error("TLS needs both --tls-cert and --tls-key");
|
|
41393
41998
|
}
|
|
@@ -41413,7 +42018,9 @@ async function main() {
|
|
|
41413
42018
|
process.stdout.write(` [${(/* @__PURE__ */ new Date()).toTimeString().slice(0, 8)}] ${line}
|
|
41414
42019
|
`);
|
|
41415
42020
|
};
|
|
41416
|
-
await
|
|
42021
|
+
await assertPortFree(probeAddress(args.host), args.port);
|
|
42022
|
+
await assertStateDirFree(sidecarSubdir(args.stateDir));
|
|
42023
|
+
await checkHostsDrift(sidecarSubdir(args.stateDir), hosts, slog);
|
|
41417
42024
|
const hostSync = makeHostSync({ stateDir: args.stateDir, manifest: hosts, log: slog });
|
|
41418
42025
|
const attendance = makeAttendanceRegistry({ log: slog });
|
|
41419
42026
|
const runs = makeRunHub({ log: slog });
|
|
@@ -41425,6 +42032,25 @@ async function main() {
|
|
|
41425
42032
|
roots: args.roots,
|
|
41426
42033
|
allowShell: args.allowShell,
|
|
41427
42034
|
defaultModel: args.defaultModel,
|
|
42035
|
+
/* Which approved root this agent works in.
|
|
42036
|
+
*
|
|
42037
|
+
* MEMBERSHIP, never a prefix test. A prefix check would accept
|
|
42038
|
+
* `<approved>/../elsewhere`, and the operator's `--root` list is the
|
|
42039
|
+
* whole security boundary here: an agent definition can be PUSHED from a
|
|
42040
|
+
* browser, so `settings.workRoot` is a request from a client the daemon
|
|
42041
|
+
* does not trust -- exactly like the clientId that resolveIdentity
|
|
42042
|
+
* refuses to take at face value.
|
|
42043
|
+
*
|
|
42044
|
+
* An unrecognised root falls back to the primary rather than refusing to
|
|
42045
|
+
* run. Removing a --root should not silently convert an agent into a
|
|
42046
|
+
* dead one; it runs where it always did, and the browser is what reports
|
|
42047
|
+
* the mismatch. */
|
|
42048
|
+
workRootFor: (def) => {
|
|
42049
|
+
const want = def.settings.workRoots?.[sidecarId];
|
|
42050
|
+
if (want && args.roots.includes(want)) return want;
|
|
42051
|
+
if (want) slog(`crew: "${def.name}" asks for root "${want}", which is not approved \u2014 using ${args.roots[0]}`);
|
|
42052
|
+
return args.roots[0];
|
|
42053
|
+
},
|
|
41428
42054
|
onScheduleSelf: () => core?.wake(),
|
|
41429
42055
|
// The mirror's cue. Late-bound like `core` below: the runner is built
|
|
41430
42056
|
// before the control plane, and a session cannot be written before both
|
|
@@ -41447,9 +42073,11 @@ async function main() {
|
|
|
41447
42073
|
if (args.daemon && runner) {
|
|
41448
42074
|
const flipped = await runner.reconcilePendingOnBoot();
|
|
41449
42075
|
if (flipped > 0) slog(`daemon: marked ${flipped} stale pending permission record(s) dead (daemon restarted)`);
|
|
42076
|
+
const hostCrewDirs = (await listOwnerRoots(args.stateDir)).filter((r2) => r2.owner !== "").map((r2) => join15(hostsDirFor(args.stateDir, r2.owner), "crew"));
|
|
41450
42077
|
const watchDirs = [
|
|
41451
|
-
|
|
41452
|
-
...args.roots.map((r2) =>
|
|
42078
|
+
join15(args.stateDir, "crew"),
|
|
42079
|
+
...args.roots.map((r2) => join15(r2, stateDirName(), "crew")),
|
|
42080
|
+
...hostCrewDirs
|
|
41453
42081
|
];
|
|
41454
42082
|
core = startDaemonCore({
|
|
41455
42083
|
// Task 12: the stall watchdog scans the SAME runner's in-flight runs
|
|
@@ -41554,7 +42182,6 @@ async function main() {
|
|
|
41554
42182
|
controlPlane = null;
|
|
41555
42183
|
}
|
|
41556
42184
|
}
|
|
41557
|
-
await assertPortFree(probeAddress(args.host), args.port);
|
|
41558
42185
|
const procs = args.allowShell ? createNodeProcs({ roots: args.roots, log: slog }) : void 0;
|
|
41559
42186
|
const sessions = runner ? await wireSessions({
|
|
41560
42187
|
isolation: args.isolation,
|
|
@@ -41562,6 +42189,7 @@ async function main() {
|
|
|
41562
42189
|
runner,
|
|
41563
42190
|
image: args.guestImage,
|
|
41564
42191
|
msbBin: args.msbBin,
|
|
42192
|
+
snapshotBudgetBytes: args.snapshotBudgetBytes,
|
|
41565
42193
|
log: slog,
|
|
41566
42194
|
// Metadata for every session run, filed under the CALLER'S own root. The
|
|
41567
42195
|
// runner service owns owner resolution (a wire-supplied clientId is
|
|
@@ -41637,6 +42265,9 @@ async function main() {
|
|
|
41637
42265
|
},
|
|
41638
42266
|
hostSync
|
|
41639
42267
|
});
|
|
42268
|
+
await writePidFile(sidecarSubdir(args.stateDir), args.port).catch((e2) => {
|
|
42269
|
+
slog(`state dir: could not record this daemon's pid (${e2 instanceof Error ? e2.message : String(e2)})`);
|
|
42270
|
+
});
|
|
41640
42271
|
let tunnelSup = null;
|
|
41641
42272
|
let tunnelWss = null;
|
|
41642
42273
|
if (tunnelProvider) {
|
|
@@ -41698,6 +42329,7 @@ async function main() {
|
|
|
41698
42329
|
openBrowser2(target);
|
|
41699
42330
|
}
|
|
41700
42331
|
const shutdown = () => {
|
|
42332
|
+
void clearPidFile(sidecarSubdir(args.stateDir));
|
|
41701
42333
|
procs?.shutdown();
|
|
41702
42334
|
const alive = sessions?.backend.list() ?? [];
|
|
41703
42335
|
if (alive.length) {
|
|
@@ -41714,8 +42346,8 @@ async function main() {
|
|
|
41714
42346
|
process.on("SIGTERM", shutdown);
|
|
41715
42347
|
}
|
|
41716
42348
|
main().catch((e2) => {
|
|
41717
|
-
const
|
|
41718
|
-
const msg =
|
|
42349
|
+
const code2 = e2?.code;
|
|
42350
|
+
const msg = code2 === "EADDRINUSE" ? `port already in use \u2014 another sidecar may be running. Stop it, or pass --port <n>.` : e2 instanceof Error ? e2.message : String(e2);
|
|
41719
42351
|
process.stderr.write(`corenel-sidecar: ${msg}
|
|
41720
42352
|
`);
|
|
41721
42353
|
process.exit(1);
|