@corenel/sidecar 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +231 -181
- package/dist/cli.js +642 -95
- package/package.json +16 -14
package/dist/cli.js
CHANGED
|
@@ -11908,7 +11908,7 @@ var require_runtime = __commonJS({
|
|
|
11908
11908
|
}
|
|
11909
11909
|
return p2 && p2.lookup(name);
|
|
11910
11910
|
};
|
|
11911
|
-
_proto.resolve = function
|
|
11911
|
+
_proto.resolve = function resolve10(name, forWrite) {
|
|
11912
11912
|
var p2 = forWrite && this.isolateWrites ? void 0 : this.parent;
|
|
11913
11913
|
var val = this.variables[name];
|
|
11914
11914
|
if (val !== void 0) {
|
|
@@ -13638,7 +13638,7 @@ var require_loader = __commonJS({
|
|
|
13638
13638
|
return _EmitterObj.apply(this, arguments) || this;
|
|
13639
13639
|
}
|
|
13640
13640
|
var _proto = Loader2.prototype;
|
|
13641
|
-
_proto.resolve = function
|
|
13641
|
+
_proto.resolve = function resolve10(from, to) {
|
|
13642
13642
|
return path.resolve(path.dirname(from), to);
|
|
13643
13643
|
};
|
|
13644
13644
|
_proto.isRelative = function isRelative(filename) {
|
|
@@ -17098,7 +17098,7 @@ var require_lib3 = __commonJS({
|
|
|
17098
17098
|
let accum = [];
|
|
17099
17099
|
let accumBytes = 0;
|
|
17100
17100
|
let abort = false;
|
|
17101
|
-
return new Body.Promise(function(
|
|
17101
|
+
return new Body.Promise(function(resolve10, reject) {
|
|
17102
17102
|
let resTimeout;
|
|
17103
17103
|
if (_this4.timeout) {
|
|
17104
17104
|
resTimeout = setTimeout(function() {
|
|
@@ -17132,7 +17132,7 @@ var require_lib3 = __commonJS({
|
|
|
17132
17132
|
}
|
|
17133
17133
|
clearTimeout(resTimeout);
|
|
17134
17134
|
try {
|
|
17135
|
-
|
|
17135
|
+
resolve10(Buffer.concat(accum, accumBytes));
|
|
17136
17136
|
} catch (err) {
|
|
17137
17137
|
reject(new FetchError(`Could not create Buffer from response body for ${_this4.url}: ${err.message}`, "system", err));
|
|
17138
17138
|
}
|
|
@@ -17807,7 +17807,7 @@ var require_lib3 = __commonJS({
|
|
|
17807
17807
|
throw new Error("native promise missing, set fetch.Promise to your favorite alternative");
|
|
17808
17808
|
}
|
|
17809
17809
|
Body.Promise = fetch3.Promise;
|
|
17810
|
-
return new fetch3.Promise(function(
|
|
17810
|
+
return new fetch3.Promise(function(resolve10, reject) {
|
|
17811
17811
|
const request = new Request3(url, opts);
|
|
17812
17812
|
const options = getNodeRequestOptions(request);
|
|
17813
17813
|
const send = (options.protocol === "https:" ? https : http).request;
|
|
@@ -17940,7 +17940,7 @@ var require_lib3 = __commonJS({
|
|
|
17940
17940
|
requestOpts.body = void 0;
|
|
17941
17941
|
requestOpts.headers.delete("content-length");
|
|
17942
17942
|
}
|
|
17943
|
-
|
|
17943
|
+
resolve10(fetch3(new Request3(locationURL, requestOpts)));
|
|
17944
17944
|
finalize();
|
|
17945
17945
|
return;
|
|
17946
17946
|
}
|
|
@@ -17961,7 +17961,7 @@ var require_lib3 = __commonJS({
|
|
|
17961
17961
|
const codings = headers.get("Content-Encoding");
|
|
17962
17962
|
if (!request.compress || request.method === "HEAD" || codings === null || res.statusCode === 204 || res.statusCode === 304) {
|
|
17963
17963
|
response = new Response3(body, response_options);
|
|
17964
|
-
|
|
17964
|
+
resolve10(response);
|
|
17965
17965
|
return;
|
|
17966
17966
|
}
|
|
17967
17967
|
const zlibOptions = {
|
|
@@ -17971,7 +17971,7 @@ var require_lib3 = __commonJS({
|
|
|
17971
17971
|
if (codings == "gzip" || codings == "x-gzip") {
|
|
17972
17972
|
body = body.pipe(zlib.createGunzip(zlibOptions));
|
|
17973
17973
|
response = new Response3(body, response_options);
|
|
17974
|
-
|
|
17974
|
+
resolve10(response);
|
|
17975
17975
|
return;
|
|
17976
17976
|
}
|
|
17977
17977
|
if (codings == "deflate" || codings == "x-deflate") {
|
|
@@ -17983,12 +17983,12 @@ var require_lib3 = __commonJS({
|
|
|
17983
17983
|
body = body.pipe(zlib.createInflateRaw());
|
|
17984
17984
|
}
|
|
17985
17985
|
response = new Response3(body, response_options);
|
|
17986
|
-
|
|
17986
|
+
resolve10(response);
|
|
17987
17987
|
});
|
|
17988
17988
|
raw.on("end", function() {
|
|
17989
17989
|
if (!response) {
|
|
17990
17990
|
response = new Response3(body, response_options);
|
|
17991
|
-
|
|
17991
|
+
resolve10(response);
|
|
17992
17992
|
}
|
|
17993
17993
|
});
|
|
17994
17994
|
return;
|
|
@@ -17996,11 +17996,11 @@ var require_lib3 = __commonJS({
|
|
|
17996
17996
|
if (codings == "br" && typeof zlib.createBrotliDecompress === "function") {
|
|
17997
17997
|
body = body.pipe(zlib.createBrotliDecompress());
|
|
17998
17998
|
response = new Response3(body, response_options);
|
|
17999
|
-
|
|
17999
|
+
resolve10(response);
|
|
18000
18000
|
return;
|
|
18001
18001
|
}
|
|
18002
18002
|
response = new Response3(body, response_options);
|
|
18003
|
-
|
|
18003
|
+
resolve10(response);
|
|
18004
18004
|
});
|
|
18005
18005
|
writeToStream(req, request);
|
|
18006
18006
|
});
|
|
@@ -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 });
|
|
@@ -22281,8 +22287,8 @@ var SidecarServer = class {
|
|
|
22281
22287
|
}
|
|
22282
22288
|
hostCall(provider, method, args) {
|
|
22283
22289
|
const id = ++this.hostSeq;
|
|
22284
|
-
return new Promise((
|
|
22285
|
-
this.hostPending.set(id, { resolve:
|
|
22290
|
+
return new Promise((resolve10, reject) => {
|
|
22291
|
+
this.hostPending.set(id, { resolve: resolve10, reject });
|
|
22286
22292
|
this.ch.send({ t: "host-call", id, provider, method, args });
|
|
22287
22293
|
});
|
|
22288
22294
|
}
|
|
@@ -22331,10 +22337,18 @@ var SidecarServer = class {
|
|
|
22331
22337
|
ask = (questions) => this.hostCall("ask", "ask", [questions]).then((v2) => v2, () => null);
|
|
22332
22338
|
};
|
|
22333
22339
|
|
|
22334
|
-
//
|
|
22340
|
+
// ../harness/sidecar/wsChannel.ts
|
|
22335
22341
|
function wsChannel(ws) {
|
|
22336
22342
|
let handler = null;
|
|
22337
22343
|
let closeHandler = null;
|
|
22344
|
+
const pending = [];
|
|
22345
|
+
const flushPending = () => {
|
|
22346
|
+
if (ws.readyState !== ws.OPEN) return;
|
|
22347
|
+
while (pending.length > 0) {
|
|
22348
|
+
ws.send(JSON.stringify(pending.shift()));
|
|
22349
|
+
}
|
|
22350
|
+
};
|
|
22351
|
+
ws.on("open", flushPending);
|
|
22338
22352
|
ws.on("message", (data) => {
|
|
22339
22353
|
if (!handler) return;
|
|
22340
22354
|
try {
|
|
@@ -22348,7 +22362,11 @@ function wsChannel(ws) {
|
|
|
22348
22362
|
});
|
|
22349
22363
|
return {
|
|
22350
22364
|
send(msg) {
|
|
22351
|
-
if (ws.readyState === ws.OPEN)
|
|
22365
|
+
if (ws.readyState === ws.OPEN) {
|
|
22366
|
+
ws.send(JSON.stringify(msg));
|
|
22367
|
+
} else if (ws.readyState === ws.CONNECTING) {
|
|
22368
|
+
pending.push(msg);
|
|
22369
|
+
}
|
|
22352
22370
|
},
|
|
22353
22371
|
onMessage(h2) {
|
|
22354
22372
|
handler = h2;
|
|
@@ -22463,13 +22481,13 @@ function probeAddress(bindHost) {
|
|
|
22463
22481
|
return h2;
|
|
22464
22482
|
}
|
|
22465
22483
|
function portInUse(host, port, timeoutMs = 500) {
|
|
22466
|
-
return new Promise((
|
|
22484
|
+
return new Promise((resolve10) => {
|
|
22467
22485
|
let settled = false;
|
|
22468
22486
|
const done = (busy) => {
|
|
22469
22487
|
if (settled) return;
|
|
22470
22488
|
settled = true;
|
|
22471
22489
|
socket.destroy();
|
|
22472
|
-
|
|
22490
|
+
resolve10(busy);
|
|
22473
22491
|
};
|
|
22474
22492
|
const socket = connect({ host, port });
|
|
22475
22493
|
socket.setTimeout(timeoutMs);
|
|
@@ -24052,7 +24070,34 @@ import { isAbsolute as isAbsolute3, relative as relative3, resolve as resolve3 }
|
|
|
24052
24070
|
// ../tools-node/fileService.ts
|
|
24053
24071
|
import { promises as fs4 } from "node:fs";
|
|
24054
24072
|
import { join as join3, resolve as resolve2, relative as relative2, dirname as dirname2, sep as sep2, basename, isAbsolute as isAbsolute2 } from "node:path";
|
|
24055
|
-
var SKIP = /* @__PURE__ */ new Set([
|
|
24073
|
+
var SKIP = /* @__PURE__ */ new Set([
|
|
24074
|
+
"node_modules",
|
|
24075
|
+
".git",
|
|
24076
|
+
"dist",
|
|
24077
|
+
"build",
|
|
24078
|
+
".next",
|
|
24079
|
+
".cache",
|
|
24080
|
+
".turbo",
|
|
24081
|
+
"dist-corenel",
|
|
24082
|
+
"test-results",
|
|
24083
|
+
"playwright-report"
|
|
24084
|
+
]);
|
|
24085
|
+
var SKIP_PATHS = /* @__PURE__ */ new Set([".claude/skills"]);
|
|
24086
|
+
var COVERAGE_REPORT_MARKERS = /* @__PURE__ */ new Set([
|
|
24087
|
+
"coverage-final.json",
|
|
24088
|
+
"lcov.info",
|
|
24089
|
+
"clover.xml",
|
|
24090
|
+
"coverage-summary.json",
|
|
24091
|
+
"prettify.js"
|
|
24092
|
+
]);
|
|
24093
|
+
async function looksLikeGeneratedCoverage(absDir) {
|
|
24094
|
+
try {
|
|
24095
|
+
const names = await fs4.readdir(absDir);
|
|
24096
|
+
return names.some((n2) => COVERAGE_REPORT_MARKERS.has(n2));
|
|
24097
|
+
} catch {
|
|
24098
|
+
return false;
|
|
24099
|
+
}
|
|
24100
|
+
}
|
|
24056
24101
|
function escapesRoot(rel, platformSep = sep2) {
|
|
24057
24102
|
if (rel === "") return false;
|
|
24058
24103
|
if (rel === ".." || rel.startsWith(".." + platformSep)) return true;
|
|
@@ -24127,6 +24172,8 @@ var NodeFileService = class {
|
|
|
24127
24172
|
if (SKIP.has(e2.name)) continue;
|
|
24128
24173
|
const absChild = join3(absDir, e2.name);
|
|
24129
24174
|
const path = normalizeFilePath(relative2(this.root, absChild));
|
|
24175
|
+
if (e2.isDirectory() && SKIP_PATHS.has(path)) continue;
|
|
24176
|
+
if (e2.isDirectory() && e2.name === "coverage" && await looksLikeGeneratedCoverage(absChild)) continue;
|
|
24130
24177
|
if (e2.isDirectory()) {
|
|
24131
24178
|
out.push({ name: e2.name, path, kind: "directory", children: await walk(absChild) });
|
|
24132
24179
|
} else {
|
|
@@ -24361,8 +24408,9 @@ function inspectArgs(name) {
|
|
|
24361
24408
|
function snapshotListArgs() {
|
|
24362
24409
|
return ["snapshot", "list", "--format", "json"];
|
|
24363
24410
|
}
|
|
24364
|
-
function execArgs(name, command) {
|
|
24365
|
-
|
|
24411
|
+
function execArgs(name, command, env) {
|
|
24412
|
+
const e2 = env ? Object.entries(env).flatMap(([k2, v2]) => ["-e", `${k2}=${v2}`]) : [];
|
|
24413
|
+
return ["exec", name, ...e2, "--", ...command];
|
|
24366
24414
|
}
|
|
24367
24415
|
function wslHostPath(p2) {
|
|
24368
24416
|
const m2 = /^([A-Za-z]):[\\/](.*)$/.exec(p2);
|
|
@@ -24457,6 +24505,34 @@ ${e2 instanceof Error ? e2.message : String(e2)}` });
|
|
|
24457
24505
|
});
|
|
24458
24506
|
}
|
|
24459
24507
|
|
|
24508
|
+
// src/session/snapshotBudget.ts
|
|
24509
|
+
var DEFAULT_SNAPSHOT_BUDGET_BYTES = 32 * 1024 ** 3;
|
|
24510
|
+
function parseByteSize(spelling) {
|
|
24511
|
+
const s2 = spelling.trim();
|
|
24512
|
+
const m2 = /^(\d+(?:\.\d+)?)\s*([KMGT]?)i?B?$/i.exec(s2);
|
|
24513
|
+
if (!m2) throw new Error(`not a size: "${spelling}" (expected e.g. "8G", "512M", or a bare byte count)`);
|
|
24514
|
+
const n2 = Number(m2[1]);
|
|
24515
|
+
const unit = (m2[2] ?? "").toUpperCase();
|
|
24516
|
+
const mult = unit === "K" ? 1024 : unit === "M" ? 1024 ** 2 : unit === "G" ? 1024 ** 3 : unit === "T" ? 1024 ** 4 : 1;
|
|
24517
|
+
return Math.round(n2 * mult);
|
|
24518
|
+
}
|
|
24519
|
+
function formatByteSize(bytes) {
|
|
24520
|
+
const units = [[1024 ** 4, "T"], [1024 ** 3, "G"], [1024 ** 2, "M"], [1024, "K"]];
|
|
24521
|
+
for (const [mult, suffix] of units) {
|
|
24522
|
+
if (bytes >= mult) return `${(bytes / mult).toFixed(1)}${suffix}`;
|
|
24523
|
+
}
|
|
24524
|
+
return `${bytes}B`;
|
|
24525
|
+
}
|
|
24526
|
+
function checkSnapshotBudget(check) {
|
|
24527
|
+
const { usedBytes, estimateBytes, budgetBytes } = check;
|
|
24528
|
+
const projected = usedBytes + estimateBytes;
|
|
24529
|
+
if (projected <= budgetBytes) return { ok: true };
|
|
24530
|
+
return {
|
|
24531
|
+
ok: false,
|
|
24532
|
+
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>.`
|
|
24533
|
+
};
|
|
24534
|
+
}
|
|
24535
|
+
|
|
24460
24536
|
// src/session/microvmBackend.ts
|
|
24461
24537
|
var GUEST_WORKDIR = "/workspace";
|
|
24462
24538
|
var seq2 = 0;
|
|
@@ -24577,6 +24653,14 @@ function makeMicrovmBackend(deps) {
|
|
|
24577
24653
|
}
|
|
24578
24654
|
return r2.stdout;
|
|
24579
24655
|
};
|
|
24656
|
+
const listSnapshotsRaw = async () => {
|
|
24657
|
+
const r2 = await deps.run(snapshotListArgs());
|
|
24658
|
+
if (r2.code !== 0) {
|
|
24659
|
+
const detail = (r2.stderr || r2.stdout).trim().split("\n").slice(-2).join("; ");
|
|
24660
|
+
throw new Error(`list snapshots failed (msb exit ${r2.code}): ${detail || "no output"}`);
|
|
24661
|
+
}
|
|
24662
|
+
return parseSnapshotList(r2.stdout);
|
|
24663
|
+
};
|
|
24580
24664
|
let lastReconcile = 0;
|
|
24581
24665
|
let reconciling = false;
|
|
24582
24666
|
const RECONCILE_MS = 1e4;
|
|
@@ -24730,7 +24814,7 @@ function makeMicrovmBackend(deps) {
|
|
|
24730
24814
|
throw new SessionUnavailable(id, "paused");
|
|
24731
24815
|
}
|
|
24732
24816
|
if (command.length === 0) throw new Error("exec needs a command");
|
|
24733
|
-
const r2 = await deps.run(execArgs(sandboxName(id), command), opts);
|
|
24817
|
+
const r2 = await deps.run(execArgs(sandboxName(id), command, opts?.env), opts);
|
|
24734
24818
|
return { code: r2.code, stdout: r2.stdout, stderr: r2.stderr };
|
|
24735
24819
|
},
|
|
24736
24820
|
/**
|
|
@@ -24904,7 +24988,13 @@ function makeMicrovmBackend(deps) {
|
|
|
24904
24988
|
workdir,
|
|
24905
24989
|
createdAt: Number.isFinite(stamped) && stamped > 0 ? stamped : row.createdAt ?? now2(),
|
|
24906
24990
|
...owner ? { owner } : {},
|
|
24907
|
-
...repoUrl && repoCommit ? { source: { url: repoUrl, commit: repoCommit, ...repoRef ? { ref: repoRef } : {} } } : {}
|
|
24991
|
+
...repoUrl && repoCommit ? { source: { url: repoUrl, commit: repoCommit, ...repoRef ? { ref: repoRef } : {} } } : {},
|
|
24992
|
+
/* THE ONLY PLACE THIS IS EVER SET TRUE. `create()` never touches it,
|
|
24993
|
+
* so a card can tell "this daemon booted it" from "this daemon
|
|
24994
|
+
* found it already running" — procs.ts's own words: a running
|
|
24995
|
+
* process nobody can see is worse than one that died, and adoption
|
|
24996
|
+
* used to be invisible past this log line. */
|
|
24997
|
+
adopted: true
|
|
24908
24998
|
});
|
|
24909
24999
|
adopted.push(id);
|
|
24910
25000
|
}
|
|
@@ -24943,6 +25033,12 @@ function makeMicrovmBackend(deps) {
|
|
|
24943
25033
|
`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`
|
|
24944
25034
|
);
|
|
24945
25035
|
}
|
|
25036
|
+
const existing = await listSnapshotsRaw();
|
|
25037
|
+
const usedBytes = existing.reduce((sum, snap) => sum + (snap.sizeBytes ?? 0), 0);
|
|
25038
|
+
const budgetBytes = deps.snapshotBudgetBytes ?? DEFAULT_SNAPSHOT_BUDGET_BYTES;
|
|
25039
|
+
const estimateBytes = parseByteSize(limits.rootDisk);
|
|
25040
|
+
const budget = checkSnapshotBudget({ usedBytes, estimateBytes, budgetBytes });
|
|
25041
|
+
if (!budget.ok) throw new Error(budget.message);
|
|
24946
25042
|
const snapName = name ?? `${sandboxName(id)}-${now2()}-${++snapSeq}`;
|
|
24947
25043
|
await msb([
|
|
24948
25044
|
"snapshot",
|
|
@@ -24974,12 +25070,19 @@ function makeMicrovmBackend(deps) {
|
|
|
24974
25070
|
* memory would show nothing after a restart while the artifacts sat on
|
|
24975
25071
|
* disk. */
|
|
24976
25072
|
async snapshots() {
|
|
24977
|
-
|
|
24978
|
-
|
|
24979
|
-
|
|
24980
|
-
|
|
24981
|
-
|
|
24982
|
-
|
|
25073
|
+
return listSnapshotsRaw();
|
|
25074
|
+
},
|
|
25075
|
+
/** Current usage against the refuse-at-budget ceiling, BEFORE a capture
|
|
25076
|
+
* ever hits it — a budget you only learn about at the moment it stops you
|
|
25077
|
+
* is a bad budget. Same arithmetic `commit()` checks against, so a caller
|
|
25078
|
+
* watching this can see a refusal coming. */
|
|
25079
|
+
async snapshotUsage() {
|
|
25080
|
+
const existing = await listSnapshotsRaw();
|
|
25081
|
+
return {
|
|
25082
|
+
usedBytes: existing.reduce((sum, snap) => sum + (snap.sizeBytes ?? 0), 0),
|
|
25083
|
+
budgetBytes: deps.snapshotBudgetBytes ?? DEFAULT_SNAPSHOT_BUDGET_BYTES,
|
|
25084
|
+
count: existing.length
|
|
25085
|
+
};
|
|
24983
25086
|
}
|
|
24984
25087
|
};
|
|
24985
25088
|
}
|
|
@@ -25090,7 +25193,10 @@ function toInfo(h2, backend) {
|
|
|
25090
25193
|
// Read from the HANDLE, so it survives every later list() — and, on the
|
|
25091
25194
|
// microVM backend, a daemon restart, since the sandbox carries it in its
|
|
25092
25195
|
// own labels.
|
|
25093
|
-
...h2.source ? { source: h2.source } : {}
|
|
25196
|
+
...h2.source ? { source: h2.source } : {},
|
|
25197
|
+
// Same reasoning as `source`: a client cannot derive "this daemon found
|
|
25198
|
+
// it running rather than booted it" from anything else it is given.
|
|
25199
|
+
...h2.adopted ? { adopted: true } : {}
|
|
25094
25200
|
};
|
|
25095
25201
|
}
|
|
25096
25202
|
function makeSessionsPort(deps) {
|
|
@@ -25166,6 +25272,9 @@ function makeSessionsPort(deps) {
|
|
|
25166
25272
|
...s2.sizeBytes !== void 0 ? { sizeBytes: s2.sizeBytes } : {}
|
|
25167
25273
|
}))
|
|
25168
25274
|
} : {},
|
|
25275
|
+
...backend.snapshotUsage ? {
|
|
25276
|
+
snapshotUsage: () => backend.snapshotUsage()
|
|
25277
|
+
} : {},
|
|
25169
25278
|
...backend.metrics ? {
|
|
25170
25279
|
watchMetrics: (id, onSample) => {
|
|
25171
25280
|
const st2 = backend.metrics(id, onSample);
|
|
@@ -27289,10 +27398,10 @@ ${content}` : parentData.content;
|
|
|
27289
27398
|
});
|
|
27290
27399
|
this.registerFiltersOnEnv(env);
|
|
27291
27400
|
const renderTimeout = 5e3;
|
|
27292
|
-
const renderPromise = new Promise((
|
|
27401
|
+
const renderPromise = new Promise((resolve10, reject) => {
|
|
27293
27402
|
try {
|
|
27294
27403
|
const result = env.renderString(content, context.parameters);
|
|
27295
|
-
|
|
27404
|
+
resolve10(result);
|
|
27296
27405
|
} catch (error) {
|
|
27297
27406
|
reject(error);
|
|
27298
27407
|
}
|
|
@@ -28011,6 +28120,7 @@ async function wireSessions(opts) {
|
|
|
28011
28120
|
image: opts.image,
|
|
28012
28121
|
// Safe to apply everywhere: it only rewrites drive-letter paths.
|
|
28013
28122
|
hostPath: wslHostPath,
|
|
28123
|
+
...opts.snapshotBudgetBytes !== void 0 ? { snapshotBudgetBytes: opts.snapshotBudgetBytes } : {},
|
|
28014
28124
|
...opts.log ? { log: opts.log } : {}
|
|
28015
28125
|
});
|
|
28016
28126
|
const manager = new SessionManager({
|
|
@@ -28890,24 +29000,75 @@ function ensureWritable(path) {
|
|
|
28890
29000
|
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.`);
|
|
28891
29001
|
}
|
|
28892
29002
|
}
|
|
28893
|
-
function
|
|
28894
|
-
|
|
28895
|
-
|
|
28896
|
-
|
|
29003
|
+
function describe(n2) {
|
|
29004
|
+
return `${n2.kind === "directory" ? "[dir] " : ""}${n2.path}${n2.kind === "file" && n2.size != null ? ` (${n2.size}b)` : ""}`;
|
|
29005
|
+
}
|
|
29006
|
+
function bfsNodes(roots, maxDepth = Infinity) {
|
|
29007
|
+
const out = [];
|
|
29008
|
+
let level = roots;
|
|
29009
|
+
let depth = 1;
|
|
29010
|
+
while (level.length) {
|
|
29011
|
+
const next = [];
|
|
29012
|
+
for (const n2 of level) {
|
|
29013
|
+
out.push(n2);
|
|
29014
|
+
if (n2.kind === "directory" && n2.children?.length && depth < maxDepth) next.push(...n2.children);
|
|
29015
|
+
}
|
|
29016
|
+
level = next;
|
|
29017
|
+
depth++;
|
|
29018
|
+
}
|
|
29019
|
+
return out;
|
|
29020
|
+
}
|
|
29021
|
+
var LIST_FILES_MAX_ENTRIES = 300;
|
|
29022
|
+
function selectByBreadthFirstCap(roots, maxDepth) {
|
|
29023
|
+
const bfs = bfsNodes(roots, maxDepth);
|
|
29024
|
+
return { keep: new Set(bfs.slice(0, LIST_FILES_MAX_ENTRIES)), total: bfs.length };
|
|
29025
|
+
}
|
|
29026
|
+
function describeInTreeOrder(roots, keep, maxDepth = Infinity, depth = 1, out = []) {
|
|
29027
|
+
for (const n2 of roots) {
|
|
29028
|
+
if (!keep.has(n2)) continue;
|
|
29029
|
+
out.push(describe(n2));
|
|
29030
|
+
if (n2.kind === "directory" && n2.children?.length && depth < maxDepth) {
|
|
29031
|
+
describeInTreeOrder(n2.children, keep, maxDepth, depth + 1, out);
|
|
29032
|
+
}
|
|
28897
29033
|
}
|
|
28898
29034
|
return out;
|
|
28899
29035
|
}
|
|
29036
|
+
function truncationMarker(total, maxDepth) {
|
|
29037
|
+
const omitted = total - LIST_FILES_MAX_ENTRIES;
|
|
29038
|
+
const omittedEntries = `${omitted} more entr${omitted === 1 ? "y" : "ies"} omitted`;
|
|
29039
|
+
if (maxDepth != null && Number.isFinite(maxDepth)) {
|
|
29040
|
+
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.`;
|
|
29041
|
+
}
|
|
29042
|
+
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.`;
|
|
29043
|
+
}
|
|
29044
|
+
function normalizeDepth(args) {
|
|
29045
|
+
const v2 = args["depth"];
|
|
29046
|
+
if (v2 == null) return void 0;
|
|
29047
|
+
const n2 = typeof v2 === "number" ? v2 : typeof v2 === "string" ? Number(v2) : NaN;
|
|
29048
|
+
if (!Number.isFinite(n2)) return void 0;
|
|
29049
|
+
const floored = Math.floor(n2);
|
|
29050
|
+
return floored < 1 ? 1 : floored;
|
|
29051
|
+
}
|
|
28900
29052
|
var listFilesTool = {
|
|
28901
29053
|
name: "list_files",
|
|
28902
|
-
description: "List files and directories in the workspace (the open folder or the virtual workspace).
|
|
29054
|
+
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.",
|
|
28903
29055
|
parameters: {
|
|
28904
29056
|
type: "object",
|
|
28905
|
-
properties: {
|
|
29057
|
+
properties: {
|
|
29058
|
+
dir: { type: "string", description: "Subdirectory to list (optional; omit for the root)." },
|
|
29059
|
+
depth: {
|
|
29060
|
+
type: "number",
|
|
29061
|
+
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."
|
|
29062
|
+
}
|
|
29063
|
+
}
|
|
28906
29064
|
},
|
|
28907
29065
|
async run(args, ctx) {
|
|
28908
29066
|
const files = requireFiles(ctx);
|
|
28909
29067
|
const nodes = await files.list(str(args, "dir") || void 0);
|
|
28910
|
-
const
|
|
29068
|
+
const maxDepth = normalizeDepth(args);
|
|
29069
|
+
const { keep, total } = selectByBreadthFirstCap(nodes, maxDepth);
|
|
29070
|
+
const lines = describeInTreeOrder(nodes, keep, maxDepth);
|
|
29071
|
+
if (total > LIST_FILES_MAX_ENTRIES) lines.push(truncationMarker(total, maxDepth));
|
|
28911
29072
|
return lines.length ? lines.join("\n") : "(empty)";
|
|
28912
29073
|
}
|
|
28913
29074
|
};
|
|
@@ -29328,7 +29489,7 @@ ${err}`;
|
|
|
29328
29489
|
}
|
|
29329
29490
|
const outcome = ctx.signal.aborted ? null : await Promise.race([
|
|
29330
29491
|
ctx.reviewEdit(ctx.callId),
|
|
29331
|
-
new Promise((
|
|
29492
|
+
new Promise((resolve10) => ctx.signal.addEventListener("abort", () => resolve10(null), { once: true }))
|
|
29332
29493
|
]);
|
|
29333
29494
|
if (!outcome) return "The run ended before the user reviewed this proposal. Do NOT assume the edit was applied.";
|
|
29334
29495
|
if (outcome.decision === "applied") {
|
|
@@ -29646,7 +29807,7 @@ function allTools() {
|
|
|
29646
29807
|
return [...defaultTools, ...extensionTools()];
|
|
29647
29808
|
}
|
|
29648
29809
|
|
|
29649
|
-
// ../
|
|
29810
|
+
// ../harness/tools/callAgent.ts
|
|
29650
29811
|
function str5(v2) {
|
|
29651
29812
|
return typeof v2 === "string" ? v2.trim() : "";
|
|
29652
29813
|
}
|
|
@@ -31455,8 +31616,8 @@ function _addRequestID(value, response) {
|
|
|
31455
31616
|
}
|
|
31456
31617
|
var APIPromise = class _APIPromise extends Promise {
|
|
31457
31618
|
constructor(responsePromise, parseResponse2 = defaultParseResponse) {
|
|
31458
|
-
super((
|
|
31459
|
-
|
|
31619
|
+
super((resolve10) => {
|
|
31620
|
+
resolve10(null);
|
|
31460
31621
|
});
|
|
31461
31622
|
this.responsePromise = responsePromise;
|
|
31462
31623
|
this.parseResponse = parseResponse2;
|
|
@@ -32031,7 +32192,7 @@ var startsWithSchemeRegexp = /^[a-z][a-z0-9+.-]*:/i;
|
|
|
32031
32192
|
var isAbsoluteURL = (url) => {
|
|
32032
32193
|
return startsWithSchemeRegexp.test(url);
|
|
32033
32194
|
};
|
|
32034
|
-
var sleep = (ms) => new Promise((
|
|
32195
|
+
var sleep = (ms) => new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
32035
32196
|
var validatePositiveInteger = (name, n2) => {
|
|
32036
32197
|
if (typeof n2 !== "number" || !Number.isInteger(n2)) {
|
|
32037
32198
|
throw new OpenAIError(`${name} must be an integer`);
|
|
@@ -32464,12 +32625,12 @@ var EventStream = class {
|
|
|
32464
32625
|
_EventStream_errored.set(this, false);
|
|
32465
32626
|
_EventStream_aborted.set(this, false);
|
|
32466
32627
|
_EventStream_catchingPromiseCreated.set(this, false);
|
|
32467
|
-
__classPrivateFieldSet7(this, _EventStream_connectedPromise, new Promise((
|
|
32468
|
-
__classPrivateFieldSet7(this, _EventStream_resolveConnectedPromise,
|
|
32628
|
+
__classPrivateFieldSet7(this, _EventStream_connectedPromise, new Promise((resolve10, reject) => {
|
|
32629
|
+
__classPrivateFieldSet7(this, _EventStream_resolveConnectedPromise, resolve10, "f");
|
|
32469
32630
|
__classPrivateFieldSet7(this, _EventStream_rejectConnectedPromise, reject, "f");
|
|
32470
32631
|
}), "f");
|
|
32471
|
-
__classPrivateFieldSet7(this, _EventStream_endPromise, new Promise((
|
|
32472
|
-
__classPrivateFieldSet7(this, _EventStream_resolveEndPromise,
|
|
32632
|
+
__classPrivateFieldSet7(this, _EventStream_endPromise, new Promise((resolve10, reject) => {
|
|
32633
|
+
__classPrivateFieldSet7(this, _EventStream_resolveEndPromise, resolve10, "f");
|
|
32473
32634
|
__classPrivateFieldSet7(this, _EventStream_rejectEndPromise, reject, "f");
|
|
32474
32635
|
}), "f");
|
|
32475
32636
|
__classPrivateFieldGet8(this, _EventStream_connectedPromise, "f").catch(() => {
|
|
@@ -32553,11 +32714,11 @@ var EventStream = class {
|
|
|
32553
32714
|
* const message = await stream.emitted('message') // rejects if the stream errors
|
|
32554
32715
|
*/
|
|
32555
32716
|
emitted(event) {
|
|
32556
|
-
return new Promise((
|
|
32717
|
+
return new Promise((resolve10, reject) => {
|
|
32557
32718
|
__classPrivateFieldSet7(this, _EventStream_catchingPromiseCreated, true, "f");
|
|
32558
32719
|
if (event !== "error")
|
|
32559
32720
|
this.once("error", reject);
|
|
32560
|
-
this.once(event,
|
|
32721
|
+
this.once(event, resolve10);
|
|
32561
32722
|
});
|
|
32562
32723
|
}
|
|
32563
32724
|
async done() {
|
|
@@ -32710,7 +32871,7 @@ var AssistantStream = class _AssistantStream extends EventStream {
|
|
|
32710
32871
|
if (done) {
|
|
32711
32872
|
return { value: void 0, done: true };
|
|
32712
32873
|
}
|
|
32713
|
-
return new Promise((
|
|
32874
|
+
return new Promise((resolve10, reject) => readQueue.push({ resolve: resolve10, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
|
|
32714
32875
|
}
|
|
32715
32876
|
const chunk = pushQueue.shift();
|
|
32716
32877
|
return { value: chunk, done: false };
|
|
@@ -34328,7 +34489,7 @@ var ChatCompletionStream = class _ChatCompletionStream extends AbstractChatCompl
|
|
|
34328
34489
|
if (done) {
|
|
34329
34490
|
return { value: void 0, done: true };
|
|
34330
34491
|
}
|
|
34331
|
-
return new Promise((
|
|
34492
|
+
return new Promise((resolve10, reject) => readQueue.push({ resolve: resolve10, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
|
|
34332
34493
|
}
|
|
34333
34494
|
const chunk = pushQueue.shift();
|
|
34334
34495
|
return { value: chunk, done: false };
|
|
@@ -35977,7 +36138,7 @@ var ResponseStream = class _ResponseStream extends EventStream {
|
|
|
35977
36138
|
if (done) {
|
|
35978
36139
|
return { value: void 0, done: true };
|
|
35979
36140
|
}
|
|
35980
|
-
return new Promise((
|
|
36141
|
+
return new Promise((resolve10, reject) => readQueue.push({ resolve: resolve10, reject })).then((event2) => event2 ? { value: event2, done: false } : { value: void 0, done: true });
|
|
35981
36142
|
}
|
|
35982
36143
|
const event = pushQueue.shift();
|
|
35983
36144
|
return { value: event, done: false };
|
|
@@ -36782,14 +36943,14 @@ function classifyStreamError(message, status) {
|
|
|
36782
36943
|
}
|
|
36783
36944
|
function abortableSleep(ms, signal) {
|
|
36784
36945
|
if (signal?.aborted) return Promise.resolve(false);
|
|
36785
|
-
return new Promise((
|
|
36946
|
+
return new Promise((resolve10) => {
|
|
36786
36947
|
const onAbort = () => {
|
|
36787
36948
|
clearTimeout(t2);
|
|
36788
|
-
|
|
36949
|
+
resolve10(false);
|
|
36789
36950
|
};
|
|
36790
36951
|
const t2 = setTimeout(() => {
|
|
36791
36952
|
signal?.removeEventListener("abort", onAbort);
|
|
36792
|
-
|
|
36953
|
+
resolve10(true);
|
|
36793
36954
|
}, ms);
|
|
36794
36955
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
36795
36956
|
});
|
|
@@ -36874,6 +37035,7 @@ var BUILTIN_NAMESPACES = {
|
|
|
36874
37035
|
ask_user: "agent",
|
|
36875
37036
|
spawn_agent: "agent",
|
|
36876
37037
|
spawn_agents: "agent",
|
|
37038
|
+
call_agent: "agent",
|
|
36877
37039
|
todo_write: "agent",
|
|
36878
37040
|
// Named here as a backstop; the sidecar's own decls also carry `namespace`,
|
|
36879
37041
|
// and an explicit field always wins (resolveNamespace). Both say the same
|
|
@@ -36892,7 +37054,17 @@ var BUILTIN_NAMESPACES = {
|
|
|
36892
37054
|
view_strategy: "agent",
|
|
36893
37055
|
run_strategy: "agent",
|
|
36894
37056
|
list_strategies: "agent",
|
|
36895
|
-
list_saved_strategies: "agent"
|
|
37057
|
+
list_saved_strategies: "agent",
|
|
37058
|
+
// Long-lived process control (proc_start/proc_stop/proc_list, see tools/procs.ts).
|
|
37059
|
+
// `shell` is its own subject, deliberately distinct from the one-shot exec's
|
|
37060
|
+
// `sidecar:shell_exec`: a process that stays running is a different risk from
|
|
37061
|
+
// one that returns, and one address lets an operator ban (or allow) both
|
|
37062
|
+
// start and stop together. proc_list is a read - it namespaces as `sidecar`
|
|
37063
|
+
// (like the other machine-state reads) so it is NOT swept up by a `shell:*`
|
|
37064
|
+
// deny; listing what is running carries none of spawn/kill's risk.
|
|
37065
|
+
proc_start: "shell",
|
|
37066
|
+
proc_stop: "shell",
|
|
37067
|
+
proc_list: "sidecar"
|
|
36896
37068
|
};
|
|
36897
37069
|
function resolveNamespace(tool) {
|
|
36898
37070
|
if (tool.namespace) return tool.namespace;
|
|
@@ -36920,6 +37092,7 @@ function toolAddress(tool) {
|
|
|
36920
37092
|
}
|
|
36921
37093
|
}
|
|
36922
37094
|
}
|
|
37095
|
+
if (ns === "shell" && (leaf === "proc_start" || leaf === "proc_stop")) leaf = "spawn";
|
|
36923
37096
|
return `${ns}:${leaf}`;
|
|
36924
37097
|
}
|
|
36925
37098
|
|
|
@@ -36931,8 +37104,12 @@ function effectiveThreshold(mode, fill = 0) {
|
|
|
36931
37104
|
const factor = fill < 0.5 ? 1 : fill < 0.7 ? 0.7 : fill < 0.85 ? 0.5 : 0.35;
|
|
36932
37105
|
return Math.max(FLOOR[mode], Math.round(BASE[mode] * factor));
|
|
36933
37106
|
}
|
|
37107
|
+
var OFFLOAD_MARKER_PREFIX = "[offloaded tool result \xB7 ";
|
|
37108
|
+
function isOffloadPointer(content) {
|
|
37109
|
+
return content.startsWith(OFFLOAD_MARKER_PREFIX);
|
|
37110
|
+
}
|
|
36934
37111
|
function formatOffloadRef(ref) {
|
|
36935
|
-
const head =
|
|
37112
|
+
const head = `${OFFLOAD_MARKER_PREFIX}${ref.toolName} \xB7 ${ref.bytes.toLocaleString()} bytes \xB7 recall the full content with recall_result({ id: "${ref.id}" })]`;
|
|
36936
37113
|
return ref.preview ? `${head}
|
|
36937
37114
|
--- preview ---
|
|
36938
37115
|
${ref.preview}${ref.bytes > ref.preview.length ? "\n\u2026(truncated)" : ""}` : head;
|
|
@@ -36955,7 +37132,9 @@ async function runAgent(args) {
|
|
|
36955
37132
|
const permission = args.permission;
|
|
36956
37133
|
const compression = args.compression;
|
|
36957
37134
|
const ctxWindow = compression?.contextWindow;
|
|
37135
|
+
const compressionStrategy = compression?.strategy ?? "eager";
|
|
36958
37136
|
let fill = 0;
|
|
37137
|
+
const deferredOffloads = [];
|
|
36959
37138
|
const maxTurns = policy.maxTurns ?? args.maxTurns ?? 8;
|
|
36960
37139
|
const signal = args.signal ?? new AbortController().signal;
|
|
36961
37140
|
const emit = (e2) => args.onEvent?.(e2);
|
|
@@ -36972,6 +37151,9 @@ async function runAgent(args) {
|
|
|
36972
37151
|
function: { name: t2.name, description: t2.description, parameters: t2.parameters }
|
|
36973
37152
|
}));
|
|
36974
37153
|
const toolByName = new Map(tools.map((t2) => [t2.name, t2]));
|
|
37154
|
+
if (compressionStrategy === "deferred" && compression && compression.mode !== "off" && toolCtx.recall) {
|
|
37155
|
+
await compactIncomingOffloads(messages, toolByName, compression, toolCtx.recall, emit);
|
|
37156
|
+
}
|
|
36975
37157
|
const usage = { promptTokens: 0, completionTokens: 0 };
|
|
36976
37158
|
let text = "";
|
|
36977
37159
|
let stopReason = "stop";
|
|
@@ -37045,6 +37227,13 @@ async function runAgent(args) {
|
|
|
37045
37227
|
stopReason = "error";
|
|
37046
37228
|
break;
|
|
37047
37229
|
}
|
|
37230
|
+
if (compressionStrategy === "deferred" && deferredOffloads.length) {
|
|
37231
|
+
for (const o2 of deferredOffloads) {
|
|
37232
|
+
const orig = messages[o2.index];
|
|
37233
|
+
if (orig.role === "tool") messages[o2.index] = { ...orig, content: formatOffloadRef(o2.ref) };
|
|
37234
|
+
}
|
|
37235
|
+
deferredOffloads.length = 0;
|
|
37236
|
+
}
|
|
37048
37237
|
usage.promptTokens += streamed.usage.promptTokens;
|
|
37049
37238
|
usage.completionTokens += streamed.usage.completionTokens;
|
|
37050
37239
|
if (ctxWindow && ctxWindow > 0 && streamed.usage.promptTokens) {
|
|
@@ -37120,14 +37309,21 @@ async function runAgent(args) {
|
|
|
37120
37309
|
const decision = await permission.check({ name: tool.name, address, mutates: tool.mutates, permission: tool.permission }, parsed);
|
|
37121
37310
|
emit({ type: "permission", tool: tool.name, decision });
|
|
37122
37311
|
if (decision === "deny") {
|
|
37123
|
-
|
|
37312
|
+
const denialMsg = args.describePermissionDenial?.(tool.name) ?? "Permission denied by the user \u2014 the action was not performed.";
|
|
37313
|
+
messages.push({ role: "tool", tool_call_id: call.id, content: denialMsg });
|
|
37124
37314
|
emit({ type: "tool-result", id: call.id, name: tool.name, result: "permission denied", isError: true });
|
|
37125
37315
|
continue;
|
|
37126
37316
|
}
|
|
37127
37317
|
}
|
|
37128
37318
|
const startedAt = Date.now();
|
|
37319
|
+
let callSettled = false;
|
|
37320
|
+
const stream = (text2) => {
|
|
37321
|
+
if (callSettled) return;
|
|
37322
|
+
emit({ type: "tool-delta", id: call.id, name: call.name, delta: text2 });
|
|
37323
|
+
};
|
|
37129
37324
|
try {
|
|
37130
|
-
const result = await tool.run(parsed, { ...toolCtx, callId: call.id });
|
|
37325
|
+
const result = await tool.run(parsed, { ...toolCtx, callId: call.id, stream });
|
|
37326
|
+
callSettled = true;
|
|
37131
37327
|
const durationMs = Date.now() - startedAt;
|
|
37132
37328
|
gstate.toolCalls++;
|
|
37133
37329
|
gstate.calledTools.add(tool.name);
|
|
@@ -37141,8 +37337,12 @@ async function runAgent(args) {
|
|
|
37141
37337
|
if (result.length > effectiveThreshold(compression.mode, fill)) {
|
|
37142
37338
|
try {
|
|
37143
37339
|
const ref = await toolCtx.recall.record(tool.name, result);
|
|
37144
|
-
out = formatOffloadRef(ref);
|
|
37145
37340
|
emit({ type: "offload", id: ref.id, name: tool.name, bytes: ref.bytes });
|
|
37341
|
+
if (compressionStrategy === "deferred") {
|
|
37342
|
+
deferredOffloads.push({ index: messages.length, ref });
|
|
37343
|
+
} else {
|
|
37344
|
+
out = formatOffloadRef(ref);
|
|
37345
|
+
}
|
|
37146
37346
|
} catch {
|
|
37147
37347
|
out = result;
|
|
37148
37348
|
}
|
|
@@ -37151,6 +37351,7 @@ async function runAgent(args) {
|
|
|
37151
37351
|
messages.push({ role: "tool", tool_call_id: call.id, content: out });
|
|
37152
37352
|
emit({ type: "tool-result", id: call.id, name: call.name, result: out, durationMs });
|
|
37153
37353
|
} catch (e2) {
|
|
37354
|
+
callSettled = true;
|
|
37154
37355
|
const errMsg = String(e2?.message || e2);
|
|
37155
37356
|
const kind2 = classifyToolError(errMsg);
|
|
37156
37357
|
const attempt = toolFails.fail(tool.name, kind2);
|
|
@@ -37174,6 +37375,30 @@ async function runAgent(args) {
|
|
|
37174
37375
|
emit({ type: "done", reason: stopReason });
|
|
37175
37376
|
return { messages, text, usage, stopReason };
|
|
37176
37377
|
}
|
|
37378
|
+
async function compactIncomingOffloads(messages, toolByName, compression, recall, emit) {
|
|
37379
|
+
const nameForCallId = /* @__PURE__ */ new Map();
|
|
37380
|
+
for (const m2 of messages) {
|
|
37381
|
+
if (m2.role === "assistant" && m2.tool_calls) {
|
|
37382
|
+
for (const c2 of m2.tool_calls) nameForCallId.set(c2.id, c2.function.name);
|
|
37383
|
+
}
|
|
37384
|
+
}
|
|
37385
|
+
for (let i2 = 0; i2 < messages.length; i2++) {
|
|
37386
|
+
const m2 = messages[i2];
|
|
37387
|
+
if (m2.role !== "tool" || typeof m2.content !== "string") continue;
|
|
37388
|
+
if (isOffloadPointer(m2.content)) continue;
|
|
37389
|
+
const toolName = nameForCallId.get(m2.tool_call_id);
|
|
37390
|
+
if (!toolName) continue;
|
|
37391
|
+
const tool = toolByName.get(toolName);
|
|
37392
|
+
if (!tool || tool.noOffload) continue;
|
|
37393
|
+
if (m2.content.length <= effectiveThreshold(compression.mode, 0)) continue;
|
|
37394
|
+
try {
|
|
37395
|
+
const ref = await recall.record(toolName, m2.content);
|
|
37396
|
+
messages[i2] = { ...m2, content: formatOffloadRef(ref) };
|
|
37397
|
+
emit({ type: "offload", id: ref.id, name: toolName, bytes: ref.bytes });
|
|
37398
|
+
} catch {
|
|
37399
|
+
}
|
|
37400
|
+
}
|
|
37401
|
+
}
|
|
37177
37402
|
function reconcileDanglingToolCalls(messages) {
|
|
37178
37403
|
let idx = -1;
|
|
37179
37404
|
for (let i2 = messages.length - 1; i2 >= 0; i2--) {
|
|
@@ -37734,12 +37959,12 @@ function makeScheduleSelfTool(opts) {
|
|
|
37734
37959
|
var PARK_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
37735
37960
|
var PARK_POLL_MS = 250;
|
|
37736
37961
|
function defaultSleep(ms, signal) {
|
|
37737
|
-
return new Promise((
|
|
37962
|
+
return new Promise((resolve10) => {
|
|
37738
37963
|
let timer;
|
|
37739
37964
|
const onAbort = () => {
|
|
37740
37965
|
if (timer !== void 0) clearTimeout(timer);
|
|
37741
37966
|
signal.removeEventListener("abort", onAbort);
|
|
37742
|
-
|
|
37967
|
+
resolve10();
|
|
37743
37968
|
};
|
|
37744
37969
|
if (signal.aborted) {
|
|
37745
37970
|
onAbort();
|
|
@@ -37747,7 +37972,7 @@ function defaultSleep(ms, signal) {
|
|
|
37747
37972
|
}
|
|
37748
37973
|
timer = setTimeout(() => {
|
|
37749
37974
|
signal.removeEventListener("abort", onAbort);
|
|
37750
|
-
|
|
37975
|
+
resolve10();
|
|
37751
37976
|
}, ms);
|
|
37752
37977
|
signal.addEventListener("abort", onAbort);
|
|
37753
37978
|
});
|
|
@@ -38492,12 +38717,12 @@ function makeDispatcher(opts) {
|
|
|
38492
38717
|
// src/runner/clock.ts
|
|
38493
38718
|
var MAX_SLEEP_MS = 15 * 60 * 1e3;
|
|
38494
38719
|
function defaultSleep2(ms, signal) {
|
|
38495
|
-
return new Promise((
|
|
38720
|
+
return new Promise((resolve10) => {
|
|
38496
38721
|
let timer;
|
|
38497
38722
|
const onAbort = () => {
|
|
38498
38723
|
if (timer !== void 0) clearTimeout(timer);
|
|
38499
38724
|
signal.removeEventListener("abort", onAbort);
|
|
38500
|
-
|
|
38725
|
+
resolve10();
|
|
38501
38726
|
};
|
|
38502
38727
|
if (signal.aborted) {
|
|
38503
38728
|
onAbort();
|
|
@@ -38505,7 +38730,7 @@ function defaultSleep2(ms, signal) {
|
|
|
38505
38730
|
}
|
|
38506
38731
|
timer = setTimeout(() => {
|
|
38507
38732
|
signal.removeEventListener("abort", onAbort);
|
|
38508
|
-
|
|
38733
|
+
resolve10();
|
|
38509
38734
|
}, ms);
|
|
38510
38735
|
signal.addEventListener("abort", onAbort);
|
|
38511
38736
|
});
|
|
@@ -38869,7 +39094,7 @@ function makeAttendanceRegistry(opts) {
|
|
|
38869
39094
|
if (clients.size === 0) {
|
|
38870
39095
|
return Promise.resolve(null);
|
|
38871
39096
|
}
|
|
38872
|
-
return new Promise((
|
|
39097
|
+
return new Promise((resolve10) => {
|
|
38873
39098
|
let cleanupAbort;
|
|
38874
39099
|
const settleNull = () => {
|
|
38875
39100
|
const entry = outstanding.get(ask.id);
|
|
@@ -38881,7 +39106,7 @@ function makeAttendanceRegistry(opts) {
|
|
|
38881
39106
|
ask,
|
|
38882
39107
|
resolve: (decision) => {
|
|
38883
39108
|
cleanupAbort?.();
|
|
38884
|
-
|
|
39109
|
+
resolve10(decision);
|
|
38885
39110
|
}
|
|
38886
39111
|
});
|
|
38887
39112
|
for (const c2 of clients) {
|
|
@@ -39375,6 +39600,131 @@ async function runHostsCli(argv) {
|
|
|
39375
39600
|
}
|
|
39376
39601
|
|
|
39377
39602
|
// src/sessionsCli.ts
|
|
39603
|
+
import { resolve as resolve6 } from "node:path";
|
|
39604
|
+
|
|
39605
|
+
// ../harness/sessions/fileRecallStore.ts
|
|
39606
|
+
var UNATTACHED_SESSION = "_unattached";
|
|
39607
|
+
|
|
39608
|
+
// ../harness/sessions/blobGc.ts
|
|
39609
|
+
var DEFAULT_UNATTACHED_GRACE_MS = 30 * 60 * 1e3;
|
|
39610
|
+
var MARKER_ID_RE = /recall_result\(\{\s*id:\s*\\?"([^"\\]+)\\?"\s*\}\)/g;
|
|
39611
|
+
async function globalReferencedIds(files, root, sessionDirs) {
|
|
39612
|
+
const ids = /* @__PURE__ */ new Set();
|
|
39613
|
+
for (const dir of sessionDirs) {
|
|
39614
|
+
if (!dir.hasSessionLog) continue;
|
|
39615
|
+
const text = await files.read(`${root}/${dir.id}/SESSION.jsonl`).catch(() => null);
|
|
39616
|
+
if (text == null) continue;
|
|
39617
|
+
for (const m2 of text.matchAll(MARKER_ID_RE)) {
|
|
39618
|
+
const id = m2[1];
|
|
39619
|
+
if (id) ids.add(id);
|
|
39620
|
+
}
|
|
39621
|
+
}
|
|
39622
|
+
return ids;
|
|
39623
|
+
}
|
|
39624
|
+
async function listSessionDirs(files, root) {
|
|
39625
|
+
const nodes = await files.list(root).catch(() => []);
|
|
39626
|
+
const flat = flattenFiles(nodes).filter((f2) => f2.path.startsWith(`${root}/`));
|
|
39627
|
+
const dirs = /* @__PURE__ */ new Map();
|
|
39628
|
+
for (const f2 of flat) {
|
|
39629
|
+
const rest = f2.path.slice(root.length + 1);
|
|
39630
|
+
const slash = rest.indexOf("/");
|
|
39631
|
+
if (slash < 0) continue;
|
|
39632
|
+
const id = rest.slice(0, slash);
|
|
39633
|
+
const isSessionLog = rest === `${id}/SESSION.jsonl`;
|
|
39634
|
+
if (!dirs.has(id)) dirs.set(id, false);
|
|
39635
|
+
if (isSessionLog) dirs.set(id, true);
|
|
39636
|
+
}
|
|
39637
|
+
return [...dirs.entries()].map(([id, hasSessionLog]) => ({ id, hasSessionLog }));
|
|
39638
|
+
}
|
|
39639
|
+
async function listBlobs(files, root, sessionId) {
|
|
39640
|
+
const dir = `${root}/${sessionId}/blobs`;
|
|
39641
|
+
const nodes = await files.list(dir).catch(() => []);
|
|
39642
|
+
const blobFiles = flattenFiles(nodes).filter((f2) => f2.path.startsWith(`${dir}/`) && f2.path.endsWith(".json"));
|
|
39643
|
+
const out = [];
|
|
39644
|
+
for (const f2 of blobFiles) {
|
|
39645
|
+
const text = await files.read(f2.path).catch(() => null);
|
|
39646
|
+
if (text == null) continue;
|
|
39647
|
+
try {
|
|
39648
|
+
const record = JSON.parse(text);
|
|
39649
|
+
if (record && typeof record.id === "string") out.push({ path: f2.path, record });
|
|
39650
|
+
} catch {
|
|
39651
|
+
}
|
|
39652
|
+
}
|
|
39653
|
+
return out;
|
|
39654
|
+
}
|
|
39655
|
+
async function planBlobGc(files, opts = {}) {
|
|
39656
|
+
const root = opts.root ?? sessionsRoot();
|
|
39657
|
+
const now2 = (opts.now ?? Date.now)();
|
|
39658
|
+
const graceMs = opts.graceMs ?? DEFAULT_UNATTACHED_GRACE_MS;
|
|
39659
|
+
const sessionDirs = await listSessionDirs(files, root);
|
|
39660
|
+
const referenced = await globalReferencedIds(files, root, sessionDirs);
|
|
39661
|
+
const reapable = [];
|
|
39662
|
+
const liveUnreferenced = [];
|
|
39663
|
+
for (const dir of sessionDirs) {
|
|
39664
|
+
const blobs = await listBlobs(files, root, dir.id);
|
|
39665
|
+
for (const { path, record } of blobs) {
|
|
39666
|
+
const isReferenced = referenced.has(record.id);
|
|
39667
|
+
const base = {
|
|
39668
|
+
sessionId: dir.id,
|
|
39669
|
+
id: record.id,
|
|
39670
|
+
path,
|
|
39671
|
+
bytes: record.bytes,
|
|
39672
|
+
toolName: record.toolName,
|
|
39673
|
+
createdAt: record.createdAt
|
|
39674
|
+
};
|
|
39675
|
+
if (isReferenced) continue;
|
|
39676
|
+
if (dir.id === UNATTACHED_SESSION) {
|
|
39677
|
+
const age = now2 - record.createdAt;
|
|
39678
|
+
if (age < graceMs) continue;
|
|
39679
|
+
reapable.push({
|
|
39680
|
+
...base,
|
|
39681
|
+
disposition: "reap",
|
|
39682
|
+
reason: `unattached, unreferenced, and ${Math.round(age / 6e4)}m old (grace is ${Math.round(graceMs / 6e4)}m)`
|
|
39683
|
+
});
|
|
39684
|
+
continue;
|
|
39685
|
+
}
|
|
39686
|
+
if (!dir.hasSessionLog) {
|
|
39687
|
+
reapable.push({ ...base, disposition: "reap", reason: "owning session directory has no SESSION.jsonl" });
|
|
39688
|
+
continue;
|
|
39689
|
+
}
|
|
39690
|
+
liveUnreferenced.push({ ...base, disposition: "report-only", reason: "unreferenced, but the owning session is live \u2014 not deleted by this tool" });
|
|
39691
|
+
}
|
|
39692
|
+
}
|
|
39693
|
+
return {
|
|
39694
|
+
reapable,
|
|
39695
|
+
liveUnreferenced,
|
|
39696
|
+
reapableBytes: reapable.reduce((sum, e2) => sum + e2.bytes, 0),
|
|
39697
|
+
liveUnreferencedBytes: liveUnreferenced.reduce((sum, e2) => sum + e2.bytes, 0)
|
|
39698
|
+
};
|
|
39699
|
+
}
|
|
39700
|
+
async function runBlobGc(files, opts = {}) {
|
|
39701
|
+
const plan = await planBlobGc(files, opts);
|
|
39702
|
+
if (!opts.delete) return { plan, removed: [] };
|
|
39703
|
+
const removed = [];
|
|
39704
|
+
for (const entry of plan.reapable) {
|
|
39705
|
+
await files.remove(entry.path).catch(() => void 0);
|
|
39706
|
+
removed.push(entry);
|
|
39707
|
+
}
|
|
39708
|
+
return { plan, removed };
|
|
39709
|
+
}
|
|
39710
|
+
|
|
39711
|
+
// src/sessionsCli.ts
|
|
39712
|
+
async function sandboxLifecycle(run, verb, name) {
|
|
39713
|
+
const call = async (args) => {
|
|
39714
|
+
const r2 = await run(args).catch((e2) => ({ code: 1, stdout: "", stderr: String(e2) }));
|
|
39715
|
+
return { ok: r2.code === 0, detail: (r2.stderr || r2.stdout).trim().split("\n").slice(-2).join("; ") };
|
|
39716
|
+
};
|
|
39717
|
+
if (verb === "pause") return call(["stop", name]);
|
|
39718
|
+
if (verb === "resume") return call(["start", name]);
|
|
39719
|
+
await call(["stop", name]);
|
|
39720
|
+
return call(["remove", name]);
|
|
39721
|
+
}
|
|
39722
|
+
function humanTime(v2) {
|
|
39723
|
+
if (!v2) return "";
|
|
39724
|
+
const n2 = Number(v2);
|
|
39725
|
+
if (!Number.isFinite(n2) || n2 <= 0) return v2;
|
|
39726
|
+
return new Date(n2).toISOString().replace("T", " ").replace(/\.\d+Z$/, "Z");
|
|
39727
|
+
}
|
|
39378
39728
|
async function listSessions(run) {
|
|
39379
39729
|
const listed = await run(listArgs()).catch(() => null);
|
|
39380
39730
|
if (!listed || listed.code !== 0) return [];
|
|
@@ -39382,7 +39732,7 @@ async function listSessions(run) {
|
|
|
39382
39732
|
for (const box of parseSandboxList(listed.stdout)) {
|
|
39383
39733
|
const inspected = await run(inspectArgs(box.name)).catch(() => null);
|
|
39384
39734
|
const labels = inspected && inspected.code === 0 ? parseSandboxLabels(inspected.stdout) : {};
|
|
39385
|
-
const id = labels[LABEL_SESSION];
|
|
39735
|
+
const id = labels[LABEL_SESSION] ?? idFromSandboxName(box.name);
|
|
39386
39736
|
if (!id) continue;
|
|
39387
39737
|
const url = labels[LABEL_REPO_URL];
|
|
39388
39738
|
const ref = labels[LABEL_REPO_REF];
|
|
@@ -39392,7 +39742,9 @@ async function listSessions(run) {
|
|
|
39392
39742
|
status: box.status || "unknown",
|
|
39393
39743
|
workdir: labels[LABEL_WORKDIR] ?? "",
|
|
39394
39744
|
owner: labels[LABEL_OWNER] ?? "",
|
|
39395
|
-
|
|
39745
|
+
// The label stores epoch millis; a bare 1787430794235 in a terminal is a
|
|
39746
|
+
// number nobody can read as a time.
|
|
39747
|
+
created: humanTime(labels[LABEL_CREATED]) || (box.createdAt ? new Date(box.createdAt).toISOString() : ""),
|
|
39396
39748
|
...url ? { repo: ref ? `${url}#${ref}` : url } : {}
|
|
39397
39749
|
});
|
|
39398
39750
|
}
|
|
@@ -39428,11 +39780,53 @@ function formatSession(r2) {
|
|
|
39428
39780
|
...r2.repo ? [`repo ${r2.repo}`] : []
|
|
39429
39781
|
].join("\n");
|
|
39430
39782
|
}
|
|
39783
|
+
function formatBlobGcReport(result, deleted) {
|
|
39784
|
+
const line = (e2) => ` ${e2.path} ${formatByteSize(e2.bytes)} (${e2.reason})`;
|
|
39785
|
+
const lines = [];
|
|
39786
|
+
const { plan } = result;
|
|
39787
|
+
if (!plan.reapable.length && !plan.liveUnreferenced.length) {
|
|
39788
|
+
lines.push("no unattached blobs found.");
|
|
39789
|
+
return lines.join("\n");
|
|
39790
|
+
}
|
|
39791
|
+
if (plan.reapable.length) {
|
|
39792
|
+
lines.push(deleted ? `removed ${result.removed.length} blob(s), ${formatByteSize(plan.reapableBytes)} freed:` : `would remove ${plan.reapable.length} blob(s), ${formatByteSize(plan.reapableBytes)} total:`);
|
|
39793
|
+
lines.push(...plan.reapable.map(line));
|
|
39794
|
+
} else {
|
|
39795
|
+
lines.push("nothing is reap-eligible.");
|
|
39796
|
+
}
|
|
39797
|
+
if (plan.liveUnreferenced.length) {
|
|
39798
|
+
lines.push("");
|
|
39799
|
+
lines.push(`${plan.liveUnreferenced.length} unreferenced blob(s) in live sessions, ${formatByteSize(plan.liveUnreferencedBytes)} total (not removed by this tool):`);
|
|
39800
|
+
lines.push(...plan.liveUnreferenced.map(line));
|
|
39801
|
+
}
|
|
39802
|
+
if (!deleted && plan.reapable.length) {
|
|
39803
|
+
lines.push("");
|
|
39804
|
+
lines.push("pass --delete to remove the first list. Nothing above was touched.");
|
|
39805
|
+
}
|
|
39806
|
+
return lines.join("\n");
|
|
39807
|
+
}
|
|
39808
|
+
var DEFAULT_GUEST_IMAGE = "corenel-guest:dev";
|
|
39809
|
+
function backendFor(deps, roots, run) {
|
|
39810
|
+
return makeMicrovmBackend({
|
|
39811
|
+
roots: () => roots,
|
|
39812
|
+
run,
|
|
39813
|
+
image: deps.image ?? DEFAULT_GUEST_IMAGE,
|
|
39814
|
+
// On Windows msb lives inside WSL, where C:\git\proj means nothing. The
|
|
39815
|
+
// daemon maps host paths the same way.
|
|
39816
|
+
...deps.wsl ? { hostPath: wslHostPath } : {}
|
|
39817
|
+
});
|
|
39818
|
+
}
|
|
39819
|
+
var VALUE_FLAGS = /* @__PURE__ */ new Set(["--root", "--msb", "--image"]);
|
|
39820
|
+
function rootFrom(argv, cwd) {
|
|
39821
|
+
const at2 = argv.indexOf("--root");
|
|
39822
|
+
return resolve6(at2 >= 0 && argv[at2 + 1] ? argv[at2 + 1] : cwd);
|
|
39823
|
+
}
|
|
39431
39824
|
async function runSessionsCli(argv, deps) {
|
|
39432
39825
|
const out = deps.out ?? ((s2) => process.stdout.write(s2));
|
|
39433
39826
|
const err = deps.err ?? ((s2) => process.stderr.write(s2));
|
|
39434
39827
|
const run = deps.run ?? makeMsbRunner(deps.msbBin, deps.wsl ? wslMsb(deps.msbBin) : void 0);
|
|
39435
|
-
const
|
|
39828
|
+
const verbs = argv.filter((a2, i2) => !a2.startsWith("--") && !(i2 > 0 && VALUE_FLAGS.has(argv[i2 - 1])));
|
|
39829
|
+
const [verb, ...rest] = verbs;
|
|
39436
39830
|
if (verb === void 0 || verb === "list") {
|
|
39437
39831
|
if (rest.length) {
|
|
39438
39832
|
err(`corenel sessions list: unexpected argument "${rest[0]}"
|
|
@@ -39459,15 +39853,158 @@ async function runSessionsCli(argv, deps) {
|
|
|
39459
39853
|
`);
|
|
39460
39854
|
return 0;
|
|
39461
39855
|
}
|
|
39856
|
+
if (verb === "create") {
|
|
39857
|
+
const root = rootFrom(argv, process.cwd());
|
|
39858
|
+
const backend = deps.backend ?? backendFor(deps, [root], run);
|
|
39859
|
+
if (backend.adopt) await backend.adopt().catch(() => void 0);
|
|
39860
|
+
try {
|
|
39861
|
+
const handle = await backend.create({ workdir: root, owner: process.env.USERNAME || process.env.USER || "cli" });
|
|
39862
|
+
out(`${handle.id}
|
|
39863
|
+
`);
|
|
39864
|
+
err(`created ${handle.id} on ${handle.workdir}
|
|
39865
|
+
`);
|
|
39866
|
+
return 0;
|
|
39867
|
+
} catch (e2) {
|
|
39868
|
+
err(`corenel sessions create: ${e2 instanceof Error ? e2.message : String(e2)}
|
|
39869
|
+
`);
|
|
39870
|
+
return 1;
|
|
39871
|
+
}
|
|
39872
|
+
}
|
|
39873
|
+
if (verb === "run") {
|
|
39874
|
+
const id = rest[0];
|
|
39875
|
+
const prompt = rest.slice(1).filter((a2) => !a2.startsWith("--")).join(" ").trim();
|
|
39876
|
+
if (!id) {
|
|
39877
|
+
err("corenel sessions run: missing <id>\n");
|
|
39878
|
+
return 1;
|
|
39879
|
+
}
|
|
39880
|
+
if (!prompt) {
|
|
39881
|
+
err('corenel sessions run: missing prompt\n usage: corenel sessions run <id> "<prompt>"\n');
|
|
39882
|
+
return 1;
|
|
39883
|
+
}
|
|
39884
|
+
const found = (await listSessions(run)).find((r2) => r2.id === id || r2.sandbox === id);
|
|
39885
|
+
if (!found) {
|
|
39886
|
+
err(`corenel sessions: no session "${id}" on this machine
|
|
39887
|
+
`);
|
|
39888
|
+
return 1;
|
|
39889
|
+
}
|
|
39890
|
+
const backend = deps.backend ?? backendFor(deps, [found.workdir], run);
|
|
39891
|
+
if (!backend.exec) {
|
|
39892
|
+
err("corenel sessions run: this backend cannot execute inside a session\n");
|
|
39893
|
+
return 1;
|
|
39894
|
+
}
|
|
39895
|
+
if (backend.adopt) await backend.adopt().catch(() => void 0);
|
|
39896
|
+
try {
|
|
39897
|
+
const token = await readAuthToken() ?? process.env.CORENEL_TOKEN ?? null;
|
|
39898
|
+
if (!token) {
|
|
39899
|
+
err("corenel sessions run: not signed in on this host.\n Run `corenel login` first \u2014 the session uses the host's credential.\n");
|
|
39900
|
+
return 1;
|
|
39901
|
+
}
|
|
39902
|
+
const r2 = await backend.exec(found.id, ["corenel", "run", prompt], { env: { CORENEL_TOKEN: token } });
|
|
39903
|
+
if (r2.stdout) out(r2.stdout.endsWith("\n") ? r2.stdout : `${r2.stdout}
|
|
39904
|
+
`);
|
|
39905
|
+
if (r2.stderr.trim()) err(r2.stderr.endsWith("\n") ? r2.stderr : `${r2.stderr}
|
|
39906
|
+
`);
|
|
39907
|
+
return r2.code ?? 1;
|
|
39908
|
+
} catch (e2) {
|
|
39909
|
+
err(`corenel sessions run: ${e2 instanceof Error ? e2.message : String(e2)}
|
|
39910
|
+
`);
|
|
39911
|
+
return 1;
|
|
39912
|
+
}
|
|
39913
|
+
}
|
|
39914
|
+
if (verb === "pause" || verb === "resume" || verb === "destroy") {
|
|
39915
|
+
const id = rest[0];
|
|
39916
|
+
if (!id) {
|
|
39917
|
+
err(`corenel sessions ${verb}: missing <id>
|
|
39918
|
+
`);
|
|
39919
|
+
return 1;
|
|
39920
|
+
}
|
|
39921
|
+
const found = (await listSessions(run)).find((r2) => r2.id === id || r2.sandbox === id);
|
|
39922
|
+
if (!found) {
|
|
39923
|
+
err(`corenel sessions: no session "${id}" on this machine
|
|
39924
|
+
`);
|
|
39925
|
+
return 1;
|
|
39926
|
+
}
|
|
39927
|
+
const backend = deps.backend ?? backendFor(deps, [found.workdir], run);
|
|
39928
|
+
if (backend.adopt) await backend.adopt().catch(() => void 0);
|
|
39929
|
+
const known = !!backend.get?.(found.id);
|
|
39930
|
+
if (!known) {
|
|
39931
|
+
const r2 = await sandboxLifecycle(run, verb, found.sandbox);
|
|
39932
|
+
if (!r2.ok) {
|
|
39933
|
+
err(`corenel sessions ${verb}: ${r2.detail || "msb refused"}
|
|
39934
|
+
`);
|
|
39935
|
+
return 1;
|
|
39936
|
+
}
|
|
39937
|
+
if (verb === "destroy") {
|
|
39938
|
+
const still = (await listSessions(run)).find((x2) => x2.id === found.id);
|
|
39939
|
+
if (still) {
|
|
39940
|
+
err(`corenel sessions destroy: ${found.id} is still present (status ${still.status})
|
|
39941
|
+
`);
|
|
39942
|
+
return 1;
|
|
39943
|
+
}
|
|
39944
|
+
}
|
|
39945
|
+
out(`${verb === "destroy" ? "destroyed" : verb === "pause" ? "paused" : "resumed"} ${found.id}
|
|
39946
|
+
`);
|
|
39947
|
+
return 0;
|
|
39948
|
+
}
|
|
39949
|
+
try {
|
|
39950
|
+
if (verb === "pause") {
|
|
39951
|
+
const r2 = await backend.pause(found.id);
|
|
39952
|
+
if (r2 && !r2.drained) {
|
|
39953
|
+
err(`paused ${found.id}, but ${r2.remaining.length} call(s) were STILL RUNNING when the drain gave up
|
|
39954
|
+
`);
|
|
39955
|
+
return 1;
|
|
39956
|
+
}
|
|
39957
|
+
out(`paused ${found.id}
|
|
39958
|
+
`);
|
|
39959
|
+
return 0;
|
|
39960
|
+
}
|
|
39961
|
+
if (verb === "resume") {
|
|
39962
|
+
await backend.resume(found.id);
|
|
39963
|
+
out(`resumed ${found.id}
|
|
39964
|
+
`);
|
|
39965
|
+
return 0;
|
|
39966
|
+
}
|
|
39967
|
+
await backend.destroy(found.id);
|
|
39968
|
+
const still = (await listSessions(run)).find((r2) => r2.id === found.id);
|
|
39969
|
+
if (still) {
|
|
39970
|
+
err(`corenel sessions destroy: ${found.id} is still present (status ${still.status})
|
|
39971
|
+
`);
|
|
39972
|
+
return 1;
|
|
39973
|
+
}
|
|
39974
|
+
out(`destroyed ${found.id}
|
|
39975
|
+
`);
|
|
39976
|
+
return 0;
|
|
39977
|
+
} catch (e2) {
|
|
39978
|
+
err(`corenel sessions ${verb}: ${e2 instanceof Error ? e2.message : String(e2)}
|
|
39979
|
+
`);
|
|
39980
|
+
return 1;
|
|
39981
|
+
}
|
|
39982
|
+
}
|
|
39983
|
+
if (verb === "gc") {
|
|
39984
|
+
const root = rootFrom(argv, process.cwd());
|
|
39985
|
+
const doDelete = argv.includes("--delete");
|
|
39986
|
+
try {
|
|
39987
|
+
const files = new NodeFileService(root);
|
|
39988
|
+
const result = await runBlobGc(files, { delete: doDelete });
|
|
39989
|
+
out(`${formatBlobGcReport(result, doDelete)}
|
|
39990
|
+
`);
|
|
39991
|
+
return 0;
|
|
39992
|
+
} catch (e2) {
|
|
39993
|
+
err(`corenel sessions gc: ${e2 instanceof Error ? e2.message : String(e2)}
|
|
39994
|
+
`);
|
|
39995
|
+
return 1;
|
|
39996
|
+
}
|
|
39997
|
+
}
|
|
39462
39998
|
err(`corenel sessions: unknown subcommand "${verb}"
|
|
39463
|
-
usage: corenel sessions [list | show <id>]
|
|
39999
|
+
usage: corenel sessions [list | show <id> | create [--root <dir>] | run <id> "<prompt>"
|
|
40000
|
+
| pause <id> | resume <id> | destroy <id> | gc [--delete]]
|
|
39464
40001
|
`);
|
|
39465
40002
|
return 1;
|
|
39466
40003
|
}
|
|
39467
40004
|
|
|
39468
40005
|
// src/args.ts
|
|
39469
40006
|
import { statSync as statSync2 } from "node:fs";
|
|
39470
|
-
import { resolve as
|
|
40007
|
+
import { resolve as resolve7 } from "node:path";
|
|
39471
40008
|
|
|
39472
40009
|
// src/flags.ts
|
|
39473
40010
|
var FLAGS = [
|
|
@@ -39499,6 +40036,7 @@ var FLAGS = [
|
|
|
39499
40036
|
{ 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." },
|
|
39500
40037
|
{ flag: "--msb", arg: "<path>", help: "Path to the msb binary, for --isolation microvm." },
|
|
39501
40038
|
{ flag: "--guest-image", arg: "<ref>", help: "Image a session boots (default corenel-guest:dev). Pin by digest for anything snapshotted." },
|
|
40039
|
+
{ 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." },
|
|
39502
40040
|
{ 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." },
|
|
39503
40041
|
{ 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." },
|
|
39504
40042
|
{ flag: "--acp", arg: "<target>", help: "Proxy an external ACP agent (claude-code). Needs --root." },
|
|
@@ -39627,6 +40165,7 @@ function parseArgs(argv) {
|
|
|
39627
40165
|
isolation: "none",
|
|
39628
40166
|
msbBin: process.env.MSB_BIN ?? "msb",
|
|
39629
40167
|
guestImage: "corenel-guest:dev",
|
|
40168
|
+
snapshotBudgetBytes: DEFAULT_SNAPSHOT_BUDGET_BYTES,
|
|
39630
40169
|
selfSigned: false,
|
|
39631
40170
|
open: false,
|
|
39632
40171
|
appUrl: "https://corenel.ai",
|
|
@@ -39651,9 +40190,9 @@ function parseArgs(argv) {
|
|
|
39651
40190
|
else if (a2 === "--host") {
|
|
39652
40191
|
out.host = argv[++i2];
|
|
39653
40192
|
hostSet = true;
|
|
39654
|
-
} else if (a2 === "--root") out.roots.push(
|
|
39655
|
-
else if (a2 === "--allow-add-under") out.addUnder.push(
|
|
39656
|
-
else if (a2 === "--clone-under") out.cloneUnder.push(
|
|
40193
|
+
} else if (a2 === "--root") out.roots.push(resolve7(argv[++i2]));
|
|
40194
|
+
else if (a2 === "--allow-add-under") out.addUnder.push(resolve7(argv[++i2]));
|
|
40195
|
+
else if (a2 === "--clone-under") out.cloneUnder.push(resolve7(argv[++i2]));
|
|
39657
40196
|
else if (a2 === "--allow-origin") out.allowOrigins.push(argv[++i2]);
|
|
39658
40197
|
else if (a2 === "--keep-transcripts") out.keepTranscripts = true;
|
|
39659
40198
|
else if (a2 === "--allow-shell") out.allowShell = true;
|
|
@@ -39663,9 +40202,17 @@ function parseArgs(argv) {
|
|
|
39663
40202
|
out.isolation = v2;
|
|
39664
40203
|
} else if (a2 === "--msb") out.msbBin = argv[++i2];
|
|
39665
40204
|
else if (a2 === "--guest-image") out.guestImage = argv[++i2];
|
|
39666
|
-
else if (a2 === "--
|
|
39667
|
-
|
|
39668
|
-
|
|
40205
|
+
else if (a2 === "--snapshot-budget") {
|
|
40206
|
+
const v2 = argv[++i2];
|
|
40207
|
+
if (v2 === void 0) throw new Error('--snapshot-budget needs a size, e.g. "32G"');
|
|
40208
|
+
try {
|
|
40209
|
+
out.snapshotBudgetBytes = parseByteSize(v2);
|
|
40210
|
+
} catch (e2) {
|
|
40211
|
+
throw new Error(`--snapshot-budget: ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
40212
|
+
}
|
|
40213
|
+
} else if (a2 === "--allow-lan") out.allowLan = true;
|
|
40214
|
+
else if (a2 === "--tls-cert") out.tlsCert = resolve7(argv[++i2]);
|
|
40215
|
+
else if (a2 === "--tls-key") out.tlsKey = resolve7(argv[++i2]);
|
|
39669
40216
|
else if (a2 === "--self-signed") out.selfSigned = true;
|
|
39670
40217
|
else if (a2 === "--tunnel") out.tunnel = argv[++i2];
|
|
39671
40218
|
else if (a2 === "--tunnel-name") out.tunnelName = argv[++i2];
|
|
@@ -39680,7 +40227,7 @@ function parseArgs(argv) {
|
|
|
39680
40227
|
else if (a2 === "--acp-auth") out.acpAuth = argv[++i2];
|
|
39681
40228
|
else if (a2 === "--agent") out.acpAgentName = argv[++i2];
|
|
39682
40229
|
else if (a2 === "--max-chain-depth") out.maxChainDepth = Number(argv[++i2]);
|
|
39683
|
-
else if (a2 === "--state-dir") out.stateDir =
|
|
40230
|
+
else if (a2 === "--state-dir") out.stateDir = resolve7(argv[++i2]);
|
|
39684
40231
|
else if (a2 === "--name") out.name = argv[++i2];
|
|
39685
40232
|
else if (a2 === "--sidecar" || a2 === "start" || a2 === "--") {
|
|
39686
40233
|
} else throw new Error(`unknown argument: ${a2}
|
|
@@ -39728,12 +40275,12 @@ function assertDirectory(path, flag2) {
|
|
|
39728
40275
|
|
|
39729
40276
|
// src/roots.ts
|
|
39730
40277
|
import { promises as fs11 } from "node:fs";
|
|
39731
|
-
import { resolve as
|
|
40278
|
+
import { resolve as resolve8 } from "node:path";
|
|
39732
40279
|
var DISABLED = "adding working directories is not enabled (no --allow-add-under)";
|
|
39733
40280
|
function createRootsController(opts) {
|
|
39734
40281
|
const roots = opts.roots;
|
|
39735
|
-
const areas = [...new Set(opts.areas.map((a2) =>
|
|
39736
|
-
const cloneAreas = [...new Set((opts.cloneAreas ?? []).map((a2) =>
|
|
40282
|
+
const areas = [...new Set(opts.areas.map((a2) => resolve8(a2)))];
|
|
40283
|
+
const cloneAreas = [...new Set((opts.cloneAreas ?? []).map((a2) => resolve8(a2)))];
|
|
39737
40284
|
const subs = /* @__PURE__ */ new Set();
|
|
39738
40285
|
const snapshot = () => ({ root: roots[0], roots: [...roots] });
|
|
39739
40286
|
async function assertInArea(abs) {
|
|
@@ -39761,8 +40308,8 @@ function createRootsController(opts) {
|
|
|
39761
40308
|
cloneAreas,
|
|
39762
40309
|
snapshot,
|
|
39763
40310
|
async addFromClone(path) {
|
|
39764
|
-
const abs =
|
|
39765
|
-
if (roots.some((r2) =>
|
|
40311
|
+
const abs = resolve8(path);
|
|
40312
|
+
if (roots.some((r2) => resolve8(r2) === abs)) return;
|
|
39766
40313
|
if (!cloneAreas.length) throw new Error("cloning repositories is not enabled (no --clone-under)");
|
|
39767
40314
|
if (!cloneAreas.some((a2) => withinRoot(a2, abs))) {
|
|
39768
40315
|
throw new Error(`checkout is outside every --clone-under directory: ${abs}`);
|
|
@@ -39780,14 +40327,14 @@ function createRootsController(opts) {
|
|
|
39780
40327
|
async browse(path) {
|
|
39781
40328
|
if (!areas.length) throw new Error(DISABLED);
|
|
39782
40329
|
if (path === void 0) return [...areas];
|
|
39783
|
-
const abs =
|
|
40330
|
+
const abs = resolve8(path);
|
|
39784
40331
|
await assertInArea(abs);
|
|
39785
40332
|
const entries = await fs11.readdir(abs, { withFileTypes: true });
|
|
39786
40333
|
return entries.filter((e2) => e2.isDirectory()).map((e2) => e2.name);
|
|
39787
40334
|
},
|
|
39788
40335
|
async add(path) {
|
|
39789
|
-
const abs =
|
|
39790
|
-
if (roots.some((r2) =>
|
|
40336
|
+
const abs = resolve8(path);
|
|
40337
|
+
if (roots.some((r2) => resolve8(r2) === abs)) return;
|
|
39791
40338
|
await assertInArea(abs);
|
|
39792
40339
|
const st2 = await fs11.stat(abs);
|
|
39793
40340
|
if (!st2.isDirectory()) throw new Error(`not a directory: ${abs}`);
|
|
@@ -39806,7 +40353,7 @@ function createRootsController(opts) {
|
|
|
39806
40353
|
// src/session/gitSource.ts
|
|
39807
40354
|
import { spawn as spawn6 } from "node:child_process";
|
|
39808
40355
|
import { promises as fs12 } from "node:fs";
|
|
39809
|
-
import { join as join12, resolve as
|
|
40356
|
+
import { join as join12, resolve as resolve9 } from "node:path";
|
|
39810
40357
|
var DEFAULT_TIMEOUT_MS3 = 10 * 6e4;
|
|
39811
40358
|
var ALLOWED_SCHEMES = ["https://", "ssh://", "git+ssh://"];
|
|
39812
40359
|
var SCP_LIKE = /^[A-Za-z0-9_.-]+@[A-Za-z0-9_.-]+:[A-Za-z0-9_./~-]+$/;
|
|
@@ -39892,7 +40439,7 @@ function failed(what, r2) {
|
|
|
39892
40439
|
${said}` : ""}`);
|
|
39893
40440
|
}
|
|
39894
40441
|
function createGitSource(deps) {
|
|
39895
|
-
const areas = [...new Set(deps.areas.map((a2) =>
|
|
40442
|
+
const areas = [...new Set(deps.areas.map((a2) => resolve9(a2)))];
|
|
39896
40443
|
async function assertInArea(abs) {
|
|
39897
40444
|
if (!areas.length) throw new Error(DISABLED2);
|
|
39898
40445
|
if (!areas.some((a2) => withinRoot(a2, abs))) {
|
|
@@ -40093,8 +40640,8 @@ var JsonRpcPeer = class {
|
|
|
40093
40640
|
return Promise.reject(new RpcError({ code: RPC_TRANSPORT_CLOSED, message: "transport closed" }));
|
|
40094
40641
|
}
|
|
40095
40642
|
const id = this.nextId++;
|
|
40096
|
-
return new Promise((
|
|
40097
|
-
this.pending.set(id, { resolve:
|
|
40643
|
+
return new Promise((resolve10, reject) => {
|
|
40644
|
+
this.pending.set(id, { resolve: resolve10, reject });
|
|
40098
40645
|
this.write({ jsonrpc: "2.0", id, method, ...params !== void 0 ? { params } : {} });
|
|
40099
40646
|
});
|
|
40100
40647
|
}
|
|
@@ -41202,8 +41749,7 @@ async function main() {
|
|
|
41202
41749
|
const rest = rawArgv.slice(1);
|
|
41203
41750
|
const at2 = rest.indexOf("--msb");
|
|
41204
41751
|
const msbBin = at2 >= 0 && rest[at2 + 1] ? rest[at2 + 1] : process.env.MSB_BIN ?? "msb";
|
|
41205
|
-
|
|
41206
|
-
process.exitCode = await runSessionsCli(verbs, { msbBin, wsl: process.platform === "win32" });
|
|
41752
|
+
process.exitCode = await runSessionsCli(rest, { msbBin, wsl: process.platform === "win32" });
|
|
41207
41753
|
return;
|
|
41208
41754
|
}
|
|
41209
41755
|
if (rawArgv[0] === "login" || rawArgv[0] === "logout" || rawArgv[0] === "auth") {
|
|
@@ -41398,6 +41944,7 @@ async function main() {
|
|
|
41398
41944
|
runner,
|
|
41399
41945
|
image: args.guestImage,
|
|
41400
41946
|
msbBin: args.msbBin,
|
|
41947
|
+
snapshotBudgetBytes: args.snapshotBudgetBytes,
|
|
41401
41948
|
log: slog,
|
|
41402
41949
|
// Metadata for every session run, filed under the CALLER'S own root. The
|
|
41403
41950
|
// runner service owns owner resolution (a wire-supplied clientId is
|