@centralu/host-linux-x64 0.1.0-beta.20 → 0.1.0-beta.22
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/host/bundle-info.json +2 -2
- package/host/main.mjs +1202 -626
- package/package.json +1 -1
package/host/main.mjs
CHANGED
|
@@ -3235,7 +3235,7 @@ var require_stream = __commonJS({
|
|
|
3235
3235
|
};
|
|
3236
3236
|
duplex._final = function(callback) {
|
|
3237
3237
|
if (ws2.readyState === ws2.CONNECTING) {
|
|
3238
|
-
ws2.once("open", function
|
|
3238
|
+
ws2.once("open", function open6() {
|
|
3239
3239
|
duplex._final(callback);
|
|
3240
3240
|
});
|
|
3241
3241
|
return;
|
|
@@ -3256,7 +3256,7 @@ var require_stream = __commonJS({
|
|
|
3256
3256
|
};
|
|
3257
3257
|
duplex._write = function(chunk, encoding, callback) {
|
|
3258
3258
|
if (ws2.readyState === ws2.CONNECTING) {
|
|
3259
|
-
ws2.once("open", function
|
|
3259
|
+
ws2.once("open", function open6() {
|
|
3260
3260
|
duplex._write(chunk, encoding, callback);
|
|
3261
3261
|
});
|
|
3262
3262
|
return;
|
|
@@ -9373,7 +9373,7 @@ function dn(e) {
|
|
|
9373
9373
|
function or(e) {
|
|
9374
9374
|
return e.length > 0 && e.every(dn);
|
|
9375
9375
|
}
|
|
9376
|
-
function
|
|
9376
|
+
function rm4(e) {
|
|
9377
9377
|
return Is(e).some((t) => t.endsWith(".jsonl"));
|
|
9378
9378
|
}
|
|
9379
9379
|
function Is(e) {
|
|
@@ -9523,7 +9523,7 @@ function tS(e, t) {
|
|
|
9523
9523
|
if (U1e(t)) return q(e, M1e);
|
|
9524
9524
|
if (D1e(t)) return q(e, I1e);
|
|
9525
9525
|
if (N1e(t)) return q(e, L1e);
|
|
9526
|
-
return
|
|
9526
|
+
return rm4(t) ? q(e, sm) : void 0;
|
|
9527
9527
|
}
|
|
9528
9528
|
function z1e(e) {
|
|
9529
9529
|
return j1e(`${e}.json`);
|
|
@@ -9532,7 +9532,7 @@ function j1e(e) {
|
|
|
9532
9532
|
return Is(e).includes(F1e);
|
|
9533
9533
|
}
|
|
9534
9534
|
function nS(e) {
|
|
9535
|
-
return Array.isArray(e) && e.some((t) => typeof t === "string" &&
|
|
9535
|
+
return Array.isArray(e) && e.some((t) => typeof t === "string" && rm4(t));
|
|
9536
9536
|
}
|
|
9537
9537
|
function H1e(e) {
|
|
9538
9538
|
return e === "user" || e === "project" || e === "local";
|
|
@@ -11008,7 +11008,7 @@ function L3(e) {
|
|
|
11008
11008
|
}
|
|
11009
11009
|
function U3(e) {
|
|
11010
11010
|
let t = N3(e);
|
|
11011
|
-
if (
|
|
11011
|
+
if (rm4(t)) return;
|
|
11012
11012
|
let n = vt(), r = [t], o = M3(e);
|
|
11013
11013
|
while (o !== n && r.length <= h2e + 1) {
|
|
11014
11014
|
let l = M3(o);
|
|
@@ -42960,12 +42960,12 @@ function refuse(message, io2 = realIo) {
|
|
|
42960
42960
|
import { parseArgs } from "node:util";
|
|
42961
42961
|
import { randomBytes as randomBytes6 } from "node:crypto";
|
|
42962
42962
|
import { homedir as homedir14, hostname as hostname4 } from "node:os";
|
|
42963
|
-
import { dirname as dirname11, join as
|
|
42963
|
+
import { dirname as dirname11, join as join33 } from "node:path";
|
|
42964
42964
|
import { mkdirSync as mkdirSync11, writeSync as writeSync4 } from "node:fs";
|
|
42965
42965
|
|
|
42966
42966
|
// packages/protocol/src/brand.ts
|
|
42967
42967
|
var APP_NAME = "Centralu";
|
|
42968
|
-
var APP_VERSION = "0.1.0-beta.
|
|
42968
|
+
var APP_VERSION = "0.1.0-beta.22";
|
|
42969
42969
|
var APP_SLUG = "centralu";
|
|
42970
42970
|
var CLIENT_INFO = { name: APP_SLUG, title: APP_NAME, version: APP_VERSION };
|
|
42971
42971
|
var DATA_DIR = ".centralu";
|
|
@@ -58032,7 +58032,7 @@ var ApprovalDetail = external_exports.discriminatedUnion("kind", [
|
|
|
58032
58032
|
}),
|
|
58033
58033
|
/**
|
|
58034
58034
|
* One project reaching another (#371) — raised by **the host** in the calling session, the first time a session of
|
|
58035
|
-
* `from` asks to reach `to`. `access` says how: 'delegate'
|
|
58035
|
+
* `from` asks to reach `to`. `access` says how: 'delegate' gives an existing session in `to` a task
|
|
58036
58036
|
* (ask_project, part B); 'apps' attaches one of `to`'s apps to the caller (part A, with `app` naming it).
|
|
58037
58037
|
*
|
|
58038
58038
|
* Unlike `capability`, this one has all three answers: allow once (this call only), always (remembered for this
|
|
@@ -58046,14 +58046,16 @@ var ApprovalDetail = external_exports.discriminatedUnion("kind", [
|
|
|
58046
58046
|
id: external_exports.string(),
|
|
58047
58047
|
name: external_exports.string(),
|
|
58048
58048
|
/**
|
|
58049
|
-
* Whether the person trusts the target project (#382). `false` is said on the card:
|
|
58050
|
-
*
|
|
58049
|
+
* Whether the person trusts the target project (#382). `false` is said on the card: the existing target session
|
|
58050
|
+
* follows that trust. Absent from a host that predates it, read as not saying.
|
|
58051
58051
|
*/
|
|
58052
58052
|
trusted: external_exports.boolean().optional()
|
|
58053
58053
|
}),
|
|
58054
58054
|
/** One line for a person to read: what the caller wants done there (the task, cut short; or the app's use) */
|
|
58055
58055
|
text: external_exports.string(),
|
|
58056
|
-
app: external_exports.object({ appId: external_exports.string(), name: external_exports.string() }).optional()
|
|
58056
|
+
app: external_exports.object({ appId: external_exports.string(), name: external_exports.string() }).optional(),
|
|
58057
|
+
/** The existing session chosen for ask_project; absent from older hosts. */
|
|
58058
|
+
session: external_exports.object({ id: external_exports.string(), name: external_exports.string() }).optional()
|
|
58057
58059
|
}),
|
|
58058
58060
|
/**
|
|
58059
58061
|
* A session asks to start another agent in its own project (#561, start_agent) — raised by **the host** in the
|
|
@@ -58533,7 +58535,9 @@ var UiPreferences = external_exports.object({
|
|
|
58533
58535
|
* preference, but this is the one record the person's settings already live in; the host reads
|
|
58534
58536
|
* it when a session starts and on each call.
|
|
58535
58537
|
*/
|
|
58536
|
-
sessionTools: external_exports.boolean()
|
|
58538
|
+
sessionTools: external_exports.boolean(),
|
|
58539
|
+
/** Running children per project: null uses preset defaults (auto unlimited, otherwise 4); 0 is unlimited. */
|
|
58540
|
+
agentCeiling: external_exports.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).nullable().optional()
|
|
58537
58541
|
});
|
|
58538
58542
|
var UiPreferencesPatch = UiPreferences.partial();
|
|
58539
58543
|
var DEFAULT_UI_PREFERENCES = {
|
|
@@ -59627,14 +59631,9 @@ var SessionInfo = external_exports.object({
|
|
|
59627
59631
|
*/
|
|
59628
59632
|
parentSessionId: external_exports.string().nullable().default(null),
|
|
59629
59633
|
/**
|
|
59630
|
-
* The session that
|
|
59631
|
-
*
|
|
59632
|
-
*
|
|
59633
|
-
* The delegated session is an ordinary session of its own project, so the person can watch it and step in; this
|
|
59634
|
-
* line is what marks it "asked by" and links back to the caller. On the row (store v45), so the mark survives a
|
|
59635
|
-
* restart, and the caller's next ask reuses the session it already asked. The caller may since have been deleted:
|
|
59636
|
-
* the id then names a session that is no longer listed. Optional rather than defaulted so a frame or a fixture
|
|
59637
|
-
* that predates it needs no change: absent and null read the same.
|
|
59634
|
+
* The same-project session that started this agent (`start_agent`, #561). Person-created and app
|
|
59635
|
+
* sessions have no owner. Old cross-project ask_project ownership is cleared on host startup
|
|
59636
|
+
* (#614); cross-project tasks retain their asking session in conversation `from` instead.
|
|
59638
59637
|
*/
|
|
59639
59638
|
askedBy: external_exports.string().nullable().optional(),
|
|
59640
59639
|
...machineField,
|
|
@@ -61760,12 +61759,12 @@ var KeeperAgentProcess = class _KeeperAgentProcess extends EventEmitter {
|
|
|
61760
61759
|
await Promise.race([this.ready, new Promise((r) => setTimeout(r, DETACH_WAIT_MS))]);
|
|
61761
61760
|
const out = this.out;
|
|
61762
61761
|
if (!out) return;
|
|
61763
|
-
await new Promise((
|
|
61764
|
-
if (out.readableEnded || out.destroyed) return
|
|
61765
|
-
const t = setTimeout(
|
|
61762
|
+
await new Promise((resolve12) => {
|
|
61763
|
+
if (out.readableEnded || out.destroyed) return resolve12();
|
|
61764
|
+
const t = setTimeout(resolve12, DETACH_WAIT_MS);
|
|
61766
61765
|
const done = () => {
|
|
61767
61766
|
clearTimeout(t);
|
|
61768
|
-
|
|
61767
|
+
resolve12();
|
|
61769
61768
|
};
|
|
61770
61769
|
out.once("end", done);
|
|
61771
61770
|
out.once("close", done);
|
|
@@ -61782,7 +61781,7 @@ import { createConnection } from "node:net";
|
|
|
61782
61781
|
var CHILDREN_PROTOCOL = 1;
|
|
61783
61782
|
var REQUEST_TIMEOUT_MS = 15e3;
|
|
61784
61783
|
function readAnswer(sock, timeoutMs) {
|
|
61785
|
-
return new Promise((
|
|
61784
|
+
return new Promise((resolve12, reject) => {
|
|
61786
61785
|
let buf = Buffer.alloc(0);
|
|
61787
61786
|
const done = (err, v) => {
|
|
61788
61787
|
clearTimeout(timer);
|
|
@@ -61790,7 +61789,7 @@ function readAnswer(sock, timeoutMs) {
|
|
|
61790
61789
|
sock.off("error", onError);
|
|
61791
61790
|
sock.off("close", onClose);
|
|
61792
61791
|
if (err) reject(err);
|
|
61793
|
-
else
|
|
61792
|
+
else resolve12(v);
|
|
61794
61793
|
};
|
|
61795
61794
|
const onData = (d2) => {
|
|
61796
61795
|
buf = Buffer.concat([buf, d2]);
|
|
@@ -61819,7 +61818,7 @@ function readAnswer(sock, timeoutMs) {
|
|
|
61819
61818
|
});
|
|
61820
61819
|
}
|
|
61821
61820
|
function connect(path2, first, timeoutMs) {
|
|
61822
|
-
return new Promise((
|
|
61821
|
+
return new Promise((resolve12, reject) => {
|
|
61823
61822
|
const sock = createConnection(path2);
|
|
61824
61823
|
const onError = (e) => reject(e);
|
|
61825
61824
|
sock.once("error", onError);
|
|
@@ -61833,7 +61832,7 @@ function connect(path2, first, timeoutMs) {
|
|
|
61833
61832
|
sock.destroy();
|
|
61834
61833
|
return reject(new Error(String(answer.error ?? "the keeper refused")));
|
|
61835
61834
|
}
|
|
61836
|
-
|
|
61835
|
+
resolve12({ sock, answer });
|
|
61837
61836
|
},
|
|
61838
61837
|
(e) => {
|
|
61839
61838
|
sock.destroy();
|
|
@@ -61919,12 +61918,12 @@ var KeeperChildren = class _KeeperChildren extends EventEmitter2 {
|
|
|
61919
61918
|
request(op, body = {}) {
|
|
61920
61919
|
if (this.closed) return Promise.reject(new Error("the connection to the keeper was lost"));
|
|
61921
61920
|
const rid = this.nextRid++;
|
|
61922
|
-
return new Promise((
|
|
61921
|
+
return new Promise((resolve12, reject) => {
|
|
61923
61922
|
const timer = setTimeout(() => {
|
|
61924
61923
|
if (this.pending.delete(rid)) reject(new Error(`the keeper did not answer ${op}`));
|
|
61925
61924
|
}, REQUEST_TIMEOUT_MS);
|
|
61926
61925
|
timer.unref();
|
|
61927
|
-
this.pending.set(rid, { resolve:
|
|
61926
|
+
this.pending.set(rid, { resolve: resolve12, reject, timer });
|
|
61928
61927
|
this.sock.write(`${JSON.stringify({ ...body, op, rid })}
|
|
61929
61928
|
`);
|
|
61930
61929
|
});
|
|
@@ -62105,12 +62104,12 @@ var KeeperPty = class _KeeperPty {
|
|
|
62105
62104
|
await Promise.race([this.ready, new Promise((r) => setTimeout(r, 1e3))]);
|
|
62106
62105
|
const sock = this.sock;
|
|
62107
62106
|
if (!sock) return;
|
|
62108
|
-
await new Promise((
|
|
62109
|
-
if (sock.readableEnded || sock.destroyed) return
|
|
62110
|
-
const t = setTimeout(
|
|
62107
|
+
await new Promise((resolve12) => {
|
|
62108
|
+
if (sock.readableEnded || sock.destroyed) return resolve12();
|
|
62109
|
+
const t = setTimeout(resolve12, 1e3);
|
|
62111
62110
|
const done = () => {
|
|
62112
62111
|
clearTimeout(t);
|
|
62113
|
-
|
|
62112
|
+
resolve12();
|
|
62114
62113
|
};
|
|
62115
62114
|
sock.once("end", done);
|
|
62116
62115
|
sock.once("close", done);
|
|
@@ -62473,7 +62472,7 @@ var Drain = class {
|
|
|
62473
62472
|
*/
|
|
62474
62473
|
track(label, run) {
|
|
62475
62474
|
if (this.refusing) return Promise.reject(new DrainCut(REFUSED_MESSAGE));
|
|
62476
|
-
return new Promise((
|
|
62475
|
+
return new Promise((resolve12, reject) => {
|
|
62477
62476
|
let settled = false;
|
|
62478
62477
|
const settle = () => {
|
|
62479
62478
|
if (settled) return false;
|
|
@@ -62485,7 +62484,7 @@ var Drain = class {
|
|
|
62485
62484
|
const entry = { label, cut: (e) => settle() && reject(e) };
|
|
62486
62485
|
this.inflight.add(entry);
|
|
62487
62486
|
Promise.resolve().then(run).then(
|
|
62488
|
-
(v) => settle() &&
|
|
62487
|
+
(v) => settle() && resolve12(v),
|
|
62489
62488
|
(e) => settle() && reject(e)
|
|
62490
62489
|
);
|
|
62491
62490
|
});
|
|
@@ -62634,7 +62633,7 @@ var HostServer = class _HostServer {
|
|
|
62634
62633
|
return this.allowedOrigins.has(origin);
|
|
62635
62634
|
}
|
|
62636
62635
|
listen() {
|
|
62637
|
-
return new Promise((
|
|
62636
|
+
return new Promise((resolve12, reject) => {
|
|
62638
62637
|
const onError = (err) => {
|
|
62639
62638
|
this.listenError = null;
|
|
62640
62639
|
if (err.code === "EADDRINUSE") {
|
|
@@ -62651,7 +62650,7 @@ var HostServer = class _HostServer {
|
|
|
62651
62650
|
this.listenError = null;
|
|
62652
62651
|
this.http.removeListener("error", onError);
|
|
62653
62652
|
const addr = this.http.address();
|
|
62654
|
-
|
|
62653
|
+
resolve12(typeof addr === "object" && addr ? addr.port : this.opts.port);
|
|
62655
62654
|
});
|
|
62656
62655
|
});
|
|
62657
62656
|
}
|
|
@@ -62909,17 +62908,17 @@ function portInUseMessage(port2, servedBy) {
|
|
|
62909
62908
|
var VIEW_HANDOVER_KEY = "views.handover";
|
|
62910
62909
|
var VIEW_HANDOVER_MAX_AGE_MS = 10 * 6e4;
|
|
62911
62910
|
function recordViewHandover(settings, views2, inline, now = Date.now()) {
|
|
62912
|
-
const
|
|
62911
|
+
const open6 = views2.list().map((v) => {
|
|
62913
62912
|
const owner = inline?.owner(v.id);
|
|
62914
62913
|
return owner ? { ...v, inline: { sessionId: owner.sessionId, callId: owner.callId, tool: owner.tool } } : v;
|
|
62915
62914
|
});
|
|
62916
|
-
if (
|
|
62915
|
+
if (open6.length === 0) {
|
|
62917
62916
|
settings.deleteAppSetting(VIEW_HANDOVER_KEY);
|
|
62918
62917
|
return 0;
|
|
62919
62918
|
}
|
|
62920
|
-
const record2 = { at: now, views:
|
|
62919
|
+
const record2 = { at: now, views: open6 };
|
|
62921
62920
|
settings.setAppSetting(VIEW_HANDOVER_KEY, JSON.stringify(record2));
|
|
62922
|
-
return
|
|
62921
|
+
return open6.length;
|
|
62923
62922
|
}
|
|
62924
62923
|
function restoreViewHandover(settings, views2, inline, now = Date.now()) {
|
|
62925
62924
|
const raw = settings.appSetting(VIEW_HANDOVER_KEY);
|
|
@@ -63028,15 +63027,15 @@ function buildViewCsp(csp) {
|
|
|
63028
63027
|
const frame = sanitizeDomains(csp?.frameDomains);
|
|
63029
63028
|
const base2 = sanitizeDomains(csp?.baseUriDomains);
|
|
63030
63029
|
const r = resource.kept.join(" ");
|
|
63031
|
-
const
|
|
63030
|
+
const join34 = (...parts) => parts.filter(Boolean).join(" ");
|
|
63032
63031
|
const policy = [
|
|
63033
63032
|
"default-src 'none'",
|
|
63034
|
-
`script-src ${
|
|
63035
|
-
`style-src ${
|
|
63036
|
-
`img-src ${
|
|
63037
|
-
`font-src ${
|
|
63038
|
-
`media-src ${
|
|
63039
|
-
`worker-src ${
|
|
63033
|
+
`script-src ${join34("'unsafe-inline'", r)}`,
|
|
63034
|
+
`style-src ${join34("'unsafe-inline'", r)}`,
|
|
63035
|
+
`img-src ${join34("data: blob:", r)}`,
|
|
63036
|
+
`font-src ${join34("data:", r)}`,
|
|
63037
|
+
`media-src ${join34("data: blob:", r)}`,
|
|
63038
|
+
`worker-src ${join34("blob:", r)}`,
|
|
63040
63039
|
`connect-src ${connect2.kept.length ? connect2.kept.join(" ") : "'none'"}`,
|
|
63041
63040
|
`frame-src ${frame.kept.length ? frame.kept.join(" ") : "'none'"}`,
|
|
63042
63041
|
"form-action 'none'",
|
|
@@ -63640,9 +63639,9 @@ import { join as join15 } from "node:path";
|
|
|
63640
63639
|
import { lstatSync as lstatSync2, watch } from "node:fs";
|
|
63641
63640
|
|
|
63642
63641
|
// packages/agent-host/src/dev-services/fs.ts
|
|
63643
|
-
import { spawn } from "node:child_process";
|
|
63642
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
63644
63643
|
import { constants as constants3 } from "node:fs";
|
|
63645
|
-
import { lstat as lstat2, mkdir, open, readdir, readlink, realpath as realpath2, rename, rm, stat as stat2, writeFile } from "node:fs/promises";
|
|
63644
|
+
import { lstat as lstat2, mkdir, open as open2, readdir, readlink, realpath as realpath2, rename, rm, stat as stat2, writeFile } from "node:fs/promises";
|
|
63646
63645
|
import { basename, dirname as dirname2, extname, isAbsolute as isAbsolute2, join as join5, relative as relative2, resolve, sep as sep2 } from "node:path";
|
|
63647
63646
|
|
|
63648
63647
|
// packages/agent-host/src/dev-services/path-guard.ts
|
|
@@ -63777,7 +63776,8 @@ function isMissingFsPath(error51) {
|
|
|
63777
63776
|
}
|
|
63778
63777
|
|
|
63779
63778
|
// packages/agent-host/src/dev-services/git-exec.ts
|
|
63780
|
-
import { execFile } from "node:child_process";
|
|
63779
|
+
import { execFile, spawn } from "node:child_process";
|
|
63780
|
+
import { open } from "node:fs/promises";
|
|
63781
63781
|
import { devNull } from "node:os";
|
|
63782
63782
|
import { promisify } from "node:util";
|
|
63783
63783
|
|
|
@@ -64003,6 +64003,40 @@ async function gitArgv(cwd, args, allowRepoPrograms = false) {
|
|
|
64003
64003
|
}
|
|
64004
64004
|
async function runGit(cwd, args, opts = {}) {
|
|
64005
64005
|
const argv = await gitArgv(cwd, args, opts.allowRepoPrograms);
|
|
64006
|
+
if (opts.outputFile) {
|
|
64007
|
+
const output = await open(opts.outputFile, "w", 384);
|
|
64008
|
+
try {
|
|
64009
|
+
await new Promise((resolve12, reject) => {
|
|
64010
|
+
const child = spawn(programPath("git"), argv, {
|
|
64011
|
+
cwd,
|
|
64012
|
+
stdio: ["pipe", output.fd, "pipe"],
|
|
64013
|
+
timeout: opts.timeout ?? DEFAULTS.timeout,
|
|
64014
|
+
...opts.env ? { env: { ...process.env, ...opts.env } } : {}
|
|
64015
|
+
});
|
|
64016
|
+
let stderr = "";
|
|
64017
|
+
let overflow = false;
|
|
64018
|
+
child.stderr.on("data", (chunk) => {
|
|
64019
|
+
if (overflow) return;
|
|
64020
|
+
stderr += chunk.toString();
|
|
64021
|
+
if (Buffer.byteLength(stderr) > (opts.maxBuffer ?? DEFAULTS.maxBuffer)) {
|
|
64022
|
+
overflow = true;
|
|
64023
|
+
child.kill();
|
|
64024
|
+
}
|
|
64025
|
+
});
|
|
64026
|
+
child.on("error", reject);
|
|
64027
|
+
child.on("close", (code2, signal) => {
|
|
64028
|
+
if (code2 === 0 && !overflow) resolve12();
|
|
64029
|
+
else reject(Object.assign(new Error(overflow ? "Git stderr exceeded maxBuffer" : stderr || `Git exited with ${code2 ?? signal}`), { code: code2, stderr }));
|
|
64030
|
+
});
|
|
64031
|
+
child.stdin.on("error", () => {
|
|
64032
|
+
});
|
|
64033
|
+
child.stdin.end(opts.input);
|
|
64034
|
+
});
|
|
64035
|
+
return "";
|
|
64036
|
+
} finally {
|
|
64037
|
+
await output.close();
|
|
64038
|
+
}
|
|
64039
|
+
}
|
|
64006
64040
|
const run = exec(programPath("git"), argv, {
|
|
64007
64041
|
cwd,
|
|
64008
64042
|
timeout: opts.timeout ?? DEFAULTS.timeout,
|
|
@@ -64163,7 +64197,7 @@ async function readTextFile(root3, rel) {
|
|
|
64163
64197
|
const file2 = await resolveExisting(root3, rel);
|
|
64164
64198
|
const pathInfo = await stat2(file2, { bigint: true });
|
|
64165
64199
|
if (!pathInfo.isFile()) fail2("Path is not a regular file");
|
|
64166
|
-
const handle = await
|
|
64200
|
+
const handle = await open2(file2, READ_TEXT_FLAGS);
|
|
64167
64201
|
try {
|
|
64168
64202
|
const opened = await handle.stat({ bigint: true });
|
|
64169
64203
|
if (!opened.isFile()) fail2("Path is not a regular file");
|
|
@@ -64229,7 +64263,7 @@ async function copyTree(src, dst, clone2 = cloneTree) {
|
|
|
64229
64263
|
async function cloneTree(src, dst) {
|
|
64230
64264
|
if (!clonesFiles()) return false;
|
|
64231
64265
|
return new Promise((done) => {
|
|
64232
|
-
const p =
|
|
64266
|
+
const p = spawn2("/bin/cp", ["-Rc", src, dst], { stdio: "ignore" });
|
|
64233
64267
|
p.on("error", () => done(false));
|
|
64234
64268
|
p.on("close", (code2) => done(code2 === 0));
|
|
64235
64269
|
});
|
|
@@ -64386,7 +64420,7 @@ function isSymlink(path2) {
|
|
|
64386
64420
|
}
|
|
64387
64421
|
|
|
64388
64422
|
// packages/agent-host/src/apps/external/app-process.ts
|
|
64389
|
-
import { spawn as
|
|
64423
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
64390
64424
|
import { closeSync as closeSync2, existsSync as existsSync5, mkdirSync, openSync as openSync2, renameSync as renameSync3, statSync as statSync3, writeSync as writeSync3 } from "node:fs";
|
|
64391
64425
|
import { dirname as dirname3 } from "node:path";
|
|
64392
64426
|
|
|
@@ -68738,14 +68772,14 @@ function inputRequiredRoundsExceededMessage(method, maxRounds) {
|
|
|
68738
68772
|
return `Multi-round-trip request '${method}' still required input after ${maxRounds} rounds (inputRequired.maxRounds)`;
|
|
68739
68773
|
}
|
|
68740
68774
|
function sleep(ms3, signal) {
|
|
68741
|
-
return new Promise((
|
|
68775
|
+
return new Promise((resolve12, reject) => {
|
|
68742
68776
|
if (signal?.aborted) {
|
|
68743
68777
|
reject(signal.reason instanceof SdkError ? signal.reason : new SdkError(SdkErrorCode.RequestTimeout, String(signal.reason)));
|
|
68744
68778
|
return;
|
|
68745
68779
|
}
|
|
68746
68780
|
const timer = setTimeout(() => {
|
|
68747
68781
|
signal?.removeEventListener("abort", onAbort);
|
|
68748
|
-
|
|
68782
|
+
resolve12();
|
|
68749
68783
|
}, ms3);
|
|
68750
68784
|
const onAbort = () => {
|
|
68751
68785
|
clearTimeout(timer);
|
|
@@ -69584,7 +69618,7 @@ var Protocol = class {
|
|
|
69584
69618
|
const flowStartedAt = Date.now();
|
|
69585
69619
|
let onAbort;
|
|
69586
69620
|
let cleanupMessageId;
|
|
69587
|
-
return new Promise((
|
|
69621
|
+
return new Promise((resolve12, reject) => {
|
|
69588
69622
|
const earlyReject = (error51) => {
|
|
69589
69623
|
reject(error51);
|
|
69590
69624
|
};
|
|
@@ -69653,7 +69687,7 @@ var Protocol = class {
|
|
|
69653
69687
|
}
|
|
69654
69688
|
if (decoded.kind === "invalid") return reject(decoded.error);
|
|
69655
69689
|
if (decoded.kind === "input_required") {
|
|
69656
|
-
if (options?.allowInputRequired === true) return
|
|
69690
|
+
if (options?.allowInputRequired === true) return resolve12(manualInputRequiredValue(decoded));
|
|
69657
69691
|
const flow = {
|
|
69658
69692
|
codec: codec2,
|
|
69659
69693
|
request,
|
|
@@ -69665,11 +69699,11 @@ var Protocol = class {
|
|
|
69665
69699
|
params
|
|
69666
69700
|
}, resultSchema, legOptions)
|
|
69667
69701
|
};
|
|
69668
|
-
return
|
|
69702
|
+
return resolve12(this._resolveNonCompleteResult(decoded, flow));
|
|
69669
69703
|
}
|
|
69670
69704
|
const result = decoded.result;
|
|
69671
69705
|
validateStandardSchema(resultSchema, result).then((parseResult) => {
|
|
69672
|
-
if (parseResult.success)
|
|
69706
|
+
if (parseResult.success) resolve12(parseResult.data);
|
|
69673
69707
|
else reject(new SdkError(SdkErrorCode.InvalidResult, `Invalid result for ${request.method}: ${parseResult.error}`));
|
|
69674
69708
|
}, reject);
|
|
69675
69709
|
});
|
|
@@ -72592,7 +72626,7 @@ var require_compile = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
72592
72626
|
ref = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, ref);
|
|
72593
72627
|
const schOrFunc = root3.refs[ref];
|
|
72594
72628
|
if (schOrFunc) return schOrFunc;
|
|
72595
|
-
let _sch =
|
|
72629
|
+
let _sch = resolve12.call(this, root3, ref);
|
|
72596
72630
|
if (_sch === void 0) {
|
|
72597
72631
|
const schema = (_a4 = root3.localRefs) === null || _a4 === void 0 ? void 0 : _a4[ref];
|
|
72598
72632
|
const { schemaId } = this.opts;
|
|
@@ -72618,7 +72652,7 @@ var require_compile = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
72618
72652
|
function sameSchemaEnv(s12, s22) {
|
|
72619
72653
|
return s12.schema === s22.schema && s12.root === s22.root && s12.baseId === s22.baseId;
|
|
72620
72654
|
}
|
|
72621
|
-
function
|
|
72655
|
+
function resolve12(root3, ref) {
|
|
72622
72656
|
let sch;
|
|
72623
72657
|
while (typeof (sch = this.refs[ref]) == "string") ref = sch;
|
|
72624
72658
|
return sch || this.schemas[ref] || resolveSchema.call(this, root3, ref);
|
|
@@ -73068,7 +73102,7 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
73068
73102
|
else if (typeof uri === "object") uri = parse7(serialize(uri, options), options);
|
|
73069
73103
|
return uri;
|
|
73070
73104
|
}
|
|
73071
|
-
function
|
|
73105
|
+
function resolve12(baseURI, relativeURI, options) {
|
|
73072
73106
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
73073
73107
|
const resolved = resolveComponent(parse7(baseURI, schemelessOptions), parse7(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
73074
73108
|
schemelessOptions.skipEscape = true;
|
|
@@ -73242,7 +73276,7 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
73242
73276
|
const fastUri = {
|
|
73243
73277
|
SCHEMES,
|
|
73244
73278
|
normalize: normalize2,
|
|
73245
|
-
resolve:
|
|
73279
|
+
resolve: resolve12,
|
|
73246
73280
|
resolveComponent,
|
|
73247
73281
|
equal,
|
|
73248
73282
|
serialize,
|
|
@@ -77857,14 +77891,14 @@ var ProbeWindow = class ProbeWindow2 {
|
|
|
77857
77891
|
*/
|
|
77858
77892
|
async exchange(buildRequest, timeoutMs) {
|
|
77859
77893
|
const id2 = `server-discover-probe-${++this._probeCounter}`;
|
|
77860
|
-
return new Promise((
|
|
77894
|
+
return new Promise((resolve12) => {
|
|
77861
77895
|
let settled = false;
|
|
77862
77896
|
const settle = (reply) => {
|
|
77863
77897
|
if (settled) return;
|
|
77864
77898
|
settled = true;
|
|
77865
77899
|
clearTimeout(timer);
|
|
77866
77900
|
if (this._pending?.id === id2) this._pending = void 0;
|
|
77867
|
-
|
|
77901
|
+
resolve12(reply);
|
|
77868
77902
|
};
|
|
77869
77903
|
const timer = setTimeout(() => settle({ kind: "timeout" }), timeoutMs);
|
|
77870
77904
|
this._pending = {
|
|
@@ -79070,13 +79104,13 @@ var Client = class extends Protocol {
|
|
|
79070
79104
|
let onCallerAbort;
|
|
79071
79105
|
let resolveOpening;
|
|
79072
79106
|
let rejectOpening;
|
|
79073
|
-
const opening = new Promise((
|
|
79074
|
-
resolveOpening =
|
|
79107
|
+
const opening = new Promise((resolve12, reject) => {
|
|
79108
|
+
resolveOpening = resolve12;
|
|
79075
79109
|
rejectOpening = reject;
|
|
79076
79110
|
});
|
|
79077
79111
|
let resolveClosed;
|
|
79078
|
-
const closed = new Promise((
|
|
79079
|
-
resolveClosed =
|
|
79112
|
+
const closed = new Promise((resolve12) => {
|
|
79113
|
+
resolveClosed = resolve12;
|
|
79080
79114
|
});
|
|
79081
79115
|
const settle = (outcome) => {
|
|
79082
79116
|
if (state === "closed") return;
|
|
@@ -79848,8 +79882,8 @@ var StreamTransport = class {
|
|
|
79848
79882
|
}
|
|
79849
79883
|
send(message) {
|
|
79850
79884
|
if (this.closed) return Promise.reject(new Error("app connection closed"));
|
|
79851
|
-
return new Promise((
|
|
79852
|
-
this.writable.write(serializeMessage(message), (err) => err ? reject(err) :
|
|
79885
|
+
return new Promise((resolve12, reject) => {
|
|
79886
|
+
this.writable.write(serializeMessage(message), (err) => err ? reject(err) : resolve12());
|
|
79853
79887
|
});
|
|
79854
79888
|
}
|
|
79855
79889
|
/** Closes only our own state. Closing the pipes is the shutdown rule's job (AppProcess.stop). */
|
|
@@ -79942,7 +79976,7 @@ var AppProcess = class _AppProcess {
|
|
|
79942
79976
|
log.note(`starting: ${spec.command} ${spec.args.join(" ")}`);
|
|
79943
79977
|
const launch2 = resolveCommand(spec.command, spec.env);
|
|
79944
79978
|
const spawnedAt = Date.now();
|
|
79945
|
-
const child =
|
|
79979
|
+
const child = spawn3(launch2.command, [...launch2.args, ...spec.args], {
|
|
79946
79980
|
cwd: spec.cwd,
|
|
79947
79981
|
env: spec.env,
|
|
79948
79982
|
/*
|
|
@@ -80061,12 +80095,12 @@ ${tail}` : head;
|
|
|
80061
80095
|
}
|
|
80062
80096
|
waitExit(ms3) {
|
|
80063
80097
|
if (!this.alive) return Promise.resolve(true);
|
|
80064
|
-
return new Promise((
|
|
80065
|
-
const t = setTimeout(() =>
|
|
80098
|
+
return new Promise((resolve12) => {
|
|
80099
|
+
const t = setTimeout(() => resolve12(false), ms3);
|
|
80066
80100
|
t.unref();
|
|
80067
80101
|
this.exitWaiters.push(() => {
|
|
80068
80102
|
clearTimeout(t);
|
|
80069
|
-
|
|
80103
|
+
resolve12(true);
|
|
80070
80104
|
});
|
|
80071
80105
|
});
|
|
80072
80106
|
}
|
|
@@ -83653,14 +83687,14 @@ function inputRequiredRoundsExceededMessage2(method, maxRounds) {
|
|
|
83653
83687
|
return `Multi-round-trip request '${method}' still required input after ${maxRounds} rounds (inputRequired.maxRounds)`;
|
|
83654
83688
|
}
|
|
83655
83689
|
function sleep2(ms3, signal) {
|
|
83656
|
-
return new Promise((
|
|
83690
|
+
return new Promise((resolve12, reject) => {
|
|
83657
83691
|
if (signal?.aborted) {
|
|
83658
83692
|
reject(signal.reason instanceof SdkError2 ? signal.reason : new SdkError2(SdkErrorCode2.RequestTimeout, String(signal.reason)));
|
|
83659
83693
|
return;
|
|
83660
83694
|
}
|
|
83661
83695
|
const timer = setTimeout(() => {
|
|
83662
83696
|
signal?.removeEventListener("abort", onAbort);
|
|
83663
|
-
|
|
83697
|
+
resolve12();
|
|
83664
83698
|
}, ms3);
|
|
83665
83699
|
const onAbort = () => {
|
|
83666
83700
|
clearTimeout(timer);
|
|
@@ -84454,7 +84488,7 @@ var Protocol2 = class {
|
|
|
84454
84488
|
const flowStartedAt = Date.now();
|
|
84455
84489
|
let onAbort;
|
|
84456
84490
|
let cleanupMessageId;
|
|
84457
|
-
return new Promise((
|
|
84491
|
+
return new Promise((resolve12, reject) => {
|
|
84458
84492
|
const earlyReject = (error51) => {
|
|
84459
84493
|
reject(error51);
|
|
84460
84494
|
};
|
|
@@ -84523,7 +84557,7 @@ var Protocol2 = class {
|
|
|
84523
84557
|
}
|
|
84524
84558
|
if (decoded.kind === "invalid") return reject(decoded.error);
|
|
84525
84559
|
if (decoded.kind === "input_required") {
|
|
84526
|
-
if (options?.allowInputRequired === true) return
|
|
84560
|
+
if (options?.allowInputRequired === true) return resolve12(manualInputRequiredValue2(decoded));
|
|
84527
84561
|
const flow = {
|
|
84528
84562
|
codec: codec2,
|
|
84529
84563
|
request,
|
|
@@ -84535,11 +84569,11 @@ var Protocol2 = class {
|
|
|
84535
84569
|
params
|
|
84536
84570
|
}, resultSchema, legOptions)
|
|
84537
84571
|
};
|
|
84538
|
-
return
|
|
84572
|
+
return resolve12(this._resolveNonCompleteResult(decoded, flow));
|
|
84539
84573
|
}
|
|
84540
84574
|
const result = decoded.result;
|
|
84541
84575
|
validateStandardSchema2(resultSchema, result).then((parseResult) => {
|
|
84542
|
-
if (parseResult.success)
|
|
84576
|
+
if (parseResult.success) resolve12(parseResult.data);
|
|
84543
84577
|
else reject(new SdkError2(SdkErrorCode2.InvalidResult, `Invalid result for ${request.method}: ${parseResult.error}`));
|
|
84544
84578
|
}, reject);
|
|
84545
84579
|
});
|
|
@@ -87451,7 +87485,7 @@ var require_compile2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
|
|
|
87451
87485
|
ref = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, ref);
|
|
87452
87486
|
const schOrFunc = root3.refs[ref];
|
|
87453
87487
|
if (schOrFunc) return schOrFunc;
|
|
87454
|
-
let _sch =
|
|
87488
|
+
let _sch = resolve12.call(this, root3, ref);
|
|
87455
87489
|
if (_sch === void 0) {
|
|
87456
87490
|
const schema = (_a4 = root3.localRefs) === null || _a4 === void 0 ? void 0 : _a4[ref];
|
|
87457
87491
|
const { schemaId } = this.opts;
|
|
@@ -87477,7 +87511,7 @@ var require_compile2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
|
|
|
87477
87511
|
function sameSchemaEnv(s12, s22) {
|
|
87478
87512
|
return s12.schema === s22.schema && s12.root === s22.root && s12.baseId === s22.baseId;
|
|
87479
87513
|
}
|
|
87480
|
-
function
|
|
87514
|
+
function resolve12(root3, ref) {
|
|
87481
87515
|
let sch;
|
|
87482
87516
|
while (typeof (sch = this.refs[ref]) == "string") ref = sch;
|
|
87483
87517
|
return sch || this.schemas[ref] || resolveSchema.call(this, root3, ref);
|
|
@@ -87927,7 +87961,7 @@ var require_fast_uri2 = /* @__PURE__ */ __commonJSMin2(((exports, module) => {
|
|
|
87927
87961
|
else if (typeof uri === "object") uri = parse7(serialize(uri, options), options);
|
|
87928
87962
|
return uri;
|
|
87929
87963
|
}
|
|
87930
|
-
function
|
|
87964
|
+
function resolve12(baseURI, relativeURI, options) {
|
|
87931
87965
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
87932
87966
|
const resolved = resolveComponent(parse7(baseURI, schemelessOptions), parse7(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
87933
87967
|
schemelessOptions.skipEscape = true;
|
|
@@ -88101,7 +88135,7 @@ var require_fast_uri2 = /* @__PURE__ */ __commonJSMin2(((exports, module) => {
|
|
|
88101
88135
|
const fastUri = {
|
|
88102
88136
|
SCHEMES,
|
|
88103
88137
|
normalize: normalize2,
|
|
88104
|
-
resolve:
|
|
88138
|
+
resolve: resolve12,
|
|
88105
88139
|
resolveComponent,
|
|
88106
88140
|
equal,
|
|
88107
88141
|
serialize,
|
|
@@ -93534,7 +93568,7 @@ var StdioServerTransport = class {
|
|
|
93534
93568
|
}
|
|
93535
93569
|
send(message) {
|
|
93536
93570
|
if (this._closed) return Promise.reject(/* @__PURE__ */ new Error("StdioServerTransport is closed"));
|
|
93537
|
-
return new Promise((
|
|
93571
|
+
return new Promise((resolve12, reject) => {
|
|
93538
93572
|
const json2 = serializeMessage2(message);
|
|
93539
93573
|
let settled = false;
|
|
93540
93574
|
const onError = (error51) => {
|
|
@@ -93549,14 +93583,14 @@ var StdioServerTransport = class {
|
|
|
93549
93583
|
settled = true;
|
|
93550
93584
|
this._stdout.off("error", onError);
|
|
93551
93585
|
this._stdout.off("drain", onDrain2);
|
|
93552
|
-
|
|
93586
|
+
resolve12();
|
|
93553
93587
|
};
|
|
93554
93588
|
this._stdout.once("error", onError);
|
|
93555
93589
|
if (this._stdout.write(json2)) {
|
|
93556
93590
|
if (settled) return;
|
|
93557
93591
|
settled = true;
|
|
93558
93592
|
this._stdout.off("error", onError);
|
|
93559
|
-
|
|
93593
|
+
resolve12();
|
|
93560
93594
|
} else if (!settled) this._stdout.once("drain", onDrain2);
|
|
93561
93595
|
});
|
|
93562
93596
|
}
|
|
@@ -93610,14 +93644,14 @@ var StdioConnectionChannel = class {
|
|
|
93610
93644
|
*/
|
|
93611
93645
|
async whenRequestsAnswered(timeoutMs) {
|
|
93612
93646
|
if (this._closed || this._pendingRequests.size === 0) return true;
|
|
93613
|
-
return await new Promise((
|
|
93647
|
+
return await new Promise((resolve12) => {
|
|
93614
93648
|
const waiter = () => {
|
|
93615
93649
|
clearTimeout(timer);
|
|
93616
|
-
|
|
93650
|
+
resolve12(true);
|
|
93617
93651
|
};
|
|
93618
93652
|
const timer = setTimeout(() => {
|
|
93619
93653
|
this._drainWaiters = this._drainWaiters.filter((pending) => pending !== waiter);
|
|
93620
|
-
|
|
93654
|
+
resolve12(false);
|
|
93621
93655
|
}, timeoutMs);
|
|
93622
93656
|
this._drainWaiters.push(waiter);
|
|
93623
93657
|
});
|
|
@@ -94046,7 +94080,7 @@ var RUN_RETENTION_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
|
94046
94080
|
var FAILURES_KEPT = 20;
|
|
94047
94081
|
var SUMMARY_CHARS = 200;
|
|
94048
94082
|
function announcingLedger(inner, announce) {
|
|
94049
|
-
const
|
|
94083
|
+
const open6 = /* @__PURE__ */ new Map();
|
|
94050
94084
|
const tell = (apps) => {
|
|
94051
94085
|
for (const a of apps ?? []) announce(a);
|
|
94052
94086
|
};
|
|
@@ -94055,20 +94089,20 @@ function announcingLedger(inner, announce) {
|
|
|
94055
94089
|
begin(row) {
|
|
94056
94090
|
inner.begin(row);
|
|
94057
94091
|
const apps = [{ projectId: row.projectId, appId: row.appId }];
|
|
94058
|
-
for (const up2 of row.parentRunId ?
|
|
94092
|
+
for (const up2 of row.parentRunId ? open6.get(row.parentRunId) ?? [] : []) {
|
|
94059
94093
|
if (!apps.some((a) => a.projectId === up2.projectId && a.appId === up2.appId)) apps.push(up2);
|
|
94060
94094
|
}
|
|
94061
|
-
|
|
94095
|
+
open6.set(row.id, apps);
|
|
94062
94096
|
tell(apps);
|
|
94063
94097
|
},
|
|
94064
94098
|
link(id2, sessionId) {
|
|
94065
94099
|
inner.link(id2, sessionId);
|
|
94066
|
-
tell(
|
|
94100
|
+
tell(open6.get(id2));
|
|
94067
94101
|
},
|
|
94068
94102
|
end(id2, e) {
|
|
94069
94103
|
inner.end(id2, e);
|
|
94070
|
-
tell(
|
|
94071
|
-
|
|
94104
|
+
tell(open6.get(id2));
|
|
94105
|
+
open6.delete(id2);
|
|
94072
94106
|
}
|
|
94073
94107
|
};
|
|
94074
94108
|
}
|
|
@@ -94530,7 +94564,7 @@ var BrokerDesk = class {
|
|
|
94530
94564
|
try {
|
|
94531
94565
|
answer = await Promise.race([
|
|
94532
94566
|
asked.answer,
|
|
94533
|
-
new Promise((
|
|
94567
|
+
new Promise((resolve12) => call2.signal.aborted ? resolve12(null) : call2.signal.addEventListener("abort", () => resolve12(null), { once: true }))
|
|
94534
94568
|
]);
|
|
94535
94569
|
} finally {
|
|
94536
94570
|
call2.signal.removeEventListener("abort", leave);
|
|
@@ -96454,6 +96488,11 @@ var ExternalApps = class {
|
|
|
96454
96488
|
}
|
|
96455
96489
|
return out;
|
|
96456
96490
|
}
|
|
96491
|
+
sessionBoundaryOpen = () => false;
|
|
96492
|
+
/** Host-owned policy; a request cannot claim an auto preset in its arguments. */
|
|
96493
|
+
setSessionBoundaryPolicy(policy) {
|
|
96494
|
+
this.sessionBoundaryOpen = policy;
|
|
96495
|
+
}
|
|
96457
96496
|
/**
|
|
96458
96497
|
* An app's tool list — if the app is down, **this is where it starts** (the first time it is
|
|
96459
96498
|
* needed).
|
|
@@ -96462,9 +96501,9 @@ var ExternalApps = class {
|
|
|
96462
96501
|
* read is the place it is enforced: this list becomes the source of truth both for what attaches to
|
|
96463
96502
|
* a session (A-5) and for the tools a screen can call.
|
|
96464
96503
|
*/
|
|
96465
|
-
async tools(ref, audience) {
|
|
96504
|
+
async tools(ref, audience, sessionId) {
|
|
96466
96505
|
const e = this.require(ref);
|
|
96467
|
-
const all = await this.use(e, async () => e.life.tools ?? []);
|
|
96506
|
+
const all = await this.use(e, async () => e.life.tools ?? [], !!sessionId && this.sessionBoundaryOpen(sessionId));
|
|
96468
96507
|
return all.filter((t) => !audience || t.visibility.includes(audience)).map((t) => t.tool);
|
|
96469
96508
|
}
|
|
96470
96509
|
/**
|
|
@@ -96596,7 +96635,8 @@ var ExternalApps = class {
|
|
|
96596
96635
|
return { runId, status, result, error: error51, durationMs };
|
|
96597
96636
|
};
|
|
96598
96637
|
if (!e.manifest) return done("rejected", null, `This app's manifest is invalid: ${e.error}`);
|
|
96599
|
-
|
|
96638
|
+
const open6 = caller.kind === "session" && this.sessionBoundaryOpen(caller.sessionId);
|
|
96639
|
+
if (!e.scope.trusted && !open6) return done("rejected", null, "This app's project is not trusted, so Centralu does not call its apps");
|
|
96600
96640
|
const held3 = this.held(e);
|
|
96601
96641
|
if (held3) return done("rejected", null, held3);
|
|
96602
96642
|
if (e.life.gaveUp) return done("rejected", null, `This app stopped after failing ${this.timing.maxFailures} times in a row`);
|
|
@@ -96644,7 +96684,7 @@ var ExternalApps = class {
|
|
|
96644
96684
|
abort.abort();
|
|
96645
96685
|
for (const sig of upstream) sig.removeEventListener("abort", onUp);
|
|
96646
96686
|
}
|
|
96647
|
-
});
|
|
96687
|
+
}, open6);
|
|
96648
96688
|
} catch (err) {
|
|
96649
96689
|
return done("error", null, err.message);
|
|
96650
96690
|
}
|
|
@@ -97248,11 +97288,11 @@ var ExternalApps = class {
|
|
|
97248
97288
|
}
|
|
97249
97289
|
// ── Lifecycle ──────────────────────────────────────────────────────────────────────
|
|
97250
97290
|
/** An app is never idle while in use — the idle timer is set again once it is done */
|
|
97251
|
-
async use(e, fn2) {
|
|
97291
|
+
async use(e, fn2, boundaryOpen = false) {
|
|
97252
97292
|
e.life.inflight += 1;
|
|
97253
97293
|
this.clearIdle(e);
|
|
97254
97294
|
try {
|
|
97255
|
-
return await fn2(await this.ensureRunning(e));
|
|
97295
|
+
return await fn2(await this.ensureRunning(e, boundaryOpen));
|
|
97256
97296
|
} finally {
|
|
97257
97297
|
e.life.inflight -= 1;
|
|
97258
97298
|
if (e.life.inflight === 0) for (const w of e.life.idleWaiters.splice(0)) w();
|
|
@@ -97266,14 +97306,14 @@ var ExternalApps = class {
|
|
|
97266
97306
|
*/
|
|
97267
97307
|
drain(e, ms3) {
|
|
97268
97308
|
if (e.life.inflight === 0) return Promise.resolve(true);
|
|
97269
|
-
return new Promise((
|
|
97309
|
+
return new Promise((resolve12) => {
|
|
97270
97310
|
const waiter = () => {
|
|
97271
97311
|
clearTimeout(t);
|
|
97272
|
-
|
|
97312
|
+
resolve12(true);
|
|
97273
97313
|
};
|
|
97274
97314
|
const t = setTimeout(() => {
|
|
97275
97315
|
e.life.idleWaiters = e.life.idleWaiters.filter((w) => w !== waiter);
|
|
97276
|
-
|
|
97316
|
+
resolve12(false);
|
|
97277
97317
|
}, ms3);
|
|
97278
97318
|
t.unref();
|
|
97279
97319
|
e.life.idleWaiters.push(waiter);
|
|
@@ -97287,10 +97327,10 @@ var ExternalApps = class {
|
|
|
97287
97327
|
* encountered restoring sessions (see the `resuming` comment in `manager.ts`). Everyone waits on the
|
|
97288
97328
|
* same in-progress startup promise instead.
|
|
97289
97329
|
*/
|
|
97290
|
-
ensureRunning(e) {
|
|
97330
|
+
ensureRunning(e, boundaryOpen = false) {
|
|
97291
97331
|
const L2 = e.life;
|
|
97292
97332
|
if (!e.manifest) throw new AppUnavailableError(`The app cannot start \u2014 its manifest is invalid: ${e.error}`);
|
|
97293
|
-
if (!e.scope.trusted) {
|
|
97333
|
+
if (!e.scope.trusted && !boundaryOpen) {
|
|
97294
97334
|
throw new AppUnavailableError("This app's project is not trusted, so the app does not start \u2014 trust the project and it will");
|
|
97295
97335
|
}
|
|
97296
97336
|
const held3 = this.held(e);
|
|
@@ -97955,9 +97995,9 @@ var InlineViews = class {
|
|
|
97955
97995
|
* just opened is never the one closed.
|
|
97956
97996
|
*/
|
|
97957
97997
|
capLive(sessionId, keep) {
|
|
97958
|
-
const
|
|
97998
|
+
const open6 = [...this.bySession.get(sessionId)?.values() ?? []].filter((x) => x.instanceId).sort((a, b2) => a.openedAt - b2.openedAt);
|
|
97959
97999
|
const n = this.limits.livePerSession;
|
|
97960
|
-
for (const old of
|
|
98000
|
+
for (const old of open6.slice(0, Math.max(0, open6.length - n))) {
|
|
97961
98001
|
if (old === keep) continue;
|
|
97962
98002
|
this.shut(old);
|
|
97963
98003
|
this.deps.emit({
|
|
@@ -98092,12 +98132,12 @@ function readBook(raw) {
|
|
|
98092
98132
|
return book;
|
|
98093
98133
|
}
|
|
98094
98134
|
function listenOn(port2, handler) {
|
|
98095
|
-
return new Promise((
|
|
98135
|
+
return new Promise((resolve12, reject) => {
|
|
98096
98136
|
const server2 = createServer2(handler);
|
|
98097
98137
|
server2.once("error", reject);
|
|
98098
98138
|
server2.listen(port2, "127.0.0.1", () => {
|
|
98099
98139
|
server2.removeListener("error", reject);
|
|
98100
|
-
|
|
98140
|
+
resolve12(server2);
|
|
98101
98141
|
});
|
|
98102
98142
|
});
|
|
98103
98143
|
}
|
|
@@ -98165,8 +98205,288 @@ var OriginPorts = class {
|
|
|
98165
98205
|
}
|
|
98166
98206
|
};
|
|
98167
98207
|
|
|
98208
|
+
// packages/agent-host/src/dev-services/worktree-recovery.ts
|
|
98209
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
98210
|
+
import { createReadStream, existsSync as existsSync13 } from "node:fs";
|
|
98211
|
+
import { chmod, lstat as lstat3, mkdir as mkdir2, mkdtemp, readFile, readdir as readdir2, realpath as realpath3, rm as rm2, stat as stat3, writeFile as writeFile2 } from "node:fs/promises";
|
|
98212
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
98213
|
+
import { join as join16, resolve as resolve2 } from "node:path";
|
|
98214
|
+
var WORKTREE_RECOVERY_MAX_BYTES = 256 * 1024 * 1024;
|
|
98215
|
+
var RECOVERY_TIMEOUT = 12e4;
|
|
98216
|
+
function worktreeRecoveryDir(sessionId) {
|
|
98217
|
+
if (!isSessionId(sessionId)) throw new Error("Invalid session id for worktree recovery");
|
|
98218
|
+
return join16(dataRoot(), "worktree-recovery", sessionId);
|
|
98219
|
+
}
|
|
98220
|
+
async function clearWorktreeRecovery(sessionId) {
|
|
98221
|
+
await rm2(worktreeRecoveryDir(sessionId), { recursive: true, force: true });
|
|
98222
|
+
}
|
|
98223
|
+
async function* gitRecords(repo, args, input) {
|
|
98224
|
+
const dir = await mkdtemp(join16(tmpdir2(), "cc-recovery-git-"));
|
|
98225
|
+
const file2 = join16(dir, "records");
|
|
98226
|
+
try {
|
|
98227
|
+
await runGit(repo, args, { outputFile: file2, timeout: RECOVERY_TIMEOUT, input });
|
|
98228
|
+
let pending = "";
|
|
98229
|
+
for await (const chunk of createReadStream(file2, { encoding: "utf8" })) {
|
|
98230
|
+
pending += chunk;
|
|
98231
|
+
let end;
|
|
98232
|
+
while ((end = pending.indexOf("\0")) !== -1) {
|
|
98233
|
+
yield pending.slice(0, end);
|
|
98234
|
+
pending = pending.slice(end + 1);
|
|
98235
|
+
}
|
|
98236
|
+
}
|
|
98237
|
+
if (pending) yield pending;
|
|
98238
|
+
} finally {
|
|
98239
|
+
await rm2(dir, { recursive: true, force: true });
|
|
98240
|
+
}
|
|
98241
|
+
}
|
|
98242
|
+
async function saveWorktreeRecovery(sessionId, cwd, trunk, branch) {
|
|
98243
|
+
const plans = [];
|
|
98244
|
+
let bytes2 = 0;
|
|
98245
|
+
const account = (size) => {
|
|
98246
|
+
bytes2 += size;
|
|
98247
|
+
if (bytes2 > WORKTREE_RECOVERY_MAX_BYTES) {
|
|
98248
|
+
throw new Error(`Worktree recovery exceeds the 256 MiB input limit (tracked and non-ignored untracked files plus staged changes, including submodules). No files were staged or removed; save the work elsewhere before deleting.`);
|
|
98249
|
+
}
|
|
98250
|
+
};
|
|
98251
|
+
const prepare = async (repo, path2, gitlinks = []) => {
|
|
98252
|
+
const tip = (await runGit(repo, ["rev-parse", "--verify", "HEAD"])).trim();
|
|
98253
|
+
const refs = ["HEAD"];
|
|
98254
|
+
if (!path2 && branch) {
|
|
98255
|
+
const ref = `refs/heads/${branch}`;
|
|
98256
|
+
if (await runGit(repo, ["rev-parse", "--verify", ref]).then(() => true).catch(() => false)) refs.push(ref);
|
|
98257
|
+
} else if (path2) {
|
|
98258
|
+
refs.push(...(await runGit(repo, ["for-each-ref", "--format=%(refname)", "refs/heads/"])).trim().split("\n").filter(Boolean));
|
|
98259
|
+
if (gitlinks.length) {
|
|
98260
|
+
const available = await runGit(repo, ["cat-file", "--batch-check=%(objectname) %(objecttype)"], {
|
|
98261
|
+
input: gitlinks.map((oid) => `${oid}^{commit}`).join("\n") + "\n",
|
|
98262
|
+
timeout: RECOVERY_TIMEOUT,
|
|
98263
|
+
// Older Git ignores the lazy-fetch switch; disallow transports there too.
|
|
98264
|
+
env: { GIT_NO_LAZY_FETCH: "1", GIT_ALLOW_PROTOCOL: "" }
|
|
98265
|
+
});
|
|
98266
|
+
for (const row of available.trim().split("\n")) {
|
|
98267
|
+
const [oid, type] = row.split(" ");
|
|
98268
|
+
if (type === "commit") refs.push(oid);
|
|
98269
|
+
}
|
|
98270
|
+
}
|
|
98271
|
+
}
|
|
98272
|
+
const baselineOid = !path2 ? await runGit(repo, ["rev-parse", "--verify", `${trunk}^{commit}`]).then((s) => s.trim()).catch(() => null) : null;
|
|
98273
|
+
const exclude = path2 ? (await runGit(repo, ["for-each-ref", "--format=^%(objectname)", "refs/remotes/"])).trim().split("\n").filter(Boolean) : baselineOid ? [`^${baselineOid}`] : [];
|
|
98274
|
+
const revisions = [...refs, ...exclude].join("\n") + "\n";
|
|
98275
|
+
const commits = Number((await runGit(repo, ["rev-list", "--count", "--stdin"], { input: revisions, timeout: RECOVERY_TIMEOUT })).trim());
|
|
98276
|
+
plans.push({ repo, path: path2, tip, refs, commits });
|
|
98277
|
+
const stagedNames = new Set((await runGit(repo, ["diff", "--cached", "--name-only", "-z", "--no-renames", tip], { timeout: RECOVERY_TIMEOUT })).split("\0"));
|
|
98278
|
+
const modules = [];
|
|
98279
|
+
const moduleCommits = /* @__PURE__ */ new Map();
|
|
98280
|
+
const keepGitlink = (rel, oid) => {
|
|
98281
|
+
modules.push(rel);
|
|
98282
|
+
const tips = moduleCommits.get(rel) ?? /* @__PURE__ */ new Set();
|
|
98283
|
+
tips.add(oid);
|
|
98284
|
+
moduleCommits.set(rel, tips);
|
|
98285
|
+
};
|
|
98286
|
+
const manifests = /* @__PURE__ */ new Set();
|
|
98287
|
+
const keepManifest = (rel, oid) => {
|
|
98288
|
+
if (rel === ".gitmodules" && !/^0+$/.test(oid)) manifests.add(oid);
|
|
98289
|
+
};
|
|
98290
|
+
let change = null;
|
|
98291
|
+
for await (const row of gitRecords(repo, ["log", "--raw", "-z", "--no-renames", "--ignore-submodules=none", "--no-abbrev", "--format=", "--root", "--diff-merges=separate", "--stdin"], revisions)) {
|
|
98292
|
+
if (change) {
|
|
98293
|
+
const [, oldMode, newMode, oldOid, newOid] = change;
|
|
98294
|
+
if (oldMode === "160000") keepGitlink(row, oldOid);
|
|
98295
|
+
if (newMode === "160000") keepGitlink(row, newOid);
|
|
98296
|
+
keepManifest(row, oldOid);
|
|
98297
|
+
keepManifest(row, newOid);
|
|
98298
|
+
change = null;
|
|
98299
|
+
} else change = row.match(/^\n*:(\d{6}) (\d{6}) ([a-f0-9]+) ([a-f0-9]+) [A-Z]$/);
|
|
98300
|
+
}
|
|
98301
|
+
for await (const row of gitRecords(repo, ["ls-tree", "-r", "-z", "HEAD"])) {
|
|
98302
|
+
const tab = row.indexOf(" ");
|
|
98303
|
+
const [mode, , oid] = row.slice(0, tab).split(" ");
|
|
98304
|
+
const rel = row.slice(tab + 1);
|
|
98305
|
+
if (mode === "160000") keepGitlink(rel, oid);
|
|
98306
|
+
keepManifest(rel, oid);
|
|
98307
|
+
}
|
|
98308
|
+
const stagedObjects = [];
|
|
98309
|
+
for await (const row of gitRecords(repo, ["ls-files", "--stage", "-z"])) {
|
|
98310
|
+
if (!row) continue;
|
|
98311
|
+
const tab = row.indexOf(" ");
|
|
98312
|
+
const rel = row.slice(tab + 1);
|
|
98313
|
+
const oid = row.split(" ")[1];
|
|
98314
|
+
if (row.startsWith("160000 ")) keepGitlink(rel, oid);
|
|
98315
|
+
else if (stagedNames.has(rel)) stagedObjects.push(oid);
|
|
98316
|
+
keepManifest(rel, oid);
|
|
98317
|
+
}
|
|
98318
|
+
const modulePaths = /* @__PURE__ */ new Map();
|
|
98319
|
+
for (const oid of manifests) {
|
|
98320
|
+
const config2 = await runGit(repo, ["config", "--blob", oid, "-z", "--get-regexp", "^submodule\\..*\\.path$"]).catch((e) => {
|
|
98321
|
+
if (e.code === 1) return "";
|
|
98322
|
+
throw e;
|
|
98323
|
+
});
|
|
98324
|
+
for (const row of config2.split("\0").filter(Boolean)) {
|
|
98325
|
+
const newline = row.indexOf("\n");
|
|
98326
|
+
const name = row.slice("submodule.".length, newline - ".path".length);
|
|
98327
|
+
const paths = modulePaths.get(name) ?? /* @__PURE__ */ new Set();
|
|
98328
|
+
paths.add(row.slice(newline + 1));
|
|
98329
|
+
modulePaths.set(name, paths);
|
|
98330
|
+
}
|
|
98331
|
+
}
|
|
98332
|
+
for (const paths of modulePaths.values()) {
|
|
98333
|
+
const tips = new Set([...paths].flatMap((rel) => [...moduleCommits.get(rel) ?? []]));
|
|
98334
|
+
for (const rel of paths) {
|
|
98335
|
+
modules.push(rel);
|
|
98336
|
+
moduleCommits.set(rel, /* @__PURE__ */ new Set([...moduleCommits.get(rel) ?? [], ...tips]));
|
|
98337
|
+
}
|
|
98338
|
+
}
|
|
98339
|
+
if (stagedObjects.length) {
|
|
98340
|
+
const sizes = await runGit(repo, ["cat-file", "--batch-check=%(objectsize)"], { input: stagedObjects.join("\n") + "\n" });
|
|
98341
|
+
for (const size of sizes.trim().split("\n")) {
|
|
98342
|
+
if (!/^\d+$/.test(size)) throw new Error("Could not measure staged recovery data");
|
|
98343
|
+
account(Number(size));
|
|
98344
|
+
}
|
|
98345
|
+
}
|
|
98346
|
+
const files = new Set((await runGit(repo, ["ls-files", "--cached", "--others", "--exclude-standard", "-z"])).split("\0").filter(Boolean));
|
|
98347
|
+
for (const rel of files) {
|
|
98348
|
+
const file2 = await lstat3(join16(repo, rel)).catch((e) => {
|
|
98349
|
+
if (e.code !== "ENOENT") throw e;
|
|
98350
|
+
return null;
|
|
98351
|
+
});
|
|
98352
|
+
if (file2?.isFile() || file2?.isSymbolicLink()) account(file2.size);
|
|
98353
|
+
else if (file2?.isDirectory() && existsSync13(join16(repo, rel, ".git"))) modules.push(rel);
|
|
98354
|
+
}
|
|
98355
|
+
for (const rel of new Set(modules)) {
|
|
98356
|
+
await assertExistingPath(repo, rel).catch((e) => {
|
|
98357
|
+
if (existsSync13(join16(repo, rel))) throw e;
|
|
98358
|
+
});
|
|
98359
|
+
if (existsSync13(join16(repo, rel, ".git"))) await prepare(join16(repo, rel), path2 ? `${path2}/${rel}` : rel, [...moduleCommits.get(rel) ?? []]);
|
|
98360
|
+
}
|
|
98361
|
+
};
|
|
98362
|
+
await prepare(cwd, "");
|
|
98363
|
+
const snapshotId = randomUUID6();
|
|
98364
|
+
const dir = join16(worktreeRecoveryDir(sessionId), snapshotId);
|
|
98365
|
+
await mkdir2(dir, { recursive: true, mode: 448 });
|
|
98366
|
+
const snapshot2 = { snapshot: snapshotId, tip: plans[0].tip, changedFiles: 0, commits: 0, repositories: [] };
|
|
98367
|
+
const patchFile = async (repo, tip, file2, env) => {
|
|
98368
|
+
await runGit(repo, ["diff", "--cached", "--binary", "--full-index", "--no-renames", `--output=${file2}`, tip], { env, timeout: RECOVERY_TIMEOUT });
|
|
98369
|
+
await chmod(file2, 384);
|
|
98370
|
+
if ((await stat3(file2)).size > 0) return true;
|
|
98371
|
+
await rm2(file2);
|
|
98372
|
+
return false;
|
|
98373
|
+
};
|
|
98374
|
+
try {
|
|
98375
|
+
for (const [n, plan] of plans.entries()) {
|
|
98376
|
+
const { repo, path: path2, tip, refs, commits } = plan;
|
|
98377
|
+
const entry = { path: path2, tip, refs, patch: false, staged: false, bundle: commits > 0 };
|
|
98378
|
+
snapshot2.repositories.push(entry);
|
|
98379
|
+
snapshot2.commits += commits;
|
|
98380
|
+
if (entry.bundle) {
|
|
98381
|
+
const temporaryRefs = [];
|
|
98382
|
+
const updates2 = [];
|
|
98383
|
+
try {
|
|
98384
|
+
entry.refs = [];
|
|
98385
|
+
for (const ref of refs) {
|
|
98386
|
+
if (ref === "HEAD" || ref.startsWith("refs/")) entry.refs.push(ref);
|
|
98387
|
+
else {
|
|
98388
|
+
const name = `refs/centralu-recovery/${snapshotId}/gitlink-${temporaryRefs.length}`;
|
|
98389
|
+
temporaryRefs.push(name);
|
|
98390
|
+
updates2.push(`create ${name} ${ref}`);
|
|
98391
|
+
entry.refs.push(name);
|
|
98392
|
+
}
|
|
98393
|
+
}
|
|
98394
|
+
if (updates2.length) await runGit(repo, ["update-ref", "--stdin"], { input: updates2.join("\n") + "\n", timeout: RECOVERY_TIMEOUT });
|
|
98395
|
+
await runGit(repo, ["bundle", "create", join16(dir, `${n}.bundle`), "--stdin"], { input: entry.refs.join("\n") + "\n", timeout: RECOVERY_TIMEOUT });
|
|
98396
|
+
} finally {
|
|
98397
|
+
if (temporaryRefs.length) await runGit(repo, ["update-ref", "--stdin"], { input: temporaryRefs.map((ref) => `delete ${ref}`).join("\n") + "\n", timeout: RECOVERY_TIMEOUT });
|
|
98398
|
+
}
|
|
98399
|
+
}
|
|
98400
|
+
entry.staged = await patchFile(repo, tip, join16(dir, `${n}.staged.patch`));
|
|
98401
|
+
const stagedNames = await runGit(repo, ["diff", "--cached", "--name-only", "-z", "--no-renames", tip], { timeout: RECOVERY_TIMEOUT });
|
|
98402
|
+
const env = { GIT_INDEX_FILE: join16(dir, `${n}.index`) };
|
|
98403
|
+
try {
|
|
98404
|
+
await runGit(repo, ["read-tree", tip], { env });
|
|
98405
|
+
await runGit(repo, ["add", "-A", "--", "."], { env, timeout: RECOVERY_TIMEOUT });
|
|
98406
|
+
const names = await runGit(repo, ["diff", "--cached", "--name-only", "-z", "--no-renames", tip], { env, timeout: RECOVERY_TIMEOUT });
|
|
98407
|
+
snapshot2.changedFiles += new Set([...names.split("\0"), ...stagedNames.split("\0")].filter(Boolean)).size;
|
|
98408
|
+
entry.patch = await patchFile(repo, tip, join16(dir, `${n}.patch`), env);
|
|
98409
|
+
} finally {
|
|
98410
|
+
await rm2(env.GIT_INDEX_FILE, { force: true });
|
|
98411
|
+
}
|
|
98412
|
+
}
|
|
98413
|
+
await writeFile2(join16(dir, "snapshot.json"), JSON.stringify(snapshot2), { mode: 384 });
|
|
98414
|
+
return { snapshot: snapshotId, tip: snapshot2.tip, changedFiles: snapshot2.changedFiles, commits: snapshot2.commits };
|
|
98415
|
+
} catch (e) {
|
|
98416
|
+
await rm2(dir, { recursive: true, force: true });
|
|
98417
|
+
throw e;
|
|
98418
|
+
}
|
|
98419
|
+
}
|
|
98420
|
+
async function worktreeRecoveryBytes(sessionId) {
|
|
98421
|
+
const dir = worktreeRecoveryDir(sessionId);
|
|
98422
|
+
if (!existsSync13(dir)) return 0;
|
|
98423
|
+
const sizes = await Promise.all((await readdir2(dir, { recursive: true })).map(async (name) => {
|
|
98424
|
+
const s = await stat3(join16(dir, name));
|
|
98425
|
+
return s.isFile() ? s.size : 0;
|
|
98426
|
+
}));
|
|
98427
|
+
return sizes.reduce((a, b2) => a + b2, 0);
|
|
98428
|
+
}
|
|
98429
|
+
async function restoreWorktreeRecovery(sessionId, repo, path2, branch, recovery, trust) {
|
|
98430
|
+
if (!isSessionId(recovery.snapshot)) throw new Error("Invalid recovery snapshot");
|
|
98431
|
+
const dir = join16(worktreeRecoveryDir(sessionId), recovery.snapshot);
|
|
98432
|
+
const snapshot2 = JSON.parse(await readFile(join16(dir, "snapshot.json"), "utf8"));
|
|
98433
|
+
const root3 = snapshot2.repositories[0];
|
|
98434
|
+
let extraNotice = "";
|
|
98435
|
+
if (root3.bundle) {
|
|
98436
|
+
const heads = (await runGit(repo, ["bundle", "list-heads", join16(dir, "0.bundle")])).trim().split("\n");
|
|
98437
|
+
for (const row of heads) {
|
|
98438
|
+
const [tip, ref] = row.split(" ");
|
|
98439
|
+
if (!tip || !ref?.startsWith("refs/heads/") || tip === recovery.tip) continue;
|
|
98440
|
+
const base2 = `recovered/${sessionId}-stored-branch`;
|
|
98441
|
+
let recovered = base2;
|
|
98442
|
+
for (let suffix = 1; ; suffix++) {
|
|
98443
|
+
const existing = await runGit(repo, ["rev-parse", "--verify", `refs/heads/${recovered}`]).then((s) => s.trim()).catch(() => null);
|
|
98444
|
+
if (existing === tip) break;
|
|
98445
|
+
if (!existing) {
|
|
98446
|
+
await runGit(repo, ["fetch", join16(dir, "0.bundle"), `${ref}:refs/heads/${recovered}`], { allowRepoPrograms: trust.trusted === true });
|
|
98447
|
+
break;
|
|
98448
|
+
}
|
|
98449
|
+
recovered = `${base2}-${suffix}`;
|
|
98450
|
+
}
|
|
98451
|
+
extraNotice += `Stored branch ${ref.slice("refs/heads/".length)} recovered as ${recovered}. `;
|
|
98452
|
+
}
|
|
98453
|
+
}
|
|
98454
|
+
if (!existsSync13(path2)) {
|
|
98455
|
+
await rm2(join16(dir, "patches-applied"), { force: true });
|
|
98456
|
+
if (await runGit(repo, ["show-ref", "--verify", `refs/heads/${branch}`]).then(() => true).catch(() => false)) {
|
|
98457
|
+
branch = `recovered/${sessionId}`;
|
|
98458
|
+
}
|
|
98459
|
+
if (root3.bundle) await runGit(repo, ["fetch", join16(dir, "0.bundle"), ...root3.refs ?? ["HEAD"]], { allowRepoPrograms: trust.trusted === true });
|
|
98460
|
+
await runGit(repo, ["worktree", "add", "-b", branch, path2, recovery.tip], { allowRepoPrograms: trust.trusted === true });
|
|
98461
|
+
if (root3.patch || root3.staged) {
|
|
98462
|
+
try {
|
|
98463
|
+
if (root3.staged) await runGit(path2, ["apply", "--cached", "--binary", join16(dir, "0.staged.patch")]);
|
|
98464
|
+
if (root3.patch) await runGit(path2, ["apply", "--binary", join16(dir, "0.patch")]);
|
|
98465
|
+
await writeFile2(join16(dir, "patches-applied"), await realpath3(path2), { mode: 384 });
|
|
98466
|
+
} catch {
|
|
98467
|
+
return { branch, notice: `Restored branch ${branch}. ${extraNotice}Patches could not all be applied. Recovery files remain at ${dir}; apply numbered .patch files manually with git apply and .staged.patch files with git apply --cached.` };
|
|
98468
|
+
}
|
|
98469
|
+
}
|
|
98470
|
+
} else {
|
|
98471
|
+
const common = async (cwd) => realpath3(resolve2(cwd, (await runGit(cwd, ["rev-parse", "--git-common-dir"])).trim()));
|
|
98472
|
+
if (await common(path2) !== await common(repo)) throw new Error(`The worktree path belongs to another repository. Recovery files remain at ${dir}`);
|
|
98473
|
+
branch = (await runGit(path2, ["symbolic-ref", "--quiet", "--short", "HEAD"])).trim();
|
|
98474
|
+
if (root3.patch || root3.staged) {
|
|
98475
|
+
const appliedTo = await readFile(join16(dir, "patches-applied"), "utf8").catch((e) => {
|
|
98476
|
+
if (e.code === "ENOENT") return null;
|
|
98477
|
+
throw e;
|
|
98478
|
+
});
|
|
98479
|
+
extraNotice += appliedTo === await realpath3(path2) ? "Saved patches were applied during the previous restore attempt; they were not reapplied on this retry. " : "The worktree already existed; saved patches were not applied on this retry and may still need manual recovery. ";
|
|
98480
|
+
}
|
|
98481
|
+
}
|
|
98482
|
+
return {
|
|
98483
|
+
branch,
|
|
98484
|
+
notice: `Restored branch ${branch}. ${extraNotice}Recovery files remain at ${dir}: ${recovery.changedFiles} uncommitted files, ${recovery.commits} commits absent from the trunk or submodule remotes. Numbered .patch files can be applied with git apply; .bundle files list their saved refs with git bundle list-heads and can be fetched with git fetch <bundle> <ref>:<branch>. snapshot.json names each repository and its refs. Submodule recovery is manual. These files are removed when this session is deleted for good.`
|
|
98485
|
+
};
|
|
98486
|
+
}
|
|
98487
|
+
|
|
98168
98488
|
// packages/agent-host/src/sessions/manager.ts
|
|
98169
|
-
import { randomUUID as
|
|
98489
|
+
import { randomUUID as randomUUID10 } from "node:crypto";
|
|
98170
98490
|
|
|
98171
98491
|
// packages/agent-host/src/sessions/tool-cost.ts
|
|
98172
98492
|
import { createHash as createHash8 } from "node:crypto";
|
|
@@ -98254,8 +98574,12 @@ and approved skills, and whether every session can look at its own project), App
|
|
|
98254
98574
|
Shortcuts (the shortcut list), and Updates.`,
|
|
98255
98575
|
updates: `# Updates
|
|
98256
98576
|
Check from Settings \u2192 Updates. It reports a new version based on the npm registry, and the person
|
|
98257
|
-
clicks to install it.
|
|
98258
|
-
|
|
98577
|
+
clicks to install it. With automatic checking on, the check runs on startup, every six hours, when
|
|
98578
|
+
Updates opens, and on focus when the last check is over an hour old. With it off, use Check now.
|
|
98579
|
+
Ordinary checks follow npm, including dist-tag rollbacks. Installing reuses a successful check
|
|
98580
|
+
from the last minute or checks again. Only while installing can a higher stored version seen less
|
|
98581
|
+
than an hour ago win over the fresh tag. Older observations yield to npm. If the check fails, it
|
|
98582
|
+
uses the stored version and says so. Checks preserve install errors until the next install attempt.
|
|
98259
98583
|
Once installed, the desktop app offers "Apply now": it relaunches into the new version, and running
|
|
98260
98584
|
agents, terminals and commands keep going. "Apply updates automatically when idle" (off by default)
|
|
98261
98585
|
installs a new version without a click and applies it once no session is working or waiting, no
|
|
@@ -98484,8 +98808,8 @@ var READER_TOOLS = [
|
|
|
98484
98808
|
var DELEGATE_TOOLS = [
|
|
98485
98809
|
{
|
|
98486
98810
|
name: "ask_project",
|
|
98487
|
-
description: "
|
|
98488
|
-
schema: external_exports.object({ project: external_exports.string(), task: external_exports.string().optional() })
|
|
98811
|
+
description: "Asks another project's existing session; returns its answer. For another project's apps, use find_apps.",
|
|
98812
|
+
schema: external_exports.object({ project: external_exports.string(), session: external_exports.string().optional(), task: external_exports.string().optional() })
|
|
98489
98813
|
}
|
|
98490
98814
|
];
|
|
98491
98815
|
var DELEGATE_BUDGET_CHARS = 400;
|
|
@@ -98609,7 +98933,7 @@ function askProjectText(r) {
|
|
|
98609
98933
|
const where = `the session "${r.sessionName}" [${r.sessionId}] in ${r.project}`;
|
|
98610
98934
|
if (r.state === "working") {
|
|
98611
98935
|
return {
|
|
98612
|
-
text: `${r.project} is still working on it in ${where}${r.notice ? ` \u2014 ${r.notice}` : ""}. To keep waiting, call ask_project again with project "${r.project}" and no task; the work goes on meanwhile.`
|
|
98936
|
+
text: `${r.project} is still working on it in ${where}${r.notice ? ` \u2014 ${r.notice}` : ""}. To keep waiting, call ask_project again with project "${r.project}", session "${r.sessionId}" and no task; the work goes on meanwhile.`
|
|
98613
98937
|
};
|
|
98614
98938
|
}
|
|
98615
98939
|
const lines = [`${r.project} answered (from ${where}), as JSON:`, trustedJsonText(r.answer || "(no final text)")];
|
|
@@ -98628,8 +98952,9 @@ function agentRunText(r) {
|
|
|
98628
98952
|
case "stopped":
|
|
98629
98953
|
return { text: `Stopped ${who}.` };
|
|
98630
98954
|
case "deleted":
|
|
98955
|
+
if (r.branch === void 0) return { text: `Moved ${who} to the trash. Tell the person in one line.` };
|
|
98631
98956
|
return {
|
|
98632
|
-
text: `
|
|
98957
|
+
text: (r.warning ? `Moved ${who} to the trash. Warning: ${r.warning} ` : `Cleaned up ${who}: its worktree and branch "${r.branch}" were removed and the session went to the trash. `) + (r.setAside.changedFiles || r.setAside.commits ? `${r.setAside.changedFiles} uncommitted files, ${r.setAside.commits} commits not on the trunk, kept in the trash. ` : "Nothing needed setting aside. ") + "Tell the person in one line."
|
|
98633
98958
|
};
|
|
98634
98959
|
case "at_rest":
|
|
98635
98960
|
return {
|
|
@@ -98665,7 +98990,8 @@ async function runOrchestratorTool(tools, name, args, caller = { sessionId: null
|
|
|
98665
98990
|
const project2 = typeof args.project === "string" ? args.project.trim() : "";
|
|
98666
98991
|
if (!project2) return { text: "Give a project \u2014 the name of another Centralu project.", isError: true };
|
|
98667
98992
|
const task = typeof args.task === "string" && args.task.trim() ? args.task : void 0;
|
|
98668
|
-
|
|
98993
|
+
const session2 = typeof args.session === "string" && args.session.trim() ? args.session.trim() : void 0;
|
|
98994
|
+
return askProjectText(await tools.askProject({ project: project2, ...session2 ? { session: session2 } : {}, ...task ? { task } : {} }, signal));
|
|
98669
98995
|
}
|
|
98670
98996
|
if (name === "start_agent" || name === "agent_session") {
|
|
98671
98997
|
if (caller.profile !== "reader") return { text: `Not a tool of this session: ${name}`, isError: true };
|
|
@@ -99077,7 +99403,7 @@ function shortHash(value) {
|
|
|
99077
99403
|
|
|
99078
99404
|
// packages/agent-host/src/sessions/orchestrator-home.ts
|
|
99079
99405
|
import { mkdirSync as mkdirSync7 } from "node:fs";
|
|
99080
|
-
import { join as
|
|
99406
|
+
import { join as join17 } from "node:path";
|
|
99081
99407
|
var preamble = true;
|
|
99082
99408
|
function setOrchestratorPreamble(on2) {
|
|
99083
99409
|
preamble = on2;
|
|
@@ -99093,7 +99419,7 @@ function orchestratorRole() {
|
|
|
99093
99419
|
${ORCHESTRATOR_ROLE}` : ORCHESTRATOR_ROLE;
|
|
99094
99420
|
}
|
|
99095
99421
|
function orchestratorHome() {
|
|
99096
|
-
const dir =
|
|
99422
|
+
const dir = join17(dataRoot(), "orchestrator");
|
|
99097
99423
|
mkdirSync7(dir, { recursive: true });
|
|
99098
99424
|
return dir;
|
|
99099
99425
|
}
|
|
@@ -99641,7 +99967,7 @@ var SessionAppsHub = class {
|
|
|
99641
99967
|
* keep answering "yes" even after trust is revoked.
|
|
99642
99968
|
*/
|
|
99643
99969
|
refsFor(session2) {
|
|
99644
|
-
const usable = this.rt.list().filter((a) => !UNUSABLE.has(a.status));
|
|
99970
|
+
const usable = this.rt.list().filter((a) => !UNUSABLE.has(a.status) || a.status === "untrusted" && !!this.opts.boundaryOpen?.(session2.id));
|
|
99645
99971
|
const hits = usable.filter((a) => givesApp(session2, a)).map((a) => ({ ref: { projectId: a.projectId, appId: a.appId }, server: appMcpServerName(a.appId) })).sort((x, y) => x.server.localeCompare(y.server));
|
|
99646
99972
|
const taken = new Set(hits.map((h) => h.server));
|
|
99647
99973
|
for (const x of this.opts.onDemand?.attached(session2.id) ?? []) {
|
|
@@ -99668,8 +99994,8 @@ var SessionAppsHub = class {
|
|
|
99668
99994
|
const own = session2?.id ? this.opts.onDemand?.attached(session2.id).find((x) => sameRef(x.ref, ref)) : void 0;
|
|
99669
99995
|
const onDemand = !!session2 && !!own && this.opts.onDemand.allowed(session2, app2);
|
|
99670
99996
|
if (!session2 || !givesApp(session2, app2) && !onDemand) return { reachable: false, reason: "other-project" };
|
|
99671
|
-
if (app2.status === "untrusted") return { reachable: false, reason: "untrusted" };
|
|
99672
|
-
if (UNUSABLE.has(app2.status)) return { reachable: false, reason: "app-unusable", status: app2.status };
|
|
99997
|
+
if (app2.status === "untrusted" && !this.opts.boundaryOpen?.(session2.id)) return { reachable: false, reason: "untrusted" };
|
|
99998
|
+
if (UNUSABLE.has(app2.status) && !(app2.status === "untrusted" && this.opts.boundaryOpen?.(session2.id))) return { reachable: false, reason: "app-unusable", status: app2.status };
|
|
99673
99999
|
const server2 = givesApp(session2, app2) ? appMcpServerName(app2.appId) : own.server;
|
|
99674
100000
|
const attached = live?.appAttachment?.(server2) ?? "attached";
|
|
99675
100001
|
if (attached === "restart") return { reachable: false, reason: "restart" };
|
|
@@ -99751,7 +100077,7 @@ var Attachment2 = class {
|
|
|
99751
100077
|
let timer;
|
|
99752
100078
|
try {
|
|
99753
100079
|
const listed = await Promise.race([
|
|
99754
|
-
this.hub.rt.tools(hit.ref, "model"),
|
|
100080
|
+
this.hub.rt.tools(hit.ref, "model", this.session.id),
|
|
99755
100081
|
new Promise((_, reject) => {
|
|
99756
100082
|
timer = setTimeout(() => reject(new Error(`did not list its tools within ${Math.round(waitMs / 1e3)}s`)), waitMs);
|
|
99757
100083
|
})
|
|
@@ -99796,7 +100122,7 @@ var Attachment2 = class {
|
|
|
99796
100122
|
let timer;
|
|
99797
100123
|
const first = await Promise.race([
|
|
99798
100124
|
pending.then((o) => ({ o })),
|
|
99799
|
-
new Promise((
|
|
100125
|
+
new Promise((resolve12) => timer = setTimeout(() => resolve12(null), opts.waitMs))
|
|
99800
100126
|
]);
|
|
99801
100127
|
clearTimeout(timer);
|
|
99802
100128
|
if (first) return toResult(first.o);
|
|
@@ -99902,15 +100228,15 @@ The call has not stopped and carries on. Do not call it again: pass this id as r
|
|
|
99902
100228
|
if (i !== -1) return Promise.resolve(this.noted.splice(i, 1)[0].callId);
|
|
99903
100229
|
if (this.closed) return Promise.resolve(null);
|
|
99904
100230
|
const waitMs = this.hub.opts.callJoinWaitMs ?? CALL_JOIN_WAIT_MS;
|
|
99905
|
-
return new Promise((
|
|
100231
|
+
return new Promise((resolve12) => {
|
|
99906
100232
|
const waiter = {
|
|
99907
100233
|
server: server2,
|
|
99908
100234
|
tool,
|
|
99909
100235
|
args: key,
|
|
99910
|
-
resolve:
|
|
100236
|
+
resolve: resolve12,
|
|
99911
100237
|
timer: setTimeout(() => {
|
|
99912
100238
|
this.waiters = this.waiters.filter((x) => x !== waiter);
|
|
99913
|
-
|
|
100239
|
+
resolve12(null);
|
|
99914
100240
|
}, waitMs)
|
|
99915
100241
|
};
|
|
99916
100242
|
waiter.timer.unref?.();
|
|
@@ -100021,12 +100347,12 @@ var AppAccess = class {
|
|
|
100021
100347
|
}
|
|
100022
100348
|
allowed(session2, app2) {
|
|
100023
100349
|
const from = session2.projectId;
|
|
100024
|
-
if (from === null || !this.projectTrusted(from)) return false;
|
|
100350
|
+
if (from === null || !this.projectTrusted(from) && !this.deps.boundaryOpen?.(session2.id)) return false;
|
|
100025
100351
|
if (app2.projectId === null) return true;
|
|
100026
100352
|
if (app2.projectId === from || app2.shared !== true) return false;
|
|
100027
100353
|
const r = this.records(session2.id).find((x) => x.appId === app2.appId && x.projectId === app2.projectId);
|
|
100028
100354
|
if (!r) return false;
|
|
100029
|
-
return r.consent === "once" || !!this.deps.store.getProjectConsent(from, app2.projectId, "apps");
|
|
100355
|
+
return !!this.deps.boundaryOpen?.(session2.id) || r.consent === "once" || !!this.deps.store.getProjectConsent(from, app2.projectId, "apps");
|
|
100030
100356
|
}
|
|
100031
100357
|
// ── The three tools ──
|
|
100032
100358
|
find(sessionId, query) {
|
|
@@ -100049,7 +100375,7 @@ var AppAccess = class {
|
|
|
100049
100375
|
const app2 = named.app;
|
|
100050
100376
|
const target = { projectId: app2.projectId, appId: app2.appId };
|
|
100051
100377
|
const has = already.find((h) => h.ref.appId === target.appId && h.ref.projectId === target.projectId);
|
|
100052
|
-
if (has) return { ok: true, server: has.server, tools: await this.toolNames(target), when: "now", already: true };
|
|
100378
|
+
if (has) return { ok: true, server: has.server, tools: await this.toolNames(target, sessionId), when: "now", already: true };
|
|
100053
100379
|
if (!this.candidates(session2).some((a) => a.appId === app2.appId && a.projectId === app2.projectId)) {
|
|
100054
100380
|
return { ok: false, error: this.whyNot(session2, app2) };
|
|
100055
100381
|
}
|
|
@@ -100062,7 +100388,7 @@ var AppAccess = class {
|
|
|
100062
100388
|
signal
|
|
100063
100389
|
);
|
|
100064
100390
|
if (!res.ok) return res;
|
|
100065
|
-
consent = this.deps.store.getProjectConsent(session2.projectId, app2.projectId, "apps") ? "always" : "once";
|
|
100391
|
+
consent = this.deps.boundaryOpen?.(sessionId) ? "auto" : this.deps.store.getProjectConsent(session2.projectId, app2.projectId, "apps") ? "always" : "once";
|
|
100066
100392
|
const again = this.gate(sessionId);
|
|
100067
100393
|
if (!again.ok) return again;
|
|
100068
100394
|
if (!this.candidates(again.session).some((a) => a.appId === app2.appId && a.projectId === app2.projectId)) {
|
|
@@ -100074,7 +100400,7 @@ var AppAccess = class {
|
|
|
100074
100400
|
const server2 = this.freeServer(app2.appId, taken);
|
|
100075
100401
|
this.save(sessionId, [...kept, { projectId: app2.projectId, appId: app2.appId, server: server2, consent, at: Date.now() }]);
|
|
100076
100402
|
const when = this.deps.attachedChanged(sessionId);
|
|
100077
|
-
return { ok: true, server: server2, tools: await this.toolNames(target), when };
|
|
100403
|
+
return { ok: true, server: server2, tools: await this.toolNames(target, sessionId), when };
|
|
100078
100404
|
}
|
|
100079
100405
|
detach(sessionId, ref) {
|
|
100080
100406
|
const mine = this.records(sessionId);
|
|
@@ -100118,7 +100444,7 @@ var AppAccess = class {
|
|
|
100118
100444
|
const session2 = this.deps.session(sessionId);
|
|
100119
100445
|
if (!session2) return { ok: false, error: "This session cannot attach apps." };
|
|
100120
100446
|
if (session2.projectId === null) return { ok: false, error: "Only a session in a project can attach apps from other projects." };
|
|
100121
|
-
if (!this.projectTrusted(session2.projectId)) {
|
|
100447
|
+
if (!this.projectTrusted(session2.projectId) && !this.deps.boundaryOpen?.(sessionId, "attach from an untrusted project")) {
|
|
100122
100448
|
return { ok: false, error: "This session's project is not trusted, so it cannot attach apps \u2014 the person can trust it from the project menu." };
|
|
100123
100449
|
}
|
|
100124
100450
|
return { ok: true, session: session2 };
|
|
@@ -100126,7 +100452,7 @@ var AppAccess = class {
|
|
|
100126
100452
|
/** What this session could attach now: shared apps of other trusted projects, and user-folder apps, minus what it has */
|
|
100127
100453
|
candidates(session2) {
|
|
100128
100454
|
const have = this.deps.hub.refsFor(session2);
|
|
100129
|
-
return this.deps.hub.rt.list().filter((a) => !UNUSABLE2.has(a.status)).filter((a) => a.projectId === null || a.projectId !== session2.projectId && a.shared === true).filter((a) => !have.some((h) => h.ref.appId === a.appId && h.ref.projectId === a.projectId)).sort((x, y) => this.refOf(x).localeCompare(this.refOf(y)));
|
|
100455
|
+
return this.deps.hub.rt.list().filter((a) => !UNUSABLE2.has(a.status) || a.status === "untrusted" && this.deps.boundaryOpen?.(session2.id)).filter((a) => a.projectId === null || a.projectId !== session2.projectId && a.shared === true).filter((a) => !have.some((h) => h.ref.appId === a.appId && h.ref.projectId === a.projectId)).sort((x, y) => this.refOf(x).localeCompare(this.refOf(y)));
|
|
100130
100456
|
}
|
|
100131
100457
|
whyNot(session2, app2) {
|
|
100132
100458
|
if (app2.projectId !== null && app2.projectId === session2.projectId) return `${this.refOf(app2)} is this project's own app; it is attached already when the project is trusted.`;
|
|
@@ -100182,13 +100508,13 @@ var AppAccess = class {
|
|
|
100182
100508
|
for (let n = 2; ; n++) if (!taken.includes(`${base2}-${n}`)) return `${base2}-${n}`;
|
|
100183
100509
|
}
|
|
100184
100510
|
/** The app's agent tools for the tool result — started here if never listed, within the hub's cap; empty on failure */
|
|
100185
|
-
async toolNames(ref) {
|
|
100511
|
+
async toolNames(ref, sessionId) {
|
|
100186
100512
|
const known = this.deps.hub.rt.knownTools(ref, "model");
|
|
100187
100513
|
if (known) return known.map((t) => t.name);
|
|
100188
100514
|
let timer;
|
|
100189
100515
|
try {
|
|
100190
100516
|
const listed = await Promise.race([
|
|
100191
|
-
this.deps.hub.rt.tools(ref, "model"),
|
|
100517
|
+
this.deps.hub.rt.tools(ref, "model", sessionId),
|
|
100192
100518
|
new Promise((_, reject) => timer = setTimeout(() => reject(new Error("timeout")), this.deps.toolListWaitMs ?? TOOL_LIST_WAIT_MS2))
|
|
100193
100519
|
]);
|
|
100194
100520
|
return listed.map((t) => t.name);
|
|
@@ -100222,7 +100548,7 @@ function parse3(raw) {
|
|
|
100222
100548
|
const v = JSON.parse(raw);
|
|
100223
100549
|
if (!Array.isArray(v)) return [];
|
|
100224
100550
|
return v.filter(
|
|
100225
|
-
(x) => !!x && typeof x === "object" && (typeof x.projectId === "string" || x.projectId === null) && typeof x.appId === "string" && typeof x.server === "string" && x.server.startsWith(appMcpServerName("")) && (x.consent === "always" || x.consent === "once" || x.consent === "none")
|
|
100551
|
+
(x) => !!x && typeof x === "object" && (typeof x.projectId === "string" || x.projectId === null) && typeof x.appId === "string" && typeof x.server === "string" && x.server.startsWith(appMcpServerName("")) && (x.consent === "always" || x.consent === "once" || x.consent === "none" || x.consent === "auto")
|
|
100226
100552
|
);
|
|
100227
100553
|
} catch {
|
|
100228
100554
|
return [];
|
|
@@ -100250,7 +100576,7 @@ var AgentRunWait = class {
|
|
|
100250
100576
|
this.sessionName = sessionName;
|
|
100251
100577
|
this.onUsage = onUsage;
|
|
100252
100578
|
this.readAnswer = readAnswer2;
|
|
100253
|
-
this.done = new Promise((
|
|
100579
|
+
this.done = new Promise((resolve12, reject) => this.settle = { resolve: resolve12, reject });
|
|
100254
100580
|
this.done.catch(() => {
|
|
100255
100581
|
});
|
|
100256
100582
|
}
|
|
@@ -100465,7 +100791,7 @@ function windowOf(list, id2, label, since, writes, nameOf) {
|
|
|
100465
100791
|
|
|
100466
100792
|
// packages/agent-host/src/sessions/ask-project.ts
|
|
100467
100793
|
import { realpathSync as realpathSync3, statSync as statSync8 } from "node:fs";
|
|
100468
|
-
import { isAbsolute as isAbsolute4, relative as relative4, resolve as
|
|
100794
|
+
import { isAbsolute as isAbsolute4, relative as relative4, resolve as resolve3, sep as sep4 } from "node:path";
|
|
100469
100795
|
var ASK_WAIT_MS = 24e4;
|
|
100470
100796
|
var ANSWER_MAX_CHARS = 6e3;
|
|
100471
100797
|
var ANSWER_HEAD_CHARS = 4500;
|
|
@@ -100534,7 +100860,7 @@ function readableGrants(paths, projectRoot) {
|
|
|
100534
100860
|
return { granted, outside };
|
|
100535
100861
|
}
|
|
100536
100862
|
function underGrant(path2, grants, cwd) {
|
|
100537
|
-
const abs =
|
|
100863
|
+
const abs = resolve3(cwd, path2);
|
|
100538
100864
|
const r = real(abs) ?? abs;
|
|
100539
100865
|
for (const g5 of grants) {
|
|
100540
100866
|
if (r === g5 || r.startsWith(g5.endsWith(sep4) ? g5 : g5 + sep4)) return true;
|
|
@@ -100835,32 +101161,32 @@ function orchestratorMemory(reader, sessionId) {
|
|
|
100835
101161
|
|
|
100836
101162
|
// packages/agent-host/src/sessions/manager.ts
|
|
100837
101163
|
import { homedir as homedir12 } from "node:os";
|
|
100838
|
-
import { basename as basename5, isAbsolute as isAbsolute9, join as
|
|
100839
|
-
import { existsSync as
|
|
101164
|
+
import { basename as basename5, isAbsolute as isAbsolute9, join as join29, relative as relative8, resolve as resolve11, sep as sep11 } from "node:path";
|
|
101165
|
+
import { existsSync as existsSync19, rmSync as rmSync7, statSync as statSync12 } from "node:fs";
|
|
100840
101166
|
import { exec as exec6 } from "node:child_process";
|
|
100841
101167
|
|
|
100842
101168
|
// packages/agent-host/src/adapters/claude/index.ts
|
|
100843
101169
|
init_sdk();
|
|
100844
101170
|
import { homedir as homedir5 } from "node:os";
|
|
100845
|
-
import { randomUUID as
|
|
101171
|
+
import { randomUUID as randomUUID9 } from "node:crypto";
|
|
100846
101172
|
|
|
100847
101173
|
// packages/agent-host/src/adapters/input-receipts.ts
|
|
100848
|
-
import { randomUUID as
|
|
101174
|
+
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
100849
101175
|
var InputReceipts = class {
|
|
100850
101176
|
pending = /* @__PURE__ */ new Map();
|
|
100851
101177
|
ended = /* @__PURE__ */ new Set();
|
|
100852
101178
|
create(observer) {
|
|
100853
|
-
const id2 =
|
|
100854
|
-
let
|
|
101179
|
+
const id2 = randomUUID8();
|
|
101180
|
+
let resolve12;
|
|
100855
101181
|
let reject;
|
|
100856
101182
|
const done = new Promise((yes, no2) => {
|
|
100857
|
-
|
|
101183
|
+
resolve12 = yes;
|
|
100858
101184
|
reject = no2;
|
|
100859
101185
|
});
|
|
100860
101186
|
done.catch(() => {
|
|
100861
101187
|
});
|
|
100862
101188
|
const receipt = { id: id2, done, cancelled: false, completed: false };
|
|
100863
|
-
this.pending.set(id2, { receipt, resolve:
|
|
101189
|
+
this.pending.set(id2, { receipt, resolve: resolve12, reject, observer });
|
|
100864
101190
|
return receipt;
|
|
100865
101191
|
}
|
|
100866
101192
|
get size() {
|
|
@@ -100971,7 +101297,7 @@ async function installedCliVersion(name, packageName, deps = realDeps2()) {
|
|
|
100971
101297
|
// packages/agent-host/src/adapters/claude/exe-link.ts
|
|
100972
101298
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
100973
101299
|
import { closeSync as closeSync6, copyFileSync as copyFileSync2, linkSync, mkdirSync as mkdirSync10, openSync as openSync6, readdirSync as readdirSync7, readFileSync as readFileSync10, readSync as readSync4, renameSync as renameSync10, rmSync as rmSync6, statSync as statSync11 } from "node:fs";
|
|
100974
|
-
import { basename as basename4, dirname as dirname8, join as
|
|
101300
|
+
import { basename as basename4, dirname as dirname8, join as join22 } from "node:path";
|
|
100975
101301
|
var PLACEHOLDER_MAX_BYTES = 1e6;
|
|
100976
101302
|
var ClaudePlaceholderError = class extends Error {
|
|
100977
101303
|
name = "ClaudePlaceholderError";
|
|
@@ -101007,7 +101333,7 @@ function placeholderProblem(exe, size, fs2 = realExeFs) {
|
|
|
101007
101333
|
}
|
|
101008
101334
|
if (head.toString("latin1") === "MZ") return null;
|
|
101009
101335
|
const pkg = dirname8(dirname8(exe));
|
|
101010
|
-
const installer = basename4(dirname8(exe)).toLowerCase() === "bin" ?
|
|
101336
|
+
const installer = basename4(dirname8(exe)).toLowerCase() === "bin" ? join22(pkg, "install.cjs") : null;
|
|
101011
101337
|
let canRerun = false;
|
|
101012
101338
|
if (installer) {
|
|
101013
101339
|
try {
|
|
@@ -101022,14 +101348,14 @@ function placeholderProblem(exe, size, fs2 = realExeFs) {
|
|
|
101022
101348
|
canRerun ? `To fix it, close every Claude Code (Centralu's Claude sessions, terminals, editors) and run \`node "${installer}"\`, or reinstall with \`npm i -g @anthropic-ai/claude-code\` while no Claude Code is running.` : "To fix it, close every Claude Code (Centralu's Claude sessions, terminals, editors) and reinstall it with `npm i -g @anthropic-ai/claude-code`."
|
|
101023
101349
|
].join(" ");
|
|
101024
101350
|
}
|
|
101025
|
-
function claudeKey(exe,
|
|
101351
|
+
function claudeKey(exe, stat7, fs2 = realExeFs) {
|
|
101026
101352
|
let version2 = null;
|
|
101027
101353
|
try {
|
|
101028
|
-
const pkg = JSON.parse(fs2.read(
|
|
101354
|
+
const pkg = JSON.parse(fs2.read(join22(dirname8(dirname8(exe)), "package.json")));
|
|
101029
101355
|
if (pkg.name === "@anthropic-ai/claude-code" && typeof pkg.version === "string" && pkg.version) version2 = pkg.version;
|
|
101030
101356
|
} catch {
|
|
101031
101357
|
}
|
|
101032
|
-
const raw = version2 ? `${version2}-${
|
|
101358
|
+
const raw = version2 ? `${version2}-${stat7.size}` : `${stat7.size}-${Math.floor(stat7.mtimeMs)}`;
|
|
101033
101359
|
return raw.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
101034
101360
|
}
|
|
101035
101361
|
function windowsStart(platform = process.platform) {
|
|
@@ -101051,7 +101377,7 @@ var ClaudeLinks = class {
|
|
|
101051
101377
|
return windowsStart(this.opts.platform);
|
|
101052
101378
|
}
|
|
101053
101379
|
base() {
|
|
101054
|
-
return
|
|
101380
|
+
return join22(this.opts.root(), "tools", "claude");
|
|
101055
101381
|
}
|
|
101056
101382
|
/**
|
|
101057
101383
|
* What to start for the `claude.exe` found at `exe`. Off Windows, or for anything but an `.exe`
|
|
@@ -101062,26 +101388,26 @@ var ClaudeLinks = class {
|
|
|
101062
101388
|
prepare(exe) {
|
|
101063
101389
|
const direct = { path: exe, key: null };
|
|
101064
101390
|
if (!this.enabled || !/\.exe$/i.test(exe)) return direct;
|
|
101065
|
-
let
|
|
101391
|
+
let stat7;
|
|
101066
101392
|
try {
|
|
101067
|
-
|
|
101393
|
+
stat7 = this.fs.stat(exe);
|
|
101068
101394
|
} catch {
|
|
101069
101395
|
return direct;
|
|
101070
101396
|
}
|
|
101071
|
-
const problem2 = placeholderProblem(exe,
|
|
101397
|
+
const problem2 = placeholderProblem(exe, stat7.size, this.fs);
|
|
101072
101398
|
if (problem2) throw new ClaudePlaceholderError(problem2);
|
|
101073
|
-
const key = claudeKey(exe,
|
|
101074
|
-
const dir =
|
|
101075
|
-
const target =
|
|
101399
|
+
const key = claudeKey(exe, stat7, this.fs);
|
|
101400
|
+
const dir = join22(this.base(), key);
|
|
101401
|
+
const target = join22(dir, "claude.exe");
|
|
101076
101402
|
const ready = () => {
|
|
101077
101403
|
try {
|
|
101078
|
-
return this.fs.stat(target).size ===
|
|
101404
|
+
return this.fs.stat(target).size === stat7.size;
|
|
101079
101405
|
} catch {
|
|
101080
101406
|
return false;
|
|
101081
101407
|
}
|
|
101082
101408
|
};
|
|
101083
101409
|
if (ready()) return this.use(key, target);
|
|
101084
|
-
const tmp =
|
|
101410
|
+
const tmp = join22(dir, `claude-${randomBytes2(4).toString("hex")}.tmp`);
|
|
101085
101411
|
try {
|
|
101086
101412
|
this.fs.mkdir(dir);
|
|
101087
101413
|
try {
|
|
@@ -101141,7 +101467,7 @@ var ClaudeLinks = class {
|
|
|
101141
101467
|
for (const name of names) {
|
|
101142
101468
|
if (name === this.current || this.uses.has(name)) continue;
|
|
101143
101469
|
try {
|
|
101144
|
-
this.fs.removeDir(
|
|
101470
|
+
this.fs.removeDir(join22(this.base(), name));
|
|
101145
101471
|
} catch {
|
|
101146
101472
|
}
|
|
101147
101473
|
}
|
|
@@ -101451,7 +101777,7 @@ async function readClaudeModels(q2) {
|
|
|
101451
101777
|
}
|
|
101452
101778
|
|
|
101453
101779
|
// packages/agent-host/src/adapters/local-process.ts
|
|
101454
|
-
import { spawn as
|
|
101780
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
101455
101781
|
import { EventEmitter as EventEmitter3 } from "node:events";
|
|
101456
101782
|
var EXIT_AFTER_OUTPUT_MS3 = 1e3;
|
|
101457
101783
|
var STDERR_TAIL = 8 * 1024;
|
|
@@ -101481,7 +101807,7 @@ var LocalAgentProcess = class _LocalAgentProcess extends EventEmitter3 {
|
|
|
101481
101807
|
exitReported = false;
|
|
101482
101808
|
spawnedAt = Date.now();
|
|
101483
101809
|
static spawn(spec, os2) {
|
|
101484
|
-
const child =
|
|
101810
|
+
const child = spawn5(spec.command, spec.args, {
|
|
101485
101811
|
cwd: spec.cwd,
|
|
101486
101812
|
env: spec.env,
|
|
101487
101813
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -102595,7 +102921,14 @@ function alwaysAllowKey(detail, opts = {}) {
|
|
|
102595
102921
|
}
|
|
102596
102922
|
|
|
102597
102923
|
// packages/agent-host/src/adapters/claude/cross-session.ts
|
|
102598
|
-
function withoutCrossSessionMessaging(options) {
|
|
102924
|
+
function withoutCrossSessionMessaging(options, preset = "normal") {
|
|
102925
|
+
if (preset === "auto")
|
|
102926
|
+
return {
|
|
102927
|
+
...options,
|
|
102928
|
+
settings: options.settings ?? {},
|
|
102929
|
+
disallowedTools: options.disallowedTools ?? [],
|
|
102930
|
+
env: { ...process.env, ...options.env }
|
|
102931
|
+
};
|
|
102599
102932
|
const env = {
|
|
102600
102933
|
...process.env,
|
|
102601
102934
|
...options.env,
|
|
@@ -102658,10 +102991,19 @@ function readTarget(toolName, input) {
|
|
|
102658
102991
|
const p = toolName === "Read" ? input.file_path : input.path;
|
|
102659
102992
|
return typeof p === "string" && p ? p : null;
|
|
102660
102993
|
}
|
|
102661
|
-
function permissionOptionsFor(
|
|
102662
|
-
if (
|
|
102663
|
-
if (
|
|
102664
|
-
return {
|
|
102994
|
+
async function permissionOptionsFor(opts) {
|
|
102995
|
+
if (opts.permissionPreset === "auto") return { permissionMode: "bypassPermissions" };
|
|
102996
|
+
if (opts.permissionPreset === "safe") return { permissionMode: "default" };
|
|
102997
|
+
if (opts.noSettingFiles) return { permissionMode: await personsDefaultMode() ?? "default" };
|
|
102998
|
+
try {
|
|
102999
|
+
const resolved = await D9t({ cwd: opts.cwd, ...settingSourcesFor(opts) });
|
|
103000
|
+
const mode = qtt(resolved).permissions?.defaultMode;
|
|
103001
|
+
if (typeof mode === "string" && PERMISSION_MODES.includes(mode)) {
|
|
103002
|
+
return { permissionMode: mode };
|
|
103003
|
+
}
|
|
103004
|
+
} catch {
|
|
103005
|
+
}
|
|
103006
|
+
return { permissionMode: "default" };
|
|
102665
103007
|
}
|
|
102666
103008
|
var PERMISSION_MODES = ["default", "acceptEdits", "bypassPermissions", "plan", "dontAsk", "auto"];
|
|
102667
103009
|
async function personsDefaultMode() {
|
|
@@ -102695,7 +103037,7 @@ var ClaudeSession = class {
|
|
|
102695
103037
|
this.opts = opts;
|
|
102696
103038
|
this.emit = emit;
|
|
102697
103039
|
this.launch = launch2;
|
|
102698
|
-
this.ended = new Promise((
|
|
103040
|
+
this.ended = new Promise((resolve12) => this.markEnded = resolve12);
|
|
102699
103041
|
this.stream = new ClaudeStreamNormalizer(sessionId, () => ({
|
|
102700
103042
|
model: opts.model ?? null,
|
|
102701
103043
|
effort: opts.effort ?? null,
|
|
@@ -102801,7 +103143,7 @@ var ClaudeSession = class {
|
|
|
102801
103143
|
this.orchestratorServer = orchestratorMcp(this.opts.orchestratorTools, this.opts.toolProfile, this.opts.sessionId);
|
|
102802
103144
|
}
|
|
102803
103145
|
const servers = this.mcpServers();
|
|
102804
|
-
const
|
|
103146
|
+
const permissionOptions = await permissionOptionsFor(this.opts);
|
|
102805
103147
|
if (!this.opts.processSource?.adopt) await this.launch.gate.turn();
|
|
102806
103148
|
this.runKey = run?.key ?? null;
|
|
102807
103149
|
this.launch.links.acquire(this.runKey);
|
|
@@ -102894,7 +103236,7 @@ var ClaudeSession = class {
|
|
|
102894
103236
|
*/
|
|
102895
103237
|
...this.opts.systemPromptAppend ? { systemPrompt: { type: "preset", preset: "claude_code", append: this.opts.systemPromptAppend } } : {}
|
|
102896
103238
|
} : {},
|
|
102897
|
-
...
|
|
103239
|
+
...permissionOptions,
|
|
102898
103240
|
/*
|
|
102899
103241
|
* The agent an app hands a schema and asks for output (M4 D-1). It is a per-query option,
|
|
102900
103242
|
* so it can only be given when the session starts. Measured (SDK 0.3.263, CLI 2.1.282,
|
|
@@ -102946,8 +103288,8 @@ var ClaudeSession = class {
|
|
|
102946
103288
|
if (questions.length === 0) return { behavior: "allow", updatedInput: toolInput };
|
|
102947
103289
|
const requestId2 = `q-${++self2.reqCounter}`;
|
|
102948
103290
|
self2.emit({ type: "question_request", sessionId: self2.sessionId, requestId: requestId2, questions });
|
|
102949
|
-
return new Promise((
|
|
102950
|
-
self2.questions.set(requestId2, { resolve:
|
|
103291
|
+
return new Promise((resolve12) => {
|
|
103292
|
+
self2.questions.set(requestId2, { resolve: resolve12, input: toolInput });
|
|
102951
103293
|
self2.releaseOnCancel(requestId2, options?.signal);
|
|
102952
103294
|
});
|
|
102953
103295
|
}
|
|
@@ -102956,17 +103298,17 @@ var ClaudeSession = class {
|
|
|
102956
103298
|
if (key && self2.alwaysAllow.allows(key)) return { behavior: "allow", updatedInput: toolInput };
|
|
102957
103299
|
const requestId = `req-${++self2.reqCounter}`;
|
|
102958
103300
|
self2.emit({ type: "approval_request", sessionId: self2.sessionId, requestId, detail });
|
|
102959
|
-
return new Promise((
|
|
102960
|
-
self2.pending.set(requestId, { resolve:
|
|
103301
|
+
return new Promise((resolve12) => {
|
|
103302
|
+
self2.pending.set(requestId, { resolve: resolve12, input: toolInput });
|
|
102961
103303
|
self2.releaseOnCancel(requestId, options?.signal);
|
|
102962
103304
|
});
|
|
102963
103305
|
}
|
|
102964
|
-
})
|
|
103306
|
+
}, preset)
|
|
102965
103307
|
});
|
|
102966
103308
|
ClaudeAdapter.liveQueries.add(q2);
|
|
102967
103309
|
ClaudeAdapter.querySessions.set(q2, this.sessionId);
|
|
102968
103310
|
this.releaseLostCalls(q2);
|
|
102969
|
-
this.goalKnown = new Promise((
|
|
103311
|
+
this.goalKnown = new Promise((resolve12) => this.settleGoalKnown = resolve12);
|
|
102970
103312
|
q2.supportedCommands().then((list) => {
|
|
102971
103313
|
if (this.goalCommand === void 0 && Array.isArray(list)) this.goalCommand = list.some((c) => c?.name === "goal");
|
|
102972
103314
|
}).catch(() => {
|
|
@@ -103136,8 +103478,8 @@ var ClaudeSession = class {
|
|
|
103136
103478
|
* turn released, rather than left hanging; the person can send the message again.
|
|
103137
103479
|
*/
|
|
103138
103480
|
releaseLostCalls(q2) {
|
|
103139
|
-
const
|
|
103140
|
-
const lost =
|
|
103481
|
+
const open6 = this.opts.processSource?.adopt?.openCalls ?? [];
|
|
103482
|
+
const lost = open6.filter((c) => isOrchestratorTool(c.tool) || appToolOf(c.tool) !== null);
|
|
103141
103483
|
if (lost.length === 0) return;
|
|
103142
103484
|
this.turnOpen = true;
|
|
103143
103485
|
this.stream.stopped();
|
|
@@ -103194,7 +103536,7 @@ var ClaudeSession = class {
|
|
|
103194
103536
|
send(text2) {
|
|
103195
103537
|
this.sendInput(text2);
|
|
103196
103538
|
}
|
|
103197
|
-
sendInput(text2, uuid3 =
|
|
103539
|
+
sendInput(text2, uuid3 = randomUUID9(), note) {
|
|
103198
103540
|
const goal = isGoalCommand(text2);
|
|
103199
103541
|
if (!this.sendGate && !(goal && this.goalCommand === void 0)) {
|
|
103200
103542
|
this.deliver(text2, uuid3, !!note);
|
|
@@ -103268,11 +103610,11 @@ var ClaudeSession = class {
|
|
|
103268
103610
|
* the model receives the answers in the CLI's own words (see the AskUserQuestion branch of `canUseTool`).
|
|
103269
103611
|
*/
|
|
103270
103612
|
answerQuestion(requestId, answers) {
|
|
103271
|
-
const
|
|
103272
|
-
if (!
|
|
103613
|
+
const open6 = this.questions.get(requestId);
|
|
103614
|
+
if (!open6) return false;
|
|
103273
103615
|
this.questions.delete(requestId);
|
|
103274
103616
|
const byQuestion = Object.fromEntries(answers.map((a) => [a.question, a.answers.join(", ")]));
|
|
103275
|
-
|
|
103617
|
+
open6.resolve({ behavior: "allow", updatedInput: { ...open6.input, answers: byQuestion } });
|
|
103276
103618
|
this.emit({ type: "question_resolved", sessionId: this.sessionId, requestId });
|
|
103277
103619
|
return true;
|
|
103278
103620
|
}
|
|
@@ -103548,8 +103890,8 @@ var ClaudeSession = class {
|
|
|
103548
103890
|
}
|
|
103549
103891
|
/** Releases a choice that was still waiting on an answer — **never released silently**, for the same reason as approval. */
|
|
103550
103892
|
releaseQuestions(why) {
|
|
103551
|
-
for (const [id2,
|
|
103552
|
-
|
|
103893
|
+
for (const [id2, open6] of this.questions) {
|
|
103894
|
+
open6.resolve({ behavior: "deny", message: why });
|
|
103553
103895
|
this.emit({ type: "question_resolved", sessionId: this.sessionId, requestId: id2 });
|
|
103554
103896
|
}
|
|
103555
103897
|
this.questions.clear();
|
|
@@ -103655,7 +103997,7 @@ var ClaudeAdapter = class _ClaudeAdapter {
|
|
|
103655
103997
|
sleep: true
|
|
103656
103998
|
};
|
|
103657
103999
|
launchSettingsFingerprint(settings) {
|
|
103658
|
-
return JSON.stringify({ v: 1, cwd: settings.cwd, permission: settings.permissionPreset, trusted: !!settings.projectTrusted, noSettings: !!settings.noSettingFiles });
|
|
104000
|
+
return JSON.stringify({ v: settings.permissionPreset === "safe" ? 1 : 2, cwd: settings.cwd, permission: settings.permissionPreset, trusted: !!settings.projectTrusted, noSettings: !!settings.noSettingFiles });
|
|
103659
104001
|
}
|
|
103660
104002
|
async detect() {
|
|
103661
104003
|
const path2 = whichTool("claude");
|
|
@@ -103742,7 +104084,7 @@ var ClaudeAdapter = class _ClaudeAdapter {
|
|
|
103742
104084
|
let timer;
|
|
103743
104085
|
await Promise.race([
|
|
103744
104086
|
Promise.allSettled([...this.launch.closing]),
|
|
103745
|
-
new Promise((
|
|
104087
|
+
new Promise((resolve12) => timer = setTimeout(resolve12, this.launch.exitGraceMs))
|
|
103746
104088
|
]);
|
|
103747
104089
|
clearTimeout(timer);
|
|
103748
104090
|
}
|
|
@@ -103752,7 +104094,7 @@ var ClaudeAdapter = class _ClaudeAdapter {
|
|
|
103752
104094
|
import { execFile as execFile5 } from "node:child_process";
|
|
103753
104095
|
|
|
103754
104096
|
// packages/agent-host/src/adapters/codex/bridge-path.ts
|
|
103755
|
-
import { existsSync as
|
|
104097
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
103756
104098
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
103757
104099
|
function bridgePath() {
|
|
103758
104100
|
const candidates = [
|
|
@@ -103761,23 +104103,23 @@ function bridgePath() {
|
|
|
103761
104103
|
new URL("./codex-orchestrator-bridge.mjs", import.meta.url)
|
|
103762
104104
|
// the bundled build-output layout
|
|
103763
104105
|
].map((u) => fileURLToPath3(u));
|
|
103764
|
-
const found = candidates.find((p) =>
|
|
104106
|
+
const found = candidates.find((p) => existsSync15(p));
|
|
103765
104107
|
if (!found) throw new Error(`orchestrator bridge not found: ${candidates.join(", ")}`);
|
|
103766
104108
|
return found;
|
|
103767
104109
|
}
|
|
103768
104110
|
|
|
103769
104111
|
// packages/agent-host/src/adapters/codex/index.ts
|
|
103770
|
-
import { existsSync as
|
|
104112
|
+
import { existsSync as existsSync17, realpathSync as realpathSync7 } from "node:fs";
|
|
103771
104113
|
import { homedir as homedir10 } from "node:os";
|
|
103772
|
-
import { dirname as dirname9, join as
|
|
104114
|
+
import { dirname as dirname9, join as join24, resolve as resolve9 } from "node:path";
|
|
103773
104115
|
import { promisify as promisify5 } from "node:util";
|
|
103774
104116
|
|
|
103775
104117
|
// packages/agent-host/src/adapters/codex/native.ts
|
|
103776
|
-
import { existsSync as
|
|
104118
|
+
import { existsSync as existsSync16, readFileSync as readFileSync11, realpathSync as realpathSync6 } from "node:fs";
|
|
103777
104119
|
var realDeps3 = () => ({
|
|
103778
104120
|
platform: HOST_PLATFORM,
|
|
103779
104121
|
arch: process.arch,
|
|
103780
|
-
exists:
|
|
104122
|
+
exists: existsSync16,
|
|
103781
104123
|
read: (p) => readFileSync11(p, "utf8"),
|
|
103782
104124
|
realpath: (p) => realpathSync6(p),
|
|
103783
104125
|
launch: (p) => launchFor(p),
|
|
@@ -103946,14 +104288,14 @@ var CodexClient = class {
|
|
|
103946
104288
|
if (this.closed) return Promise.reject(new Error("codex app-server has already exited"));
|
|
103947
104289
|
const id2 = `${this.idPrefix}${this.nextId++}`;
|
|
103948
104290
|
this.write({ id: id2, method, params });
|
|
103949
|
-
return new Promise((
|
|
104291
|
+
return new Promise((resolve12, reject) => {
|
|
103950
104292
|
const timer = setTimeout(() => {
|
|
103951
104293
|
if (this.pending.delete(id2)) reject(new Error(`No response for ${method}`));
|
|
103952
104294
|
}, timeoutMs);
|
|
103953
104295
|
this.pending.set(id2, {
|
|
103954
104296
|
resolve: (v) => {
|
|
103955
104297
|
clearTimeout(timer);
|
|
103956
|
-
|
|
104298
|
+
resolve12(v);
|
|
103957
104299
|
},
|
|
103958
104300
|
reject: (e) => {
|
|
103959
104301
|
clearTimeout(timer);
|
|
@@ -103977,12 +104319,12 @@ var CodexClient = class {
|
|
|
103977
104319
|
if (this.closed) return;
|
|
103978
104320
|
this.closed = true;
|
|
103979
104321
|
this.proc.stdin.end();
|
|
103980
|
-
const exited = await new Promise((
|
|
103981
|
-
if (this.proc.exitCode !== null || this.proc.signalCode != null || this.finished) return
|
|
103982
|
-
const t = setTimeout(() =>
|
|
104322
|
+
const exited = await new Promise((resolve12) => {
|
|
104323
|
+
if (this.proc.exitCode !== null || this.proc.signalCode != null || this.finished) return resolve12(true);
|
|
104324
|
+
const t = setTimeout(() => resolve12(false), 2e3);
|
|
103983
104325
|
this.proc.once("exit", () => {
|
|
103984
104326
|
clearTimeout(t);
|
|
103985
|
-
|
|
104327
|
+
resolve12(true);
|
|
103986
104328
|
});
|
|
103987
104329
|
});
|
|
103988
104330
|
if (!exited) this.proc.kill("SIGKILL");
|
|
@@ -104003,19 +104345,19 @@ var CodexClient = class {
|
|
|
104003
104345
|
};
|
|
104004
104346
|
|
|
104005
104347
|
// packages/agent-host/src/adapters/codex/rollout.ts
|
|
104006
|
-
import { createReadStream as
|
|
104007
|
-
import { readdir as
|
|
104348
|
+
import { createReadStream as createReadStream3 } from "node:fs";
|
|
104349
|
+
import { readdir as readdir5 } from "node:fs/promises";
|
|
104008
104350
|
import { createInterface as createInterface2 } from "node:readline";
|
|
104009
104351
|
import { homedir as homedir6 } from "node:os";
|
|
104010
|
-
import { join as
|
|
104352
|
+
import { join as join23 } from "node:path";
|
|
104011
104353
|
var MIN_SUMMARY_CHARS = 200;
|
|
104012
|
-
async function findRolloutPath(threadId, sessionsDir =
|
|
104354
|
+
async function findRolloutPath(threadId, sessionsDir = join23(homedir6(), ".codex", "sessions")) {
|
|
104013
104355
|
try {
|
|
104014
104356
|
const suffix = `-${threadId}.jsonl`;
|
|
104015
|
-
const names = await
|
|
104357
|
+
const names = await readdir5(sessionsDir, { recursive: true });
|
|
104016
104358
|
const hits = names.filter((n) => String(n).endsWith(suffix)).sort();
|
|
104017
104359
|
const hit = hits[hits.length - 1];
|
|
104018
|
-
return hit ?
|
|
104360
|
+
return hit ? join23(sessionsDir, String(hit)) : null;
|
|
104019
104361
|
} catch {
|
|
104020
104362
|
return null;
|
|
104021
104363
|
}
|
|
@@ -104039,7 +104381,7 @@ async function lastCompactSummary(threadId, sessionsDir) {
|
|
|
104039
104381
|
if (!path2) return null;
|
|
104040
104382
|
try {
|
|
104041
104383
|
let last = null;
|
|
104042
|
-
const rl2 = createInterface2({ input:
|
|
104384
|
+
const rl2 = createInterface2({ input: createReadStream3(path2, "utf8"), crlfDelay: Infinity });
|
|
104043
104385
|
for await (const line of rl2) {
|
|
104044
104386
|
if (!line.includes('"compacted"')) continue;
|
|
104045
104387
|
try {
|
|
@@ -104216,7 +104558,7 @@ function userInputText(content) {
|
|
|
104216
104558
|
}
|
|
104217
104559
|
|
|
104218
104560
|
// packages/agent-host/src/adapters/codex/images.ts
|
|
104219
|
-
import { readFile as
|
|
104561
|
+
import { readFile as readFile3, stat as stat5 } from "node:fs/promises";
|
|
104220
104562
|
var IMAGE_MIMES2 = {
|
|
104221
104563
|
png: "image/png",
|
|
104222
104564
|
jpg: "image/jpeg",
|
|
@@ -104232,9 +104574,9 @@ async function imageEventFromDisk(sessionId, path2, maxBytes = IMAGE_MAX_BYTES)
|
|
|
104232
104574
|
const mime = IMAGE_MIMES2[ext2];
|
|
104233
104575
|
if (!mime) return fail4(`Cannot display this format (.${ext2})`);
|
|
104234
104576
|
try {
|
|
104235
|
-
const s = await
|
|
104577
|
+
const s = await stat5(path2);
|
|
104236
104578
|
if (s.size > maxBytes) return fail4(`Image is too large (${Math.round(s.size / 1048576)}MB)`);
|
|
104237
|
-
const buf = await
|
|
104579
|
+
const buf = await readFile3(path2);
|
|
104238
104580
|
return { type: "message_image", sessionId, mime, data: buf.toString("base64"), path: path2 };
|
|
104239
104581
|
} catch (err) {
|
|
104240
104582
|
return fail4(`Failed to read image: ${err.message}`);
|
|
@@ -105218,16 +105560,16 @@ var CodexChildTracker = class {
|
|
|
105218
105560
|
};
|
|
105219
105561
|
|
|
105220
105562
|
// packages/agent-host/src/adapters/codex/sandbox.ts
|
|
105221
|
-
import { realpath as
|
|
105222
|
-
import { resolve as
|
|
105563
|
+
import { realpath as realpath6 } from "node:fs/promises";
|
|
105564
|
+
import { resolve as resolve8 } from "node:path";
|
|
105223
105565
|
async function sandboxConfig(cwd, preset, readConfig) {
|
|
105224
105566
|
const roots = [];
|
|
105225
105567
|
try {
|
|
105226
105568
|
const stdout = await runGit(cwd, ["rev-parse", "--path-format=absolute", "--git-dir", "--git-common-dir"], { timeout: 5e3 });
|
|
105227
105569
|
const [gitDir, commonDir] = stdout.trim().split(/\r?\n/);
|
|
105228
105570
|
if (gitDir && commonDir) {
|
|
105229
|
-
const gitPath = await
|
|
105230
|
-
const commonPath = await
|
|
105571
|
+
const gitPath = await realpath6(resolve8(cwd, gitDir));
|
|
105572
|
+
const commonPath = await realpath6(resolve8(cwd, commonDir));
|
|
105231
105573
|
if (gitPath !== commonPath) roots.push(commonPath);
|
|
105232
105574
|
}
|
|
105233
105575
|
} catch {
|
|
@@ -105255,7 +105597,7 @@ function permissionOptionsFor2(preset) {
|
|
|
105255
105597
|
function repoFilesConfig(opts) {
|
|
105256
105598
|
if (opts.projectTrusted === true && !opts.noSettingFiles) return {};
|
|
105257
105599
|
const keys = /* @__PURE__ */ new Set();
|
|
105258
|
-
for (const start2 of [
|
|
105600
|
+
for (const start2 of [resolve9(opts.cwd), realPathOr(opts.cwd)]) {
|
|
105259
105601
|
for (let dir = start2; ; dir = dirname9(dir)) {
|
|
105260
105602
|
keys.add(dir);
|
|
105261
105603
|
if (dirname9(dir) === dir) break;
|
|
@@ -105270,7 +105612,7 @@ function realPathOr(path2) {
|
|
|
105270
105612
|
try {
|
|
105271
105613
|
return realpathSync7.native(path2);
|
|
105272
105614
|
} catch {
|
|
105273
|
-
return
|
|
105615
|
+
return resolve9(path2);
|
|
105274
105616
|
}
|
|
105275
105617
|
}
|
|
105276
105618
|
var APP_APPROVAL_MODE = {
|
|
@@ -106161,10 +106503,10 @@ var CodexSession = class {
|
|
|
106161
106503
|
return this.opts.outputSchema ? { outputSchema: this.opts.outputSchema } : {};
|
|
106162
106504
|
}
|
|
106163
106505
|
respondApproval(requestId, decision, _scope, matcher) {
|
|
106164
|
-
const
|
|
106165
|
-
if (
|
|
106506
|
+
const open6 = this.approvals.get(requestId);
|
|
106507
|
+
if (open6 === void 0) return false;
|
|
106166
106508
|
this.approvals.delete(requestId);
|
|
106167
|
-
const serverId =
|
|
106509
|
+
const serverId = open6.serverId;
|
|
106168
106510
|
if (this.elicitations.delete(requestId)) {
|
|
106169
106511
|
this.client.respond(serverId, {
|
|
106170
106512
|
action: decision === "deny" ? "decline" : "accept",
|
|
@@ -106389,7 +106731,7 @@ function threadIdOf(res) {
|
|
|
106389
106731
|
}
|
|
106390
106732
|
function codexHome() {
|
|
106391
106733
|
const custom2 = process.env.CODEX_HOME?.trim();
|
|
106392
|
-
return custom2 ? custom2 :
|
|
106734
|
+
return custom2 ? custom2 : join24(homedir10(), ".codex");
|
|
106393
106735
|
}
|
|
106394
106736
|
var CODEX_VERBOSITIES = ["low", "medium", "high"];
|
|
106395
106737
|
var CodexAdapter = class {
|
|
@@ -106445,7 +106787,7 @@ var CodexAdapter = class {
|
|
|
106445
106787
|
const launch2 = launchFor(path2 ?? "codex");
|
|
106446
106788
|
const { stdout } = await exec4(launch2.command, [...launch2.args, "--version"], { timeout: 5e3 });
|
|
106447
106789
|
const version2 = `${stdout.trim()} \xB7 ${path2 ?? "PATH"}`;
|
|
106448
|
-
const loggedIn =
|
|
106790
|
+
const loggedIn = existsSync17(join24(codexHome(), "auth.json"));
|
|
106449
106791
|
return {
|
|
106450
106792
|
tool: "codex",
|
|
106451
106793
|
installed: true,
|
|
@@ -106579,7 +106921,7 @@ function registeredTool(adapters2, preferred) {
|
|
|
106579
106921
|
|
|
106580
106922
|
// packages/agent-host/src/dev-services/store.ts
|
|
106581
106923
|
import Database from "better-sqlite3";
|
|
106582
|
-
import { existsSync as
|
|
106924
|
+
import { existsSync as existsSync18, readFileSync as readFileSync12 } from "node:fs";
|
|
106583
106925
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
106584
106926
|
function resolveSchemaPath() {
|
|
106585
106927
|
const candidates = [
|
|
@@ -106588,7 +106930,7 @@ function resolveSchemaPath() {
|
|
|
106588
106930
|
new URL("../../../protocol/src/schema/schema.sql", import.meta.url)
|
|
106589
106931
|
// source tree
|
|
106590
106932
|
].map((u) => fileURLToPath4(u));
|
|
106591
|
-
const found = candidates.find((p) =>
|
|
106933
|
+
const found = candidates.find((p) => existsSync18(p));
|
|
106592
106934
|
if (!found) throw new Error(`schema.sql not found: ${candidates.join(", ")}`);
|
|
106593
106935
|
return found;
|
|
106594
106936
|
}
|
|
@@ -106866,7 +107208,7 @@ var Store = class _Store {
|
|
|
106866
107208
|
const probe = Object.create(_Store.prototype);
|
|
106867
107209
|
const steps = probe.migrationSteps();
|
|
106868
107210
|
const known = steps[steps.length - 1].to;
|
|
106869
|
-
if (dbPath2 === ":memory:" || !
|
|
107211
|
+
if (dbPath2 === ":memory:" || !existsSync18(dbPath2)) return empty(known);
|
|
106870
107212
|
const db = new Database(dbPath2, { readonly: true, fileMustExist: true });
|
|
106871
107213
|
try {
|
|
106872
107214
|
probe.db = db;
|
|
@@ -108667,7 +109009,7 @@ var Store = class _Store {
|
|
|
108667
109009
|
this.db.prepare(`DELETE FROM sessions WHERE id = ? AND deleted_at IS NOT NULL`).run(sessionId);
|
|
108668
109010
|
return true;
|
|
108669
109011
|
});
|
|
108670
|
-
while (!step()) await new Promise((
|
|
109012
|
+
while (!step()) await new Promise((resolve12) => setImmediate(resolve12));
|
|
108671
109013
|
return true;
|
|
108672
109014
|
}
|
|
108673
109015
|
/** One session's messages in seq order, one transaction per chunk, letting the event loop go between chunks (#179) */
|
|
@@ -108682,7 +109024,7 @@ var Store = class _Store {
|
|
|
108682
109024
|
if (rows.length > 0) after = rows[rows.length - 1].seq;
|
|
108683
109025
|
return rows.length < chunk;
|
|
108684
109026
|
});
|
|
108685
|
-
while (!step()) await new Promise((
|
|
109027
|
+
while (!step()) await new Promise((resolve12) => setImmediate(resolve12));
|
|
108686
109028
|
}
|
|
108687
109029
|
isTrashed(sessionId) {
|
|
108688
109030
|
return !!this.db.prepare(`SELECT 1 FROM sessions WHERE id = ? AND deleted_at IS NOT NULL`).get(sessionId);
|
|
@@ -109349,7 +109691,8 @@ function parseTrashRecord(json2) {
|
|
|
109349
109691
|
projectName: str8(r.projectName),
|
|
109350
109692
|
projectPath: str8(r.projectPath),
|
|
109351
109693
|
removeExternal: r.removeExternal === true,
|
|
109352
|
-
removeWorktree: r.removeWorktree === true
|
|
109694
|
+
removeWorktree: r.removeWorktree === true,
|
|
109695
|
+
...r.recovery && typeof r.recovery === "object" && typeof r.recovery.snapshot === "string" && typeof r.recovery.tip === "string" && typeof r.recovery.changedFiles === "number" && typeof r.recovery.commits === "number" ? { recovery: r.recovery } : {}
|
|
109353
109696
|
};
|
|
109354
109697
|
}
|
|
109355
109698
|
var OUT_OF_TRASH = `session_id NOT IN (SELECT id FROM sessions WHERE deleted_at IS NOT NULL)`;
|
|
@@ -109374,7 +109717,7 @@ function indexedText(kind, payload) {
|
|
|
109374
109717
|
|
|
109375
109718
|
// packages/agent-host/src/dev-services/git.ts
|
|
109376
109719
|
import { execFile as execFile6 } from "node:child_process";
|
|
109377
|
-
import { isAbsolute as isAbsolute8, relative as relative7, resolve as
|
|
109720
|
+
import { isAbsolute as isAbsolute8, relative as relative7, resolve as resolve10, sep as sep9 } from "node:path";
|
|
109378
109721
|
import { promisify as promisify6 } from "node:util";
|
|
109379
109722
|
var exec5 = promisify6(execFile6);
|
|
109380
109723
|
var OK2 = { timeout: 1e4, maxBuffer: 32 * 1024 * 1024 };
|
|
@@ -109446,8 +109789,8 @@ function assertLexicalGitPath(cwd, path2) {
|
|
|
109446
109789
|
if (!path2 || path2.includes("\0") || path2.startsWith(":")) {
|
|
109447
109790
|
throw Object.assign(new Error("Invalid git path"), { code: "internal" });
|
|
109448
109791
|
}
|
|
109449
|
-
const root3 =
|
|
109450
|
-
const target = isAbsolute8(path2) ?
|
|
109792
|
+
const root3 = resolve10(cwd);
|
|
109793
|
+
const target = isAbsolute8(path2) ? resolve10(path2) : resolve10(root3, path2);
|
|
109451
109794
|
const rel = relative7(root3, target);
|
|
109452
109795
|
if (rel === "") throw Object.assign(new Error("Path is the project root, not a file in it"), { code: "internal" });
|
|
109453
109796
|
if (rel !== ".." && !rel.startsWith(`..${sep9}`) && !isAbsolute8(rel)) return rel;
|
|
@@ -109566,14 +109909,14 @@ async function gitIgnoredEntries(cwd, limit = 50, trust = {}) {
|
|
|
109566
109909
|
if (paths.length === 0) return [];
|
|
109567
109910
|
const sizes = /* @__PURE__ */ new Map();
|
|
109568
109911
|
try {
|
|
109569
|
-
const out = await new Promise((
|
|
109912
|
+
const out = await new Promise((resolve12, reject) => {
|
|
109570
109913
|
execFile6(
|
|
109571
109914
|
"du",
|
|
109572
109915
|
["-sk", ...paths.slice(0, limit)],
|
|
109573
109916
|
{ cwd, timeout: 5e3, maxBuffer: 1 << 20 },
|
|
109574
109917
|
(err, stdout) => (
|
|
109575
109918
|
// du exits non-zero if even one entry cannot be read — whatever it did print is used anyway
|
|
109576
|
-
stdout ?
|
|
109919
|
+
stdout ? resolve12(stdout) : reject(err)
|
|
109577
109920
|
)
|
|
109578
109921
|
);
|
|
109579
109922
|
});
|
|
@@ -109686,8 +110029,8 @@ async function gitValidBranchName(name) {
|
|
|
109686
110029
|
return false;
|
|
109687
110030
|
}
|
|
109688
110031
|
}
|
|
109689
|
-
async function gitWorktreeRemove(repoCwd, path2, force = false) {
|
|
109690
|
-
await git(repoCwd, ["worktree", "remove", ...force ? ["--force"] : [], path2]);
|
|
110032
|
+
async function gitWorktreeRemove(repoCwd, path2, force = false, withSubmodules = false) {
|
|
110033
|
+
await git(repoCwd, ["worktree", "remove", ...force ? ["--force", ...withSubmodules ? ["--force"] : []] : [], path2]);
|
|
109691
110034
|
}
|
|
109692
110035
|
async function gitBranchDelete(repoCwd, branch, trust = {}) {
|
|
109693
110036
|
await git(repoCwd, ["branch", "-D", branch], trust);
|
|
@@ -109703,13 +110046,13 @@ async function gitWorktreeDirty(path2, trust = {}) {
|
|
|
109703
110046
|
}
|
|
109704
110047
|
|
|
109705
110048
|
// packages/agent-host/src/dev-services/worktree-dependencies.ts
|
|
109706
|
-
import { lstat as
|
|
109707
|
-
import { join as
|
|
110049
|
+
import { lstat as lstat5, readFile as readFile4, readdir as readdir6 } from "node:fs/promises";
|
|
110050
|
+
import { join as join25 } from "node:path";
|
|
109708
110051
|
var LOCKFILES = ["pnpm-lock.yaml", "package-lock.json", "npm-shrinkwrap.json", "yarn.lock", "bun.lock", "bun.lockb"];
|
|
109709
110052
|
async function prepareWorktreeDependencies(projectCwd, worktreeCwd) {
|
|
109710
110053
|
let locked = false;
|
|
109711
110054
|
for (const name of LOCKFILES) {
|
|
109712
|
-
const target = await
|
|
110055
|
+
const target = await lstat5(join25(worktreeCwd, name)).catch((err) => {
|
|
109713
110056
|
if (err.code === "ENOENT") return null;
|
|
109714
110057
|
throw err;
|
|
109715
110058
|
});
|
|
@@ -109718,7 +110061,7 @@ async function prepareWorktreeDependencies(projectCwd, worktreeCwd) {
|
|
|
109718
110061
|
resolveExisting(projectCwd, name),
|
|
109719
110062
|
resolveExisting(worktreeCwd, name)
|
|
109720
110063
|
]);
|
|
109721
|
-
const [before, after] = await Promise.all([
|
|
110064
|
+
const [before, after] = await Promise.all([readFile4(source), readFile4(destination)]);
|
|
109722
110065
|
if (!before.equals(after)) return;
|
|
109723
110066
|
locked = true;
|
|
109724
110067
|
}
|
|
@@ -109727,22 +110070,22 @@ async function prepareWorktreeDependencies(projectCwd, worktreeCwd) {
|
|
|
109727
110070
|
const folders = [""];
|
|
109728
110071
|
while (folders.length) {
|
|
109729
110072
|
const folder = folders.pop();
|
|
109730
|
-
const entries = await
|
|
110073
|
+
const entries = await readdir6(join25(worktreeCwd, folder), { withFileTypes: true });
|
|
109731
110074
|
for (const entry of entries) {
|
|
109732
110075
|
if (entry.isDirectory() && entry.name !== ".git" && entry.name !== "node_modules") {
|
|
109733
|
-
folders.push(
|
|
110076
|
+
folders.push(join25(folder, entry.name));
|
|
109734
110077
|
}
|
|
109735
110078
|
}
|
|
109736
110079
|
if (folder && !entries.some((e) => e.name === "package.json" && e.isFile())) continue;
|
|
109737
110080
|
if (entries.some((e) => e.name === "node_modules")) continue;
|
|
109738
|
-
const rel =
|
|
110081
|
+
const rel = join25(folder, "node_modules");
|
|
109739
110082
|
try {
|
|
109740
110083
|
const src = await resolveExisting(projectCwd, rel);
|
|
109741
|
-
if (!(await
|
|
109742
|
-
const manifest =
|
|
110084
|
+
if (!(await lstat5(src)).isDirectory()) continue;
|
|
110085
|
+
const manifest = join25(folder, "package.json");
|
|
109743
110086
|
const [source, target] = await Promise.all([
|
|
109744
|
-
resolveExisting(projectCwd, manifest).then((p) =>
|
|
109745
|
-
resolveExisting(worktreeCwd, manifest).then((p) =>
|
|
110087
|
+
resolveExisting(projectCwd, manifest).then((p) => readFile4(p)),
|
|
110088
|
+
resolveExisting(worktreeCwd, manifest).then((p) => readFile4(p))
|
|
109746
110089
|
]);
|
|
109747
110090
|
if (!source.equals(target)) continue;
|
|
109748
110091
|
const dst = await prepareCopyTarget(worktreeCwd, rel);
|
|
@@ -109764,8 +110107,8 @@ async function prepareWorktreeDependencies(projectCwd, worktreeCwd) {
|
|
|
109764
110107
|
}
|
|
109765
110108
|
|
|
109766
110109
|
// packages/agent-host/src/dev-services/file-search.ts
|
|
109767
|
-
import { readdir as
|
|
109768
|
-
import { join as
|
|
110110
|
+
import { readdir as readdir7 } from "node:fs/promises";
|
|
110111
|
+
import { join as join26 } from "node:path";
|
|
109769
110112
|
var MAX_FILES2 = 2e4;
|
|
109770
110113
|
var TTL_MS2 = 15e3;
|
|
109771
110114
|
var WALK_DEPTH = 6;
|
|
@@ -109793,7 +110136,7 @@ async function walk(root3) {
|
|
|
109793
110136
|
const { dir, rel, depth } = queue.shift();
|
|
109794
110137
|
let entries;
|
|
109795
110138
|
try {
|
|
109796
|
-
entries = await
|
|
110139
|
+
entries = await readdir7(dir, { withFileTypes: true });
|
|
109797
110140
|
} catch {
|
|
109798
110141
|
continue;
|
|
109799
110142
|
}
|
|
@@ -109801,7 +110144,7 @@ async function walk(root3) {
|
|
|
109801
110144
|
if (e.name.startsWith(".") && e.name !== ".env") continue;
|
|
109802
110145
|
if (e.isDirectory()) {
|
|
109803
110146
|
if (SKIP.has(e.name) || depth >= WALK_DEPTH) continue;
|
|
109804
|
-
queue.push({ dir:
|
|
110147
|
+
queue.push({ dir: join26(dir, e.name), rel: wireJoin(rel, e.name), depth: depth + 1 });
|
|
109805
110148
|
} else {
|
|
109806
110149
|
out.push(wireJoin(rel, e.name).normalize("NFC"));
|
|
109807
110150
|
if (out.length >= MAX_FILES2) break;
|
|
@@ -109866,14 +110209,14 @@ async function searchFiles(root3, query, limit = 20, trust = {}) {
|
|
|
109866
110209
|
}
|
|
109867
110210
|
|
|
109868
110211
|
// packages/agent-host/src/dev-services/attachments.ts
|
|
109869
|
-
import { mkdir as
|
|
109870
|
-
import { extname as extname3, join as
|
|
109871
|
-
var root = () =>
|
|
110212
|
+
import { mkdir as mkdir5, writeFile as writeFile3, rm as rm5 } from "node:fs/promises";
|
|
110213
|
+
import { extname as extname3, join as join27 } from "node:path";
|
|
110214
|
+
var root = () => join27(dataRoot(), "attachments");
|
|
109872
110215
|
function sessionDir(sessionId) {
|
|
109873
110216
|
if (!isSessionId(sessionId)) {
|
|
109874
110217
|
throw Object.assign(new Error(`Not a session id: ${sessionId}`), { code: "internal" });
|
|
109875
110218
|
}
|
|
109876
|
-
return
|
|
110219
|
+
return join27(root(), sessionId);
|
|
109877
110220
|
}
|
|
109878
110221
|
var EXT = {
|
|
109879
110222
|
"image/png": ".png",
|
|
@@ -109883,43 +110226,43 @@ var EXT = {
|
|
|
109883
110226
|
};
|
|
109884
110227
|
async function saveAttachment(sessionId, name, mime, dataBase64) {
|
|
109885
110228
|
const dir = sessionDir(sessionId);
|
|
109886
|
-
await
|
|
110229
|
+
await mkdir5(dir, { recursive: true });
|
|
109887
110230
|
const ext2 = extname3(name) || EXT[mime] || "";
|
|
109888
|
-
const file2 =
|
|
110231
|
+
const file2 = join27(dir, `${Date.now()}-${Math.random().toString(36).slice(2, 8)}${ext2}`);
|
|
109889
110232
|
const buf = Buffer.from(dataBase64, "base64");
|
|
109890
|
-
await
|
|
110233
|
+
await writeFile3(file2, buf);
|
|
109891
110234
|
return { kind: mime.startsWith("image/") ? "image" : "file", path: file2, name, mime, bytes: buf.length };
|
|
109892
110235
|
}
|
|
109893
110236
|
async function clearAttachments(sessionId) {
|
|
109894
|
-
await
|
|
110237
|
+
await rm5(sessionDir(sessionId), { recursive: true, force: true });
|
|
109895
110238
|
}
|
|
109896
110239
|
async function attachmentBytes(sessionId) {
|
|
109897
|
-
const { readdir:
|
|
110240
|
+
const { readdir: readdir10, lstat: lstat7 } = await import("node:fs/promises");
|
|
109898
110241
|
const dir = sessionDir(sessionId);
|
|
109899
|
-
const names = await
|
|
110242
|
+
const names = await readdir10(dir).catch(() => []);
|
|
109900
110243
|
let total = 0;
|
|
109901
110244
|
for (const name of names) {
|
|
109902
|
-
const s = await
|
|
110245
|
+
const s = await lstat7(join27(dir, name)).catch(() => null);
|
|
109903
110246
|
if (s?.isFile()) total += s.size;
|
|
109904
110247
|
}
|
|
109905
110248
|
return total;
|
|
109906
110249
|
}
|
|
109907
110250
|
var ATTACHMENTS_MAX_BYTES = 500 * 1048576;
|
|
109908
110251
|
async function sweepAttachments(maxBytes = ATTACHMENTS_MAX_BYTES) {
|
|
109909
|
-
const { readdir:
|
|
110252
|
+
const { readdir: readdir10, stat: stat7, rm: rmFile } = await import("node:fs/promises");
|
|
109910
110253
|
const files = [];
|
|
109911
110254
|
let dirs;
|
|
109912
110255
|
try {
|
|
109913
|
-
dirs = await
|
|
110256
|
+
dirs = await readdir10(root());
|
|
109914
110257
|
} catch {
|
|
109915
110258
|
return 0;
|
|
109916
110259
|
}
|
|
109917
110260
|
for (const d2 of dirs) {
|
|
109918
|
-
const dir =
|
|
109919
|
-
const names = await
|
|
110261
|
+
const dir = join27(root(), d2);
|
|
110262
|
+
const names = await readdir10(dir).catch(() => []);
|
|
109920
110263
|
for (const name of names) {
|
|
109921
|
-
const p =
|
|
109922
|
-
const s = await
|
|
110264
|
+
const p = join27(dir, name);
|
|
110265
|
+
const s = await stat7(p).catch(() => null);
|
|
109923
110266
|
if (s?.isFile()) files.push({ path: p, size: s.size, mtime: s.mtimeMs });
|
|
109924
110267
|
}
|
|
109925
110268
|
}
|
|
@@ -109939,7 +110282,7 @@ async function sweepAttachments(maxBytes = ATTACHMENTS_MAX_BYTES) {
|
|
|
109939
110282
|
|
|
109940
110283
|
// packages/agent-host/src/dev-services/message-image.ts
|
|
109941
110284
|
import { constants as constants7 } from "node:fs";
|
|
109942
|
-
import { open as
|
|
110285
|
+
import { open as open5, realpath as realpath7 } from "node:fs/promises";
|
|
109943
110286
|
import { homedir as homedir11 } from "node:os";
|
|
109944
110287
|
import { posix as posix3, sep as sep10, win32 as win326 } from "node:path";
|
|
109945
110288
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
@@ -109996,7 +110339,7 @@ var mb2 = (n) => `${(n / 1e6).toFixed(1)} MB`;
|
|
|
109996
110339
|
async function readMessageImage(abs, max = IMAGE_PREVIEW_MAX_BYTES) {
|
|
109997
110340
|
let file2;
|
|
109998
110341
|
try {
|
|
109999
|
-
file2 = await
|
|
110342
|
+
file2 = await realpath7(abs);
|
|
110000
110343
|
} catch (e) {
|
|
110001
110344
|
const code2 = e.code;
|
|
110002
110345
|
if (code2 === "EACCES" || code2 === "EPERM") return refuse5("unreadable", `Centralu may not read ${abs}`);
|
|
@@ -110004,7 +110347,7 @@ async function readMessageImage(abs, max = IMAGE_PREVIEW_MAX_BYTES) {
|
|
|
110004
110347
|
}
|
|
110005
110348
|
let handle;
|
|
110006
110349
|
try {
|
|
110007
|
-
handle = await
|
|
110350
|
+
handle = await open5(file2, OPEN_FLAGS);
|
|
110008
110351
|
} catch (e) {
|
|
110009
110352
|
const code2 = e.code;
|
|
110010
110353
|
if (code2 === "EACCES" || code2 === "EPERM") return refuse5("unreadable", `Centralu may not read ${file2}`, file2);
|
|
@@ -110035,36 +110378,36 @@ async function readMessageImage(abs, max = IMAGE_PREVIEW_MAX_BYTES) {
|
|
|
110035
110378
|
}
|
|
110036
110379
|
|
|
110037
110380
|
// packages/agent-host/src/dev-services/handoff-notes.ts
|
|
110038
|
-
import { lstat as
|
|
110039
|
-
import { join as
|
|
110040
|
-
var root2 = () =>
|
|
110381
|
+
import { lstat as lstat6, mkdir as mkdir6, readdir as readdir8, rm as rm6, writeFile as writeFile4 } from "node:fs/promises";
|
|
110382
|
+
import { join as join28 } from "node:path";
|
|
110383
|
+
var root2 = () => join28(dataRoot(), "handoff");
|
|
110041
110384
|
function handoffNoteDir(projectId) {
|
|
110042
110385
|
if (!isProjectId(projectId)) {
|
|
110043
110386
|
throw Object.assign(new Error(`Not a project id: ${projectId}`), { code: "internal" });
|
|
110044
110387
|
}
|
|
110045
|
-
return
|
|
110388
|
+
return join28(root2(), projectId);
|
|
110046
110389
|
}
|
|
110047
110390
|
function handoffNotePath(projectId, sessionId) {
|
|
110048
110391
|
if (!isSessionId(sessionId)) {
|
|
110049
110392
|
throw Object.assign(new Error(`Not a session id: ${sessionId}`), { code: "internal" });
|
|
110050
110393
|
}
|
|
110051
|
-
return
|
|
110394
|
+
return join28(handoffNoteDir(projectId), `${sessionId}.md`);
|
|
110052
110395
|
}
|
|
110053
110396
|
async function handoffNoteBytes(projectId, sessionId) {
|
|
110054
|
-
const s = await
|
|
110397
|
+
const s = await lstat6(handoffNotePath(projectId, sessionId)).catch(() => null);
|
|
110055
110398
|
return s?.isFile() ? s.size : 0;
|
|
110056
110399
|
}
|
|
110057
110400
|
async function writeHandoffNote(projectId, sessionId, text2) {
|
|
110058
110401
|
const path2 = handoffNotePath(projectId, sessionId);
|
|
110059
|
-
await
|
|
110060
|
-
await
|
|
110061
|
-
await
|
|
110402
|
+
await mkdir6(handoffNoteDir(projectId), { recursive: true });
|
|
110403
|
+
await rm6(path2, { force: true });
|
|
110404
|
+
await writeFile4(path2, text2, "utf8");
|
|
110062
110405
|
return path2;
|
|
110063
110406
|
}
|
|
110064
110407
|
async function sweepHandoffNotes(owned, projectId) {
|
|
110065
110408
|
let dirs;
|
|
110066
110409
|
try {
|
|
110067
|
-
dirs = projectId ? [projectId] : (await
|
|
110410
|
+
dirs = projectId ? [projectId] : (await readdir8(root2(), { withFileTypes: true })).filter((d2) => d2.isDirectory()).map((d2) => d2.name);
|
|
110068
110411
|
} catch {
|
|
110069
110412
|
return;
|
|
110070
110413
|
}
|
|
@@ -110075,12 +110418,12 @@ async function sweepHandoffNotes(owned, projectId) {
|
|
|
110075
110418
|
} catch {
|
|
110076
110419
|
continue;
|
|
110077
110420
|
}
|
|
110078
|
-
const entries = await
|
|
110421
|
+
const entries = await readdir8(dir, { withFileTypes: true }).catch(() => []);
|
|
110079
110422
|
for (const e of entries) {
|
|
110080
110423
|
if (!e.isFile() || !e.name.endsWith(".md")) continue;
|
|
110081
110424
|
const owner = e.name.slice(0, -".md".length);
|
|
110082
110425
|
if (owned(owner)) continue;
|
|
110083
|
-
await
|
|
110426
|
+
await rm6(join28(dir, e.name), { force: true }).catch(() => {
|
|
110084
110427
|
});
|
|
110085
110428
|
}
|
|
110086
110429
|
}
|
|
@@ -110154,7 +110497,7 @@ var HUB_SENDER = "_hub-orchestrator";
|
|
|
110154
110497
|
var builderKey = (ref) => `${ref.projectId ?? "_user"}/${ref.appId}`;
|
|
110155
110498
|
var APP_BUILDERS_KEY = "apps.builders";
|
|
110156
110499
|
var SessionManager = class {
|
|
110157
|
-
constructor(store2, adapters2, emit, endpoint, worktreeRoot =
|
|
110500
|
+
constructor(store2, adapters2, emit, endpoint, worktreeRoot = join29(homedir12(), DATA_DIR, "worktrees"), keeperOpts = {}) {
|
|
110158
110501
|
this.store = store2;
|
|
110159
110502
|
this.adapters = adapters2;
|
|
110160
110503
|
this.emit = emit;
|
|
@@ -110167,10 +110510,17 @@ var SessionManager = class {
|
|
|
110167
110510
|
rawEmit(withoutToolRecord(full));
|
|
110168
110511
|
};
|
|
110169
110512
|
const LIVE_ONLY = ["working", "waiting_approval"];
|
|
110170
|
-
|
|
110513
|
+
const storedSessions = store2.listSessions();
|
|
110514
|
+
const storedProjects = new Map(storedSessions.map((s) => [s.id, s.projectId]));
|
|
110515
|
+
for (const s of storedSessions) {
|
|
110171
110516
|
const stale = LIVE_ONLY.includes(s.state) && !keeperOpts.keptSessions?.has(s.id);
|
|
110172
110517
|
const fixed = stale ? { ...s, state: "idle", waitingSince: null } : s;
|
|
110518
|
+
const ownerProject = fixed.askedBy ? storedProjects.get(fixed.askedBy) ?? store2.trashedSession(fixed.askedBy)?.record.projectId : void 0;
|
|
110519
|
+
const first = fixed.askedBy && ownerProject === void 0 ? store2.loadMessagesFrom(fixed.id, 0, 1)[0]?.payload : void 0;
|
|
110520
|
+
const oldAsk = fixed.askedBy && (ownerProject !== void 0 ? ownerProject !== fixed.projectId : first?.from?.sessionId === fixed.askedBy && first.text?.includes("through Centralu's ask_project"));
|
|
110521
|
+
if (oldAsk) delete fixed.askedBy;
|
|
110173
110522
|
this.meta.set(s.id, fixed);
|
|
110523
|
+
if (oldAsk) store2.upsertSession(fixed);
|
|
110174
110524
|
if (stale) {
|
|
110175
110525
|
console.error(`[agent-host] stale state reset: ${s.id.slice(0, 8)} ${s.state} -> idle`);
|
|
110176
110526
|
store2.upsertSession(fixed);
|
|
@@ -110311,6 +110661,8 @@ var SessionManager = class {
|
|
|
110311
110661
|
* comment on `runAppAgent`).
|
|
110312
110662
|
*/
|
|
110313
110663
|
agentRuns = /* @__PURE__ */ new Map();
|
|
110664
|
+
/** Temporary readers of a person's turn; they never own work or pin the calling session awake. */
|
|
110665
|
+
turnObservers = /* @__PURE__ */ new Map();
|
|
110314
110666
|
/**
|
|
110315
110667
|
* A card the host raises in a session's approval slot — requestId -> question: an app's capability question (M4
|
|
110316
110668
|
* D-4), or one project reaching another (#371). It uses the same slot as the adapter's card (the session's
|
|
@@ -110319,8 +110671,8 @@ var SessionManager = class {
|
|
|
110319
110671
|
*/
|
|
110320
110672
|
hostAsks = /* @__PURE__ */ new Map();
|
|
110321
110673
|
/**
|
|
110322
|
-
* ask_project's delegations still owed to their caller (#371 part B) — `<caller session>><target project>` -> the
|
|
110323
|
-
* delegation. One per
|
|
110674
|
+
* ask_project's delegations still owed to their caller (#371 part B) — `<caller session>><target project>><target session>` -> the
|
|
110675
|
+
* delegation. One per chosen session at a time: a second task while the first runs is refused, and a call with no task waits
|
|
110324
110676
|
* on this one. Removed once the caller has read the outcome; an outcome nobody comes back for goes with the caller.
|
|
110325
110677
|
*/
|
|
110326
110678
|
delegations = /* @__PURE__ */ new Map();
|
|
@@ -110382,11 +110734,11 @@ var SessionManager = class {
|
|
|
110382
110734
|
);
|
|
110383
110735
|
}
|
|
110384
110736
|
async addProject(path2) {
|
|
110385
|
-
if (!
|
|
110737
|
+
if (!existsSync19(path2) || !statSync12(path2).isDirectory()) {
|
|
110386
110738
|
throw Object.assign(new Error(`Directory not found: ${path2}`), { code: "internal" });
|
|
110387
110739
|
}
|
|
110388
110740
|
const existing = this.store.findProjectByPath(path2);
|
|
110389
|
-
const id2 = existing?.id ??
|
|
110741
|
+
const id2 = existing?.id ?? randomUUID10();
|
|
110390
110742
|
this.store.addProject({ id: id2, path: path2, name: basename5(path2) });
|
|
110391
110743
|
return this.projectInfo(id2, path2);
|
|
110392
110744
|
}
|
|
@@ -110398,8 +110750,8 @@ var SessionManager = class {
|
|
|
110398
110750
|
projectsHome = () => {
|
|
110399
110751
|
throw Object.assign(new Error("This host has no default location for a new project \u2014 give a parent folder"), { code: "internal" });
|
|
110400
110752
|
};
|
|
110401
|
-
useProjectsHome(
|
|
110402
|
-
this.projectsHome =
|
|
110753
|
+
useProjectsHome(resolve12) {
|
|
110754
|
+
this.projectsHome = resolve12;
|
|
110403
110755
|
}
|
|
110404
110756
|
/**
|
|
110405
110757
|
* The folder a new project would get, and whether a clone source is one this host clones (#515) — checked, nothing
|
|
@@ -110900,6 +111252,9 @@ var SessionManager = class {
|
|
|
110900
111252
|
* null — the only place that can create a session with no project is `orchestrator()` below.
|
|
110901
111253
|
*/
|
|
110902
111254
|
async createSession(params) {
|
|
111255
|
+
if (params.askedBy && this.meta.get(params.askedBy)?.projectId !== params.projectId) {
|
|
111256
|
+
throw new Error("A session cannot create a session in another project");
|
|
111257
|
+
}
|
|
110903
111258
|
if (!params.tool) {
|
|
110904
111259
|
throw Object.assign(new Error("No agent adapter is available. Install and configure an agent CLI before creating a session."), { code: "tool_not_installed" });
|
|
110905
111260
|
}
|
|
@@ -110914,7 +111269,7 @@ var SessionManager = class {
|
|
|
110914
111269
|
);
|
|
110915
111270
|
}
|
|
110916
111271
|
}
|
|
110917
|
-
const id2 =
|
|
111272
|
+
const id2 = randomUUID10();
|
|
110918
111273
|
let worktree = null;
|
|
110919
111274
|
if (params.worktree && params.projectId) {
|
|
110920
111275
|
const summary = await gitSummary(params.cwd, this.gitTrust(params.projectId));
|
|
@@ -110927,7 +111282,7 @@ var SessionManager = class {
|
|
|
110927
111282
|
);
|
|
110928
111283
|
}
|
|
110929
111284
|
const path2 = this.worktreePathFor(params.projectId, id2);
|
|
110930
|
-
if (!this.projectTrusted(params.projectId)) {
|
|
111285
|
+
if (!this.projectTrusted(params.projectId) && !(params.askedBy && this.autoBoundaryOpen(params.askedBy, "worktree in an untrusted project"))) {
|
|
110931
111286
|
throw Object.assign(
|
|
110932
111287
|
new Error("Worktree sessions need a trusted project: checking out runs the repository's hooks and filters. Trust the project first."),
|
|
110933
111288
|
{ code: "internal" }
|
|
@@ -111278,14 +111633,14 @@ var SessionManager = class {
|
|
|
111278
111633
|
}
|
|
111279
111634
|
/** Tool calls the store holds for this session with no result after them (the newest 200 rows) */
|
|
111280
111635
|
openToolCalls(sessionId) {
|
|
111281
|
-
const
|
|
111636
|
+
const open6 = /* @__PURE__ */ new Map();
|
|
111282
111637
|
for (const r of this.store.loadMessages(sessionId, 200)) {
|
|
111283
111638
|
const p = r.payload;
|
|
111284
111639
|
if (typeof p?.callId !== "string") continue;
|
|
111285
|
-
if (r.kind === "tool_call" && typeof p.summary?.tool === "string")
|
|
111286
|
-
else if (r.kind === "tool_result")
|
|
111640
|
+
if (r.kind === "tool_call" && typeof p.summary?.tool === "string") open6.set(p.callId, p.summary.tool);
|
|
111641
|
+
else if (r.kind === "tool_result") open6.delete(p.callId);
|
|
111287
111642
|
}
|
|
111288
|
-
return [...
|
|
111643
|
+
return [...open6].map(([callId, tool]) => ({ callId, tool }));
|
|
111289
111644
|
}
|
|
111290
111645
|
/**
|
|
111291
111646
|
* Re-attaches the tool processes a previous host left running in the keeper (#280 step 2), so
|
|
@@ -111318,6 +111673,7 @@ var SessionManager = class {
|
|
|
111318
111673
|
);
|
|
111319
111674
|
}
|
|
111320
111675
|
async doResumeSession(sessionId, adopt) {
|
|
111676
|
+
if (this.worktreeCleanup.has(sessionId)) throw Object.assign(new Error("The session worktree is being removed"), { code: "internal" });
|
|
111321
111677
|
const m = this.meta.get(sessionId);
|
|
111322
111678
|
if (!m) throw Object.assign(new Error(`Session not found: ${sessionId}`), { code: "session_not_found" });
|
|
111323
111679
|
if (this.handles.has(sessionId)) return { session: m, resumed: true };
|
|
@@ -111506,6 +111862,7 @@ var SessionManager = class {
|
|
|
111506
111862
|
* exactly as it was, and only what happens from here on piles up in the copy.
|
|
111507
111863
|
*/
|
|
111508
111864
|
async forkConversation(sessionId) {
|
|
111865
|
+
if (this.worktreeCleanup.has(sessionId)) throw Object.assign(new Error("The session worktree is being removed"), { code: "internal" });
|
|
111509
111866
|
const m = this.meta.get(sessionId);
|
|
111510
111867
|
if (!m) throw Object.assign(new Error(`Session not found: ${sessionId}`), { code: "session_not_found" });
|
|
111511
111868
|
const source = m.externalId ?? m.importedFrom;
|
|
@@ -111538,6 +111895,7 @@ var SessionManager = class {
|
|
|
111538
111895
|
this.leaving.delete(sessionId);
|
|
111539
111896
|
}
|
|
111540
111897
|
}
|
|
111898
|
+
trashing = /* @__PURE__ */ new Map();
|
|
111541
111899
|
/**
|
|
111542
111900
|
* Moves a session to the trash (#204) — what deleting a session means now. Nothing is destroyed: the process
|
|
111543
111901
|
* stops, and the session leaves every list (sidebar, inbox, palette, grid, search, the agents' session tools and
|
|
@@ -111556,7 +111914,19 @@ var SessionManager = class {
|
|
|
111556
111914
|
* delete knows that layout (the Claude SDK's `deleteSession`). Left in place it is also a second way back — the
|
|
111557
111915
|
* tool, and **+ → Past conversations**, still have it.
|
|
111558
111916
|
*/
|
|
111559
|
-
async trashSession(sessionId, removeWorktree = false, removeExternal = false) {
|
|
111917
|
+
async trashSession(sessionId, removeWorktree = false, removeExternal = false, recovery) {
|
|
111918
|
+
const pending = this.trashing.get(sessionId);
|
|
111919
|
+
if (pending) return pending;
|
|
111920
|
+
const deleting = this.trashSessionOnce(sessionId, removeWorktree, removeExternal, recovery);
|
|
111921
|
+
this.trashing.set(sessionId, deleting);
|
|
111922
|
+
try {
|
|
111923
|
+
await deleting;
|
|
111924
|
+
} finally {
|
|
111925
|
+
this.trashing.delete(sessionId);
|
|
111926
|
+
}
|
|
111927
|
+
}
|
|
111928
|
+
async trashSessionOnce(sessionId, removeWorktree, removeExternal, recovery) {
|
|
111929
|
+
if (this.worktreeCleanup.has(sessionId) && !recovery) throw Object.assign(new Error("The session worktree is being removed"), { code: "internal" });
|
|
111560
111930
|
const m = this.meta.get(sessionId);
|
|
111561
111931
|
if (!m) {
|
|
111562
111932
|
this.emit({ type: "session_deleted", sessionId });
|
|
@@ -111572,6 +111942,7 @@ var SessionManager = class {
|
|
|
111572
111942
|
agentRun.deleted = true;
|
|
111573
111943
|
agentRun.fail(new Error("the person deleted the agent session before it answered"));
|
|
111574
111944
|
}
|
|
111945
|
+
for (const observer of this.turnObservers.get(sessionId) ?? []) observer.fail(new Error("the person deleted the session before it answered"));
|
|
111575
111946
|
for (const [key, d2] of this.delegations) {
|
|
111576
111947
|
if (d2.callerId !== sessionId) continue;
|
|
111577
111948
|
this.delegations.delete(key);
|
|
@@ -111605,7 +111976,8 @@ var SessionManager = class {
|
|
|
111605
111976
|
projectName: project2?.name ?? null,
|
|
111606
111977
|
projectPath: project2?.path ?? null,
|
|
111607
111978
|
removeExternal: removeExternal && !!externalId,
|
|
111608
|
-
removeWorktree: removeWorktree && !!m.worktree
|
|
111979
|
+
removeWorktree: removeWorktree && !!m.worktree,
|
|
111980
|
+
...recovery ? { recovery } : {}
|
|
111609
111981
|
});
|
|
111610
111982
|
this.appsHub?.sessionGone(sessionId);
|
|
111611
111983
|
this.emit({ type: "session_deleted", sessionId });
|
|
@@ -111613,7 +111985,7 @@ var SessionManager = class {
|
|
|
111613
111985
|
/** Only one trash operation per session at a time — a purge racing a restore would restore half of a deleted session */
|
|
111614
111986
|
trashBusy = /* @__PURE__ */ new Set();
|
|
111615
111987
|
async trashOp(sessionId, run) {
|
|
111616
|
-
if (this.trashBusy.has(sessionId)) {
|
|
111988
|
+
if (this.trashBusy.has(sessionId) || this.worktreeCleanup.has(sessionId)) {
|
|
111617
111989
|
throw Object.assign(new Error("That session is already being restored or deleted"), { code: "internal" });
|
|
111618
111990
|
}
|
|
111619
111991
|
this.trashBusy.add(sessionId);
|
|
@@ -111630,7 +112002,7 @@ var SessionManager = class {
|
|
|
111630
112002
|
this.store.listTrash().map(async (r) => {
|
|
111631
112003
|
const { record: record2 } = r;
|
|
111632
112004
|
const live = record2.projectId ? projects.get(record2.projectId) : void 0;
|
|
111633
|
-
const files = await attachmentBytes(r.id).catch(() => 0) + (record2.projectId ? await handoffNoteBytes(record2.projectId, r.id).catch(() => 0) : 0);
|
|
112005
|
+
const files = await attachmentBytes(r.id).catch(() => 0) + await worktreeRecoveryBytes(r.id).catch(() => 0) + (record2.projectId ? await handoffNoteBytes(record2.projectId, r.id).catch(() => 0) : 0);
|
|
111634
112006
|
return {
|
|
111635
112007
|
id: r.id,
|
|
111636
112008
|
name: r.name,
|
|
@@ -111646,7 +112018,7 @@ var SessionManager = class {
|
|
|
111646
112018
|
bytes: r.bytes + files,
|
|
111647
112019
|
conversationFile: !r.hasConversationFile ? "none" : record2.removeExternal ? "remove" : "keep",
|
|
111648
112020
|
// Only a worktree still on disk is listed — the merged-worktree cleanup removes the folder before the trash is emptied
|
|
111649
|
-
worktree: r.worktree &&
|
|
112021
|
+
worktree: r.worktree && existsSync19(r.worktree.path) ? { ...r.worktree, remove: record2.removeWorktree } : null
|
|
111650
112022
|
};
|
|
111651
112023
|
})
|
|
111652
112024
|
);
|
|
@@ -111684,7 +112056,7 @@ var SessionManager = class {
|
|
|
111684
112056
|
const home = projects.find((p) => p.id === record2.projectId) ?? (record2.projectPath ? projects.find((p) => p.path === record2.projectPath) : void 0);
|
|
111685
112057
|
if (home) {
|
|
111686
112058
|
projectId = home.id;
|
|
111687
|
-
} else if (record2.projectPath &&
|
|
112059
|
+
} else if (record2.projectPath && existsSync19(record2.projectPath) && statSync12(record2.projectPath).isDirectory()) {
|
|
111688
112060
|
this.store.addProject({
|
|
111689
112061
|
id: record2.projectId,
|
|
111690
112062
|
path: record2.projectPath,
|
|
@@ -111703,14 +112075,21 @@ var SessionManager = class {
|
|
|
111703
112075
|
} else if (t.session.kind === "orchestrator" && this.store.orchestratorId()) {
|
|
111704
112076
|
throw Object.assign(new Error("Another orchestrator is in place \u2014 the app runs one at a time"), { code: "internal" });
|
|
111705
112077
|
}
|
|
112078
|
+
let recovered;
|
|
112079
|
+
if (record2.recovery && t.session.worktree && projectId) {
|
|
112080
|
+
const repo = this.store.listProjects().find((p) => p.id === projectId).path;
|
|
112081
|
+
recovered = await restoreWorktreeRecovery(sessionId, repo, t.session.worktree.path, t.session.worktree.branch, record2.recovery, this.gitTrust(projectId));
|
|
112082
|
+
}
|
|
111706
112083
|
const back = await this.store.restoreSession(sessionId, projectId);
|
|
111707
112084
|
if (!back) throw Object.assign(new Error(`Not in the trash: ${sessionId}`), { code: "session_not_found" });
|
|
111708
112085
|
const LIVE_ONLY = ["working", "waiting_approval"];
|
|
111709
112086
|
const fixed = LIVE_ONLY.includes(back.state) ? { ...back, state: "idle", waitingSince: null } : back;
|
|
111710
|
-
if (
|
|
112087
|
+
if (recovered && fixed.worktree) fixed.worktree = { ...fixed.worktree, branch: recovered.branch };
|
|
112088
|
+
if (fixed !== back || recovered) this.store.upsertSession(fixed);
|
|
111711
112089
|
this.meta.set(sessionId, fixed);
|
|
111712
112090
|
const session2 = { ...this.meta.get(sessionId), live: false };
|
|
111713
112091
|
this.emit({ type: "session_created", sessionId, session: session2 });
|
|
112092
|
+
if (recovered) this.onEvent({ type: "notice", sessionId, level: "info", from: "Centralu", label: "worktree recovery", text: recovered.notice });
|
|
111714
112093
|
return { session: session2, project: registered };
|
|
111715
112094
|
});
|
|
111716
112095
|
}
|
|
@@ -111749,6 +112128,7 @@ var SessionManager = class {
|
|
|
111749
112128
|
await gitWorktreeRemove(record2.projectPath, s.worktree.path, true).catch(() => {
|
|
111750
112129
|
});
|
|
111751
112130
|
}
|
|
112131
|
+
await clearWorktreeRecovery(sessionId);
|
|
111752
112132
|
await this.store.purgeSession(sessionId);
|
|
111753
112133
|
await clearAttachments(sessionId).catch(() => {
|
|
111754
112134
|
});
|
|
@@ -111821,6 +112201,7 @@ var SessionManager = class {
|
|
|
111821
112201
|
return live && m && live.model !== m.model ? "model_change" : void 0;
|
|
111822
112202
|
}
|
|
111823
112203
|
async updateSettings(sessionId, s) {
|
|
112204
|
+
if (this.worktreeCleanup.has(sessionId)) throw Object.assign(new Error("The session worktree is being removed"), { code: "internal" });
|
|
111824
112205
|
const m = this.meta.get(sessionId);
|
|
111825
112206
|
if (!m) throw Object.assign(new Error(`Session not found: ${sessionId}`), { code: "session_not_found" });
|
|
111826
112207
|
if (s.model !== void 0 && s.model !== m.model) this.cacheLedger.note(sessionId, "model_change");
|
|
@@ -111937,6 +112318,7 @@ var SessionManager = class {
|
|
|
111937
112318
|
}
|
|
111938
112319
|
const run = e.sessionId ? this.agentRuns.get(e.sessionId) : void 0;
|
|
111939
112320
|
run?.onEvent(e);
|
|
112321
|
+
if (e.sessionId) for (const observer of this.turnObservers.get(e.sessionId) ?? []) observer.onEvent(e);
|
|
111940
112322
|
this.emit(seq != null ? { ...e, seq } : e);
|
|
111941
112323
|
if (endedTurn && e.sessionId && !run?.tracksInputs) void this.reportBackIfAwaited(e.sessionId, e, run?.completedAnswer);
|
|
111942
112324
|
if (e.type === "approval_resolved" && e.sessionId) this.raiseHostAsks(e.sessionId);
|
|
@@ -112318,6 +112700,7 @@ If you need more, you can read that session's recent conversation with read_sess
|
|
|
112318
112700
|
async deliver(sessionId, text2, attachments, from, relayed, fromApp, fromAppVia = "inline", sendInput) {
|
|
112319
112701
|
const restarting = this.deferredRestarts.get(sessionId);
|
|
112320
112702
|
if (restarting) await restarting;
|
|
112703
|
+
if (this.worktreeCleanup.has(sessionId)) throw Object.assign(new Error("The session worktree is being removed"), { code: "internal" });
|
|
112321
112704
|
const m = this.meta.get(sessionId);
|
|
112322
112705
|
if (!m) throw Object.assign(new Error(`Session not found: ${sessionId}`), { code: "session_not_found" });
|
|
112323
112706
|
if (!this.handles.has(sessionId)) {
|
|
@@ -112328,6 +112711,7 @@ If you need more, you can read that session's recent conversation with read_sess
|
|
|
112328
112711
|
});
|
|
112329
112712
|
}
|
|
112330
112713
|
}
|
|
112714
|
+
if (this.worktreeCleanup.has(sessionId)) throw Object.assign(new Error("The session worktree is being removed"), { code: "internal" });
|
|
112331
112715
|
const h = this.requireHandle(sessionId);
|
|
112332
112716
|
this.closeStream(sessionId);
|
|
112333
112717
|
const seq = this.store.nextSeq(sessionId);
|
|
@@ -112382,6 +112766,7 @@ ${attachments.map((a) => `@${a.path}`).join("\n")}` : text2;
|
|
|
112382
112766
|
* untouched, and only the tool underneath it is swapped.
|
|
112383
112767
|
*/
|
|
112384
112768
|
async switchTool(sessionId, tool) {
|
|
112769
|
+
if (this.worktreeCleanup.has(sessionId)) throw Object.assign(new Error("The session worktree is being removed"), { code: "internal" });
|
|
112385
112770
|
const m = this.meta.get(sessionId);
|
|
112386
112771
|
if (!m) throw Object.assign(new Error(`Session not found: ${sessionId}`), { code: "session_not_found" });
|
|
112387
112772
|
if (m.tool === tool) return { ...m, live: this.handles.has(sessionId) };
|
|
@@ -112663,7 +113048,7 @@ ${attachments.map((a) => `@${a.path}`).join("\n")}` : text2;
|
|
|
112663
113048
|
worktreePathFor(projectId, sessionId) {
|
|
112664
113049
|
if (!isProjectId(projectId)) throw Object.assign(new Error(`Not a project id: ${projectId}`), { code: "internal" });
|
|
112665
113050
|
if (!isSessionId(sessionId)) throw Object.assign(new Error(`Not a session id: ${sessionId}`), { code: "internal" });
|
|
112666
|
-
return
|
|
113051
|
+
return join29(this.worktreeRoot, projectId, sessionId);
|
|
112667
113052
|
}
|
|
112668
113053
|
/**
|
|
112669
113054
|
* Sets up a new worktree's workspace (#69): copy configured files, reuse JS dependencies, run setup.
|
|
@@ -112846,7 +113231,7 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
112846
113231
|
const base2 = this.cwdFor(m);
|
|
112847
113232
|
const paths = /* @__PURE__ */ new Set();
|
|
112848
113233
|
for (const p of e.paths) {
|
|
112849
|
-
const rel = relative8(root3,
|
|
113234
|
+
const rel = relative8(root3, resolve11(base2, p));
|
|
112850
113235
|
if (!rel || rel === ".." || rel.startsWith(`..${sep11}`) || isAbsolute9(rel)) continue;
|
|
112851
113236
|
paths.add(rel.replaceAll(sep11, "/"));
|
|
112852
113237
|
}
|
|
@@ -113046,6 +113431,7 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
113046
113431
|
* reports background work.
|
|
113047
113432
|
*/
|
|
113048
113433
|
sessionIdle(sessionId) {
|
|
113434
|
+
if (this.worktreeCleanup.has(sessionId)) return { idle: false, reason: "turn" };
|
|
113049
113435
|
const m = this.meta.get(sessionId);
|
|
113050
113436
|
if (!m) throw Object.assign(new Error(`Session not found: ${sessionId}`), { code: "session_not_found" });
|
|
113051
113437
|
if (this.agentRuns.get(sessionId)?.waitingForInputs) return { idle: false, reason: "turn" };
|
|
@@ -113056,7 +113442,7 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
113056
113442
|
}
|
|
113057
113443
|
/**
|
|
113058
113444
|
* The sessions whose running work this one waits on (#568, the sleep rule in sleep.ts): a child it started with
|
|
113059
|
-
* `start_agent`, or a session
|
|
113445
|
+
* `start_agent`, or a session handling its ask_project task, while the turn it gave them runs, and a session that owes
|
|
113060
113446
|
* it a report (`reportBack`, `awaitingReport`). Their answer is about to come back to it.
|
|
113061
113447
|
*/
|
|
113062
113448
|
waitsOn(sessionId) {
|
|
@@ -113099,9 +113485,10 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
113099
113485
|
const handle = this.requireHandle(sessionId);
|
|
113100
113486
|
const run = this.agentRuns.get(sessionId);
|
|
113101
113487
|
if (run) run.stoppedByPerson = true;
|
|
113488
|
+
for (const observer of this.turnObservers.get(sessionId) ?? []) observer.stoppedByPerson = true;
|
|
113102
113489
|
handle.interrupt();
|
|
113103
113490
|
for (const d2 of this.delegations.values()) {
|
|
113104
|
-
if (d2.callerId === sessionId && (
|
|
113491
|
+
if (d2.callerId === sessionId && (d2.waiters ?? 0) > 0) d2.stop("the person stopped the session that asked");
|
|
113105
113492
|
}
|
|
113106
113493
|
}
|
|
113107
113494
|
/**
|
|
@@ -113110,6 +113497,7 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
113110
113497
|
* process.
|
|
113111
113498
|
*/
|
|
113112
113499
|
async restartSession(sessionId, cause2) {
|
|
113500
|
+
if (this.worktreeCleanup.has(sessionId)) throw Object.assign(new Error("The session worktree is being removed"), { code: "internal" });
|
|
113113
113501
|
if (cause2) {
|
|
113114
113502
|
this.restartReasons.set(sessionId, cause2);
|
|
113115
113503
|
if (cause2 === "launch_settings") console.error(`[sessions] restarting ${sessionId}: launch settings changed or previously unrecorded`);
|
|
@@ -113717,10 +114105,11 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
113717
114105
|
kind: "project_access",
|
|
113718
114106
|
access: access2,
|
|
113719
114107
|
from: { id: from.id, name: from.name },
|
|
113720
|
-
// Said on the card (#382):
|
|
114108
|
+
// Said on the card (#382): the selected session there follows its trust, so the person should know before allowing
|
|
113721
114109
|
to: { id: to.id, name: to.name, trusted: to.trusted },
|
|
113722
114110
|
text: what.text,
|
|
113723
|
-
...what.app ? { app: what.app } : {}
|
|
114111
|
+
...what.app ? { app: what.app } : {},
|
|
114112
|
+
...what.session ? { session: what.session } : {}
|
|
113724
114113
|
},
|
|
113725
114114
|
signal
|
|
113726
114115
|
);
|
|
@@ -113735,20 +114124,26 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
113735
114124
|
}
|
|
113736
114125
|
return { ok: false, error: `The question to the person was withdrawn before they answered, so ${to.name} was not reached.` };
|
|
113737
114126
|
}
|
|
113738
|
-
/**
|
|
113739
|
-
|
|
113740
|
-
|
|
113741
|
-
|
|
114127
|
+
/** The caller's own preset, read at the boundary, never its project's or parent's preset. */
|
|
114128
|
+
autoBoundaryOpen(sessionId, boundary) {
|
|
114129
|
+
if (this.meta.get(sessionId)?.permissionPreset !== "auto") return false;
|
|
114130
|
+
if (boundary) console.error(`[permissions] auto session ${sessionId}: ${boundary} allowed`);
|
|
114131
|
+
return true;
|
|
114132
|
+
}
|
|
114133
|
+
/** Auto allows and logs; other presets raise a host card, withdrawn if the signal fires. */
|
|
113742
114134
|
askHost(sessionId, detail, signal) {
|
|
113743
|
-
return
|
|
113744
|
-
|
|
113745
|
-
|
|
113746
|
-
|
|
114135
|
+
if (signal?.aborted) return Promise.resolve(null);
|
|
114136
|
+
const note = detail.kind === "project_access" && !detail.to.trusted ? " (target not trusted)" : "";
|
|
114137
|
+
if (this.autoBoundaryOpen(sessionId, `${detail.kind}${note}`)) return Promise.resolve("allow");
|
|
114138
|
+
return new Promise((resolve12) => {
|
|
114139
|
+
if (signal?.aborted) return resolve12(null);
|
|
114140
|
+
const requestId = `${detail.kind === "project_access" ? "xp" : detail.kind === "agent_start" ? "ag" : detail.kind === "centralu_update" ? "up" : "mk"}-${randomUUID10()}`;
|
|
114141
|
+
this.hostAsks.set(requestId, { requestId, sessionId, detail, shown: false, resolve: resolve12 });
|
|
113747
114142
|
signal?.addEventListener("abort", () => {
|
|
113748
114143
|
if (!this.hostAsks.delete(requestId)) return;
|
|
113749
114144
|
const m = this.meta.get(sessionId);
|
|
113750
114145
|
if (m?.pendingApproval?.requestId === requestId) this.onEvent({ type: "approval_resolved", sessionId, requestId, decision: "deny" });
|
|
113751
|
-
|
|
114146
|
+
resolve12(null);
|
|
113752
114147
|
}, { once: true });
|
|
113753
114148
|
this.raiseHostAsks(sessionId);
|
|
113754
114149
|
});
|
|
@@ -113780,25 +114175,24 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
113780
114175
|
* and a session that was itself asked by another project cannot ask a third: depth one, so two projects
|
|
113781
114176
|
* that consented to each other cannot bounce a task between them without a person.
|
|
113782
114177
|
* 2. The person's consent for the pair (`ensureProjectAccess`, kind 'delegate').
|
|
113783
|
-
* 3.
|
|
113784
|
-
*
|
|
113785
|
-
* gets, so its own approvals stand as cards in that session, where the person sees them. Marked `askedBy`.
|
|
114178
|
+
* 3. An existing ordinary session in the target, named by name or id; no creation or ownership change.
|
|
114179
|
+
* Without a name exactly one is selected, otherwise its sessions are listed for the caller to choose.
|
|
113786
114180
|
* 4. The task goes in a frame (`askFrame`) marked as sent by the caller, and the call waits for the turn, up to
|
|
113787
114181
|
* `ASK_WAIT_MS`. Past it the answer is "still working" and a call with no task waits again.
|
|
113788
114182
|
* 5. The final answer comes back cut to size, and the paths it names inside the target project become readable
|
|
113789
114183
|
* to the caller (`readableGrants`).
|
|
113790
114184
|
*
|
|
113791
114185
|
* Stop on the caller, the call's own signal, or the person deleting either session ends the wait; the first two
|
|
113792
|
-
* stop the
|
|
114186
|
+
* stop an assigned idle target only while waiting; a task that joined the person's turn only ends the wait.
|
|
113793
114187
|
*/
|
|
113794
114188
|
async askProject(callerId, opts, signal) {
|
|
113795
114189
|
const caller = this.meta.get(callerId);
|
|
113796
114190
|
if (!caller?.projectId) return { ok: false, error: "This session belongs to no project, so it cannot ask another one" };
|
|
113797
|
-
if (caller.askedBy) {
|
|
114191
|
+
if ((caller.askedBy || [...this.delegations.values()].some((d8) => d8.sessionId === callerId && !d8.agent && !d8.settled)) && !this.autoBoundaryOpen(callerId, "delegation depth")) {
|
|
113798
114192
|
return { ok: false, error: "This session was itself started or asked by another session, so it cannot ask a project \u2014 say in your answer what is needed instead." };
|
|
113799
114193
|
}
|
|
113800
114194
|
const projects = this.store.listProjects();
|
|
113801
|
-
if (projects.find((p) => p.id === caller.projectId)?.trusted !== true) {
|
|
114195
|
+
if (projects.find((p) => p.id === caller.projectId)?.trusted !== true && !this.autoBoundaryOpen(callerId, "delegate from an untrusted project")) {
|
|
113802
114196
|
return { ok: false, error: "This session's project is not trusted, so it cannot ask another project \u2014 the person can trust it from the project menu." };
|
|
113803
114197
|
}
|
|
113804
114198
|
const wanted = opts.project.trim();
|
|
@@ -113811,62 +114205,88 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
113811
114205
|
};
|
|
113812
114206
|
}
|
|
113813
114207
|
if (target.id === caller.projectId) return { ok: false, error: `${target.name} is this session's own project \u2014 do the work here.` };
|
|
113814
|
-
const
|
|
114208
|
+
const candidates = [...this.meta.values()].filter(
|
|
114209
|
+
(s) => s.projectId === target.id && s.kind === "worker" && !s.appId && !this.builderRefOf(s)
|
|
114210
|
+
);
|
|
114211
|
+
const list = () => candidates.map((s) => JSON.stringify({
|
|
114212
|
+
id: s.id,
|
|
114213
|
+
name: s.name,
|
|
114214
|
+
state: s.state,
|
|
114215
|
+
lastActive: lastActiveOf(this.store, s.id) ?? null
|
|
114216
|
+
})).join("\n");
|
|
114217
|
+
const wantedSession = opts.session?.trim();
|
|
114218
|
+
const outstanding = [...this.delegations.entries()].filter(
|
|
114219
|
+
([, d8]) => d8.callerId === callerId && d8.target.id === target.id && !d8.agent
|
|
114220
|
+
);
|
|
114221
|
+
if (!opts.task && !wantedSession && outstanding.length === 1) {
|
|
114222
|
+
const [key2, pending2] = outstanding[0];
|
|
114223
|
+
return this.awaitDelegation(key2, pending2, signal);
|
|
114224
|
+
}
|
|
114225
|
+
const named = wantedSession ? candidates.filter((s) => s.name.toLowerCase() === wantedSession.toLowerCase()) : candidates;
|
|
114226
|
+
const chosen = candidates.find((s) => s.id === wantedSession) ?? (named.length === 1 ? named[0] : void 0);
|
|
114227
|
+
if (!chosen) {
|
|
114228
|
+
return { ok: false, error: candidates.length ? `${wantedSession ? `No unique ordinary session named "${wantedSession}" in ${target.name}. ` : ""}Name a session with the session argument. Sessions (name, state, last active):
|
|
114229
|
+
${list()}` : `${target.name} has no ordinary session. The person has to open one there; this call cannot create one.` };
|
|
114230
|
+
}
|
|
114231
|
+
const key = `${callerId}>${target.id}>${chosen.id}`;
|
|
113815
114232
|
const pending = this.delegations.get(key);
|
|
113816
114233
|
if (pending && !opts.task) return this.awaitDelegation(key, pending, signal);
|
|
113817
114234
|
if (pending && !pending.settled) {
|
|
113818
|
-
return {
|
|
113819
|
-
|
|
113820
|
-
|
|
113821
|
-
|
|
113822
|
-
|
|
113823
|
-
|
|
113824
|
-
|
|
113825
|
-
|
|
114235
|
+
return { ok: false, error: `${target.name} is still working on your last task in the session "${chosen.name}". Call ask_project with project "${target.name}", session "${chosen.id}" and no task to wait for it.` };
|
|
114236
|
+
}
|
|
114237
|
+
if (!opts.task) return { ok: false, error: `Give a task: what ${target.name} / ${chosen.name} should do.` };
|
|
114238
|
+
const available = () => {
|
|
114239
|
+
const current = this.meta.get(chosen.id);
|
|
114240
|
+
if (!current || current.projectId !== target.id || current.kind !== "worker" || current.appId || this.builderRefOf(current)) return "The chosen session is no longer available";
|
|
114241
|
+
if (this.agentRuns.has(chosen.id) && !this.agentRuns.get(chosen.id).finished) return `"${current.name}" is busy (${current.state}) with another delegated task. Wait for it.`;
|
|
114242
|
+
if (inTurn(current.state) && (!this.adapters.get(current.tool)?.capabilities.midTurnInput || !this.handles.get(current.id)?.sendTracked)) return `"${current.name}" is busy (${current.state}); its CLI cannot take a mid-turn note. Try again later.`;
|
|
114243
|
+
};
|
|
114244
|
+
const busy = available();
|
|
114245
|
+
if (busy) return { ok: false, error: busy };
|
|
114246
|
+
const consent = await this.ensureProjectAccess(callerId, target.id, "delegate", {
|
|
114247
|
+
text: taskLine(opts.task),
|
|
114248
|
+
session: { id: chosen.id, name: chosen.name }
|
|
114249
|
+
}, signal);
|
|
113826
114250
|
if (!consent.ok) return consent;
|
|
113827
|
-
const
|
|
113828
|
-
|
|
113829
|
-
if (
|
|
113830
|
-
|
|
113831
|
-
|
|
113832
|
-
|
|
113833
|
-
|
|
113834
|
-
|
|
113835
|
-
const found = await adapter.detect();
|
|
113836
|
-
if (!found.installed || !found.loggedIn) {
|
|
113837
|
-
return { ok: false, error: `${target.name}'s agent (${adapter.descriptor.label}) cannot take the task: ${found.detail}` };
|
|
113838
|
-
}
|
|
113839
|
-
try {
|
|
113840
|
-
const remembered = this.store.projectToolDefaults(target.id)[tool];
|
|
113841
|
-
const info = await this.createSession({
|
|
113842
|
-
projectId: target.id,
|
|
113843
|
-
cwd: target.path,
|
|
113844
|
-
tool,
|
|
113845
|
-
...remembered?.model ? { model: remembered.model } : {},
|
|
113846
|
-
...remembered?.effort ? { effort: remembered.effort } : {},
|
|
113847
|
-
permissionPreset: "normal",
|
|
113848
|
-
askedBy: callerId
|
|
113849
|
-
});
|
|
113850
|
-
delegatedId = info.id;
|
|
113851
|
-
} catch (e) {
|
|
113852
|
-
return { ok: false, error: `Could not open a session in ${target.name}: ${e.message}` };
|
|
113853
|
-
}
|
|
113854
|
-
const callerProject2 = projects.find((p) => p.id === caller.projectId)?.name ?? "another project";
|
|
113855
|
-
this.rename(delegatedId, `Asked by ${callerProject2} \xB7 ${(/* @__PURE__ */ new Date()).toTimeString().slice(0, 5)}`);
|
|
113856
|
-
}
|
|
113857
|
-
const d2 = this.startDelegation(key, callerId, delegatedId, target);
|
|
114251
|
+
const changed = available();
|
|
114252
|
+
if (changed) return { ok: false, error: changed };
|
|
114253
|
+
if (signal?.aborted) return { ok: false, error: "The call was cancelled before delivery" };
|
|
114254
|
+
if (pending) this.delegations.delete(key);
|
|
114255
|
+
const delegatedId = chosen.id;
|
|
114256
|
+
const midTurn = inTurn(this.meta.get(delegatedId).state);
|
|
114257
|
+
let releaseInput;
|
|
114258
|
+
const d2 = this.startDelegation(key, callerId, delegatedId, target, false, midTurn);
|
|
113858
114259
|
const callerProject = projects.find((p) => p.id === caller.projectId)?.name ?? "another project";
|
|
113859
114260
|
try {
|
|
113860
|
-
|
|
114261
|
+
const from = { sessionId: callerId, name: labelOf(this.store, caller) };
|
|
114262
|
+
if (midTurn) {
|
|
114263
|
+
const releaseHold = d2.wait.holdInput();
|
|
114264
|
+
const release = () => {
|
|
114265
|
+
releaseHold();
|
|
114266
|
+
this.applyDeferredRestart(delegatedId);
|
|
114267
|
+
};
|
|
114268
|
+
releaseInput = release;
|
|
114269
|
+
await this.deliver(delegatedId, askFrame(callerProject, opts.task), void 0, from, false, void 0, "inline", (h, input) => {
|
|
114270
|
+
const fail4 = (e) => {
|
|
114271
|
+
d2.wait.fail(e);
|
|
114272
|
+
release();
|
|
114273
|
+
};
|
|
114274
|
+
void h.sendTracked(input, { complete: release, fail: fail4 }).then(release, fail4);
|
|
114275
|
+
});
|
|
114276
|
+
} else {
|
|
114277
|
+
await this.deliver(delegatedId, askFrame(callerProject, opts.task), void 0, from, false);
|
|
114278
|
+
}
|
|
113861
114279
|
} catch (e) {
|
|
113862
|
-
|
|
114280
|
+
d2.wait.fail(e);
|
|
114281
|
+
releaseInput?.();
|
|
114282
|
+
if (this.delegations.get(key) === d2) this.delegations.delete(key);
|
|
113863
114283
|
if (this.agentRuns.get(delegatedId) === d2.wait) this.agentRuns.delete(delegatedId);
|
|
113864
114284
|
return { ok: false, error: `Could not hand the task to ${target.name}: ${e.message}` };
|
|
113865
114285
|
}
|
|
113866
114286
|
return this.awaitDelegation(key, d2, signal);
|
|
113867
114287
|
}
|
|
113868
114288
|
/** Registers the wait on the delegated session's turn (the same watcher an app's agent uses, `AgentRunWait`) */
|
|
113869
|
-
startDelegation(key, callerId, sessionId, target, agent = false) {
|
|
114289
|
+
startDelegation(key, callerId, sessionId, target, agent = false, joinedTurn = false) {
|
|
113870
114290
|
let notice;
|
|
113871
114291
|
let answerAfter = this.meta.get(sessionId)?.lastSeq ?? 0;
|
|
113872
114292
|
const wait = new AgentRunWait((message) => notice = message, () => this.meta.get(sessionId)?.name ?? "the session", void 0, () => {
|
|
@@ -113874,7 +114294,13 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
113874
114294
|
answerAfter = this.meta.get(sessionId)?.lastSeq ?? answerAfter;
|
|
113875
114295
|
return answer;
|
|
113876
114296
|
});
|
|
114297
|
+
let cancelWait;
|
|
114298
|
+
const waitCancelled = new Promise((resolve12) => {
|
|
114299
|
+
cancelWait = resolve12;
|
|
114300
|
+
});
|
|
113877
114301
|
const d2 = {
|
|
114302
|
+
joinedTurn,
|
|
114303
|
+
waitCancelled,
|
|
113878
114304
|
callerId,
|
|
113879
114305
|
sessionId,
|
|
113880
114306
|
target,
|
|
@@ -113887,8 +114313,12 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
113887
114313
|
(r) => ({ ok: true, answer: r.answer }),
|
|
113888
114314
|
(e) => ({ ok: false, error: e.message })
|
|
113889
114315
|
),
|
|
113890
|
-
stop: (why) => {
|
|
114316
|
+
stop: (why, targetStop = false) => {
|
|
113891
114317
|
if (d2.settled) return;
|
|
114318
|
+
if (joinedTurn && !targetStop) {
|
|
114319
|
+
cancelWait({ ok: false, error: `${why}; the caller stopped waiting and the target session was left running` });
|
|
114320
|
+
return;
|
|
114321
|
+
}
|
|
113892
114322
|
wait.fail(new Error(`${why}, so the delegated turn was stopped`));
|
|
113893
114323
|
try {
|
|
113894
114324
|
this.handles.get(sessionId)?.interrupt();
|
|
@@ -113899,31 +114329,79 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
113899
114329
|
};
|
|
113900
114330
|
void d2.outcome.then((r) => {
|
|
113901
114331
|
if (this.agentRuns.get(sessionId) === wait) this.agentRuns.delete(sessionId);
|
|
113902
|
-
if (agent) void this.reportBackIfAwaited(sessionId, r.ok ? { type: "turn_complete", sessionId } : { type: "error", sessionId, error: { code: "internal", message: r.error, retryable: false } }, r.ok ? r.answer : void 0);
|
|
114332
|
+
if (agent || wait.tracksInputs) void this.reportBackIfAwaited(sessionId, r.ok ? { type: "turn_complete", sessionId } : { type: "error", sessionId, error: { code: "internal", message: r.error, retryable: false } }, r.ok ? r.answer : void 0);
|
|
113903
114333
|
});
|
|
113904
114334
|
this.agentRuns.set(sessionId, wait);
|
|
113905
114335
|
this.delegations.set(key, d2);
|
|
113906
114336
|
return d2;
|
|
113907
114337
|
}
|
|
114338
|
+
/** Read an existing turn without claiming it in delegations, agentRuns or the caller's sleep graph. */
|
|
114339
|
+
observeTurn(child, subscribe) {
|
|
114340
|
+
const active = this.agentRuns.get(child.id);
|
|
114341
|
+
const shared = active && !active.finished ? active : void 0;
|
|
114342
|
+
const wait = shared ?? new AgentRunWait(
|
|
114343
|
+
() => {
|
|
114344
|
+
},
|
|
114345
|
+
() => child.name,
|
|
114346
|
+
void 0,
|
|
114347
|
+
() => finalAnswer(this.store.loadMessages(child.id, 50))
|
|
114348
|
+
);
|
|
114349
|
+
const project2 = this.store.listProjects().find((p) => p.id === child.projectId);
|
|
114350
|
+
const target = { id: child.projectId ?? "", name: project2?.name ?? "", path: this.store.sessionCwd(child.id) ?? project2?.path ?? "" };
|
|
114351
|
+
if (!shared && subscribe) {
|
|
114352
|
+
const readers = this.turnObservers.get(child.id) ?? /* @__PURE__ */ new Set();
|
|
114353
|
+
readers.add(wait);
|
|
114354
|
+
this.turnObservers.set(child.id, readers);
|
|
114355
|
+
}
|
|
114356
|
+
return {
|
|
114357
|
+
run: {
|
|
114358
|
+
// This is the target's own turn, never work owned by its observer.
|
|
114359
|
+
callerId: child.id,
|
|
114360
|
+
sessionId: child.id,
|
|
114361
|
+
target,
|
|
114362
|
+
wait,
|
|
114363
|
+
get settled() {
|
|
114364
|
+
return wait.finished;
|
|
114365
|
+
},
|
|
114366
|
+
notice: () => void 0,
|
|
114367
|
+
outcome: wait.done.then(
|
|
114368
|
+
(r) => ({ ok: true, answer: r.answer }),
|
|
114369
|
+
(e) => ({ ok: false, error: e.message })
|
|
114370
|
+
),
|
|
114371
|
+
stop: (why) => {
|
|
114372
|
+
if (wait.finished) return;
|
|
114373
|
+
wait.fail(new Error(`${why}, so the turn was stopped`));
|
|
114374
|
+
this.handles.get(child.id)?.interrupt();
|
|
114375
|
+
}
|
|
114376
|
+
},
|
|
114377
|
+
release: () => {
|
|
114378
|
+
if (shared) return;
|
|
114379
|
+
const readers = this.turnObservers.get(child.id);
|
|
114380
|
+
readers?.delete(wait);
|
|
114381
|
+
if (readers?.size === 0) this.turnObservers.delete(child.id);
|
|
114382
|
+
}
|
|
114383
|
+
};
|
|
114384
|
+
}
|
|
113908
114385
|
/** Waits for a delegation up to the bound, and turns its outcome into what the caller reads */
|
|
113909
114386
|
async awaitDelegation(key, d2, signal) {
|
|
113910
114387
|
const sessionName = () => this.meta.get(d2.sessionId)?.name ?? d2.sessionId;
|
|
113911
114388
|
let timer;
|
|
113912
114389
|
let onAbort;
|
|
113913
|
-
const bound = new Promise((
|
|
113914
|
-
const aborted2 = new Promise((
|
|
114390
|
+
const bound = new Promise((resolve12) => timer = setTimeout(() => resolve12("bound"), ASK_WAIT_MS));
|
|
114391
|
+
const aborted2 = new Promise((resolve12) => {
|
|
113915
114392
|
if (!signal) return;
|
|
113916
|
-
onAbort = () =>
|
|
114393
|
+
onAbort = () => resolve12("aborted");
|
|
113917
114394
|
if (signal.aborted) onAbort();
|
|
113918
114395
|
else signal.addEventListener("abort", onAbort, { once: true });
|
|
113919
114396
|
});
|
|
114397
|
+
d2.waiters = (d2.waiters ?? 0) + 1;
|
|
113920
114398
|
try {
|
|
113921
|
-
const r = await Promise.race([d2.outcome, bound, aborted2]);
|
|
114399
|
+
const r = await Promise.race([d2.outcome, ...d2.waitCancelled ? [d2.waitCancelled] : [], bound, aborted2]);
|
|
113922
114400
|
if (r === "aborted") {
|
|
113923
114401
|
d2.stop("the call that asked was cancelled");
|
|
113924
|
-
await d2.outcome;
|
|
113925
|
-
this.delegations.delete(key);
|
|
113926
|
-
return { ok: false, error: `The call was cancelled, so ${d2.target.name}'s session "${sessionName()}" was stopped.` };
|
|
114402
|
+
if (!d2.joinedTurn) await d2.outcome;
|
|
114403
|
+
if (this.delegations.get(key) === d2) this.delegations.delete(key);
|
|
114404
|
+
return { ok: false, error: d2.joinedTurn ? `The call was cancelled; ${d2.target.name}'s session "${sessionName()}" was left running.` : `The call was cancelled, so ${d2.target.name}'s session "${sessionName()}" was stopped.` };
|
|
113927
114405
|
}
|
|
113928
114406
|
if (r === "bound") {
|
|
113929
114407
|
const notice = d2.notice();
|
|
@@ -113933,7 +114411,7 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
113933
114411
|
if (!r.ok) {
|
|
113934
114412
|
return {
|
|
113935
114413
|
ok: false,
|
|
113936
|
-
error: `${d2.target.name}'s session "${sessionName()}" [${d2.sessionId}] did not finish: ${r.error}. Tell the person, or ask again with a clearer task.`
|
|
114414
|
+
error: d2.joinedTurn ? `The wait for ${d2.target.name}'s session "${sessionName()}" [${d2.sessionId}] ended: ${r.error}.` : `${d2.target.name}'s session "${sessionName()}" [${d2.sessionId}] did not finish: ${r.error}. Tell the person, or ask again with a clearer task.`
|
|
113937
114415
|
};
|
|
113938
114416
|
}
|
|
113939
114417
|
const answer = r.answer ?? finalAnswer(this.store.loadMessages(d2.sessionId, 50));
|
|
@@ -113954,6 +114432,7 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
113954
114432
|
outside
|
|
113955
114433
|
};
|
|
113956
114434
|
} finally {
|
|
114435
|
+
d2.waiters = (d2.waiters ?? 1) - 1;
|
|
113957
114436
|
clearTimeout(timer);
|
|
113958
114437
|
if (onAbort) signal?.removeEventListener("abort", onAbort);
|
|
113959
114438
|
}
|
|
@@ -113975,19 +114454,19 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
113975
114454
|
async startAgent(callerId, opts, signal) {
|
|
113976
114455
|
const caller = this.meta.get(callerId);
|
|
113977
114456
|
if (!caller?.projectId) return { ok: false, error: "This session belongs to no project, so it cannot start agents." };
|
|
113978
|
-
if (caller.askedBy) {
|
|
114457
|
+
if ((caller.askedBy || [...this.delegations.values()].some((d8) => d8.sessionId === callerId && !d8.agent && !d8.settled)) && !this.autoBoundaryOpen(callerId, "delegation depth")) {
|
|
113979
114458
|
return { ok: false, error: "This session was itself started or asked by another session, so it cannot start agents \u2014 say in your answer what is needed instead." };
|
|
113980
114459
|
}
|
|
113981
114460
|
const project2 = this.store.listProjects().find((p) => p.id === caller.projectId);
|
|
113982
114461
|
if (!project2) return { ok: false, error: "This session's project is no longer registered." };
|
|
113983
|
-
if (project2.trusted !== true) {
|
|
114462
|
+
if (project2.trusted !== true && !this.autoBoundaryOpen(callerId, "start in an untrusted project")) {
|
|
113984
114463
|
return { ok: false, error: "This session's project is not trusted, so it cannot start agents \u2014 the person can trust it from the project menu." };
|
|
113985
114464
|
}
|
|
113986
114465
|
const tool = opts.tool ?? this.firstTool(project2.defaultTool);
|
|
113987
114466
|
if (!tool) return { ok: false, error: "No agent adapter is available. Install and configure an agent CLI before starting an agent." };
|
|
113988
114467
|
const adapter = this.adapters.get(tool);
|
|
113989
114468
|
if (!adapter) return { ok: false, error: `No agent named "${tool}" here. Installed tools: ${[...this.adapters.keys()].join(", ") || "none"}.` };
|
|
113990
|
-
const full = this.agentCeilingError(project2.id);
|
|
114469
|
+
const full = this.agentCeilingError(callerId, project2.id);
|
|
113991
114470
|
if (full) return { ok: false, error: full };
|
|
113992
114471
|
const remembered = this.store.projectToolDefaults(project2.id)[tool];
|
|
113993
114472
|
const model = opts.model ?? remembered?.model ?? void 0;
|
|
@@ -114012,7 +114491,7 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
114012
114491
|
signal
|
|
114013
114492
|
);
|
|
114014
114493
|
if (!consent.ok) return consent;
|
|
114015
|
-
const fullNow = this.agentCeilingError(project2.id);
|
|
114494
|
+
const fullNow = this.agentCeilingError(callerId, project2.id);
|
|
114016
114495
|
if (fullNow) return { ok: false, error: fullNow };
|
|
114017
114496
|
const cwd = worktree ? project2.path : this.store.sessionCwd(callerId) ?? caller.worktree?.path ?? project2.path;
|
|
114018
114497
|
let child;
|
|
@@ -114050,95 +114529,126 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
114050
114529
|
return result.ok && notice ? { ...result, notice: [result.notice, notice].filter(Boolean).join("; ") } : result;
|
|
114051
114530
|
}
|
|
114052
114531
|
/**
|
|
114053
|
-
* agent_session (#561):
|
|
114054
|
-
* caller, in the caller's project.
|
|
114532
|
+
* agent_session (#561): safe/normal work only with their own children; auto opens ownership within the project.
|
|
114055
114533
|
*
|
|
114056
114534
|
* status where it stands, without waiting (the answer, once the turn the caller gave it has ended)
|
|
114057
114535
|
* wait waits on that turn up to the bound, as start_agent does
|
|
114058
114536
|
* send gives it more, including during the caller's turn on adapters that accept mid-turn input; refused
|
|
114059
114537
|
* while it works on the person's own message or has a higher preset than the caller
|
|
114060
114538
|
* stop stops the turn the caller gave it; a turn the person started there is theirs
|
|
114061
|
-
* delete
|
|
114539
|
+
* delete trashes a child without a worktree, stopping it first; worktree cleanup requires an idle child (#608)
|
|
114062
114540
|
*/
|
|
114063
114541
|
async agentSession(callerId, opts, signal) {
|
|
114542
|
+
if (opts.sessionId === callerId && opts.action !== "status") {
|
|
114543
|
+
return { ok: false, error: "agent_session cannot wait on, stop, delete or send to its own calling session." };
|
|
114544
|
+
}
|
|
114064
114545
|
const caller = this.meta.get(callerId);
|
|
114065
114546
|
const child = this.meta.get(opts.sessionId);
|
|
114066
|
-
|
|
114547
|
+
const deletingWithoutWorktree = opts.action === "delete" && !child?.worktree;
|
|
114548
|
+
const startedHere = child?.askedBy === callerId || deletingWithoutWorktree && child?.parentSessionId === callerId;
|
|
114549
|
+
if (!caller?.projectId || !child || !startedHere && (deletingWithoutWorktree || !this.autoBoundaryOpen(callerId, "session ownership")) || child.projectId !== caller.projectId) {
|
|
114067
114550
|
return { ok: false, error: `${opts.sessionId} is not a session this session started. agent_session works only on sessions start_agent started here.` };
|
|
114068
114551
|
}
|
|
114552
|
+
if (opts.sessionId === callerId) return this.agentAtRest(child);
|
|
114069
114553
|
const key = `${callerId}>${child.id}`;
|
|
114070
|
-
|
|
114071
|
-
|
|
114072
|
-
|
|
114073
|
-
|
|
114074
|
-
|
|
114075
|
-
|
|
114076
|
-
|
|
114077
|
-
|
|
114078
|
-
|
|
114079
|
-
|
|
114080
|
-
|
|
114081
|
-
|
|
114082
|
-
|
|
114083
|
-
|
|
114084
|
-
|
|
114085
|
-
|
|
114086
|
-
|
|
114087
|
-
|
|
114088
|
-
|
|
114089
|
-
|
|
114090
|
-
|
|
114091
|
-
|
|
114092
|
-
|
|
114093
|
-
|
|
114094
|
-
|
|
114095
|
-
|
|
114096
|
-
|
|
114097
|
-
|
|
114554
|
+
let d2 = this.delegations.get(key);
|
|
114555
|
+
let releaseObservation;
|
|
114556
|
+
if (!d2 && this.autoBoundaryOpen(callerId)) {
|
|
114557
|
+
const existing = [...this.delegations.values()].find((run) => run.sessionId === child.id && !run.settled);
|
|
114558
|
+
if (existing) d2 = existing;
|
|
114559
|
+
else if (opts.action !== "delete" && (child.state === "working" || child.state === "waiting_approval")) {
|
|
114560
|
+
const observation = this.observeTurn(child, opts.action !== "status");
|
|
114561
|
+
d2 = observation.run;
|
|
114562
|
+
releaseObservation = observation.release;
|
|
114563
|
+
}
|
|
114564
|
+
}
|
|
114565
|
+
const observing = !!d2 && d2.callerId !== callerId;
|
|
114566
|
+
try {
|
|
114567
|
+
switch (opts.action) {
|
|
114568
|
+
case "status":
|
|
114569
|
+
if (d2 && !d2.settled) return this.agentWorking(d2, false, observing);
|
|
114570
|
+
return d2 ? await this.awaitAgent(key, d2, signal, observing) : this.agentAtRest(child);
|
|
114571
|
+
case "wait":
|
|
114572
|
+
return d2 ? await this.awaitAgent(key, d2, signal, observing) : this.agentAtRest(child);
|
|
114573
|
+
case "stop": {
|
|
114574
|
+
if (!d2 || d2.settled) return { ok: false, error: `Nothing you gave "${child.name}" is running.` };
|
|
114575
|
+
if (!observing && this.awaitingReport.get(child.id) === callerId) this.awaitingReport.delete(child.id);
|
|
114576
|
+
d2.stop("the session that started it stopped it", observing);
|
|
114577
|
+
await d2.outcome;
|
|
114578
|
+
if (!observing) this.delegations.delete(key);
|
|
114579
|
+
return { ok: true, state: "stopped", sessionId: child.id, sessionName: child.name, tool: child.tool };
|
|
114580
|
+
}
|
|
114581
|
+
case "delete": {
|
|
114582
|
+
if (!child.worktree) {
|
|
114583
|
+
const busy = child.state === "working" || child.state === "waiting_approval" || !!child.pendingApproval || child.pendingQuestions.length > 0 || this.agentRuns.has(child.id) && !this.agentRuns.get(child.id).finished;
|
|
114584
|
+
if (!this.trashing.has(child.id) && busy && (!d2 || d2.settled || d2.callerId !== callerId)) {
|
|
114585
|
+
return { ok: false, error: `"${child.name}" is busy with something else; the person may be using it. Wait for that turn and its pending approval or question to finish before deleting it.` };
|
|
114586
|
+
}
|
|
114587
|
+
await this.trashSession(child.id);
|
|
114588
|
+
if (d2) this.delegations.delete(key);
|
|
114589
|
+
return { ok: true, state: "deleted", sessionId: child.id, sessionName: child.name, tool: child.tool };
|
|
114590
|
+
}
|
|
114591
|
+
if (d2 && !d2.settled) return { ok: false, error: `"${child.name}" is still working on what you gave it \u2014 stop it or wait first.` };
|
|
114592
|
+
const r = await this.cleanUpWorktreeChild(child);
|
|
114593
|
+
if (!r.ok) return r;
|
|
114594
|
+
if (d2) this.delegations.delete(key);
|
|
114595
|
+
return { ok: true, state: "deleted", sessionId: child.id, sessionName: child.name, tool: child.tool, branch: r.branch, setAside: r.setAside, ...r.warning ? { warning: r.warning } : {} };
|
|
114098
114596
|
}
|
|
114099
|
-
|
|
114100
|
-
|
|
114101
|
-
|
|
114102
|
-
|
|
114103
|
-
|
|
114597
|
+
case "send": {
|
|
114598
|
+
if (!presetWithin(child.permissionPreset, caller.permissionPreset)) {
|
|
114599
|
+
return {
|
|
114600
|
+
ok: false,
|
|
114601
|
+
error: `"${child.name}" now has more permissions (${child.permissionPreset}) than this session (${caller.permissionPreset}), so this session cannot give it work. Tell the person.`
|
|
114602
|
+
};
|
|
114104
114603
|
}
|
|
114105
|
-
const
|
|
114106
|
-
|
|
114107
|
-
|
|
114108
|
-
this.
|
|
114109
|
-
|
|
114604
|
+
const from = { sessionId: callerId, name: labelOf(this.store, caller) };
|
|
114605
|
+
if (d2 && !d2.settled) {
|
|
114606
|
+
const text2 = agentNoteFrame(from.name, opts.text ?? "");
|
|
114607
|
+
if (!this.adapters.get(child.tool)?.capabilities.midTurnInput || !this.handles.get(child.id)?.sendTracked) {
|
|
114608
|
+
return { ok: false, error: `"${child.name}" cannot take messages while working. Wait for it (action "wait") or stop it first.` };
|
|
114609
|
+
}
|
|
114610
|
+
const releaseHold = d2.wait.holdInput();
|
|
114611
|
+
const release = () => {
|
|
114612
|
+
releaseHold();
|
|
114613
|
+
this.applyDeferredRestart(child.id);
|
|
114614
|
+
};
|
|
114615
|
+
try {
|
|
114616
|
+
await this.deliver(child.id, text2, void 0, from, false, void 0, "inline", (h, input) => {
|
|
114617
|
+
const fail4 = (e) => {
|
|
114618
|
+
d2.wait.fail(e);
|
|
114619
|
+
release();
|
|
114620
|
+
};
|
|
114621
|
+
void h.sendTracked(input, { complete: release, fail: fail4 }).then(release, fail4);
|
|
114622
|
+
});
|
|
114623
|
+
} catch (e) {
|
|
114624
|
+
release();
|
|
114625
|
+
return { ok: false, error: `Could not send it: ${e.message}` };
|
|
114626
|
+
}
|
|
114627
|
+
return this.agentWorking(d2, false, observing);
|
|
114628
|
+
}
|
|
114629
|
+
if (child.state === "working" || child.state === "waiting_approval" || this.agentRuns.has(child.id) && !this.agentRuns.get(child.id).finished) {
|
|
114630
|
+
return { ok: false, error: `"${child.name}" is busy with something else (the person may be using it). Try again later.` };
|
|
114631
|
+
}
|
|
114632
|
+
if (d2) this.delegations.delete(key);
|
|
114633
|
+
const full = this.agentCeilingError(callerId, child.projectId ?? "");
|
|
114634
|
+
if (full) return { ok: false, error: full };
|
|
114635
|
+
const project2 = this.store.listProjects().find((p) => p.id === child.projectId);
|
|
114636
|
+
const cwd = this.store.sessionCwd(child.id) ?? child.worktree?.path ?? project2?.path ?? "";
|
|
114637
|
+
const next = this.startDelegation(key, callerId, child.id, { id: child.projectId ?? "", name: project2?.name ?? "", path: cwd }, true);
|
|
114110
114638
|
try {
|
|
114111
|
-
await this.deliver(child.id,
|
|
114112
|
-
const fail4 = (e) => {
|
|
114113
|
-
d2.wait.fail(e);
|
|
114114
|
-
release();
|
|
114115
|
-
};
|
|
114116
|
-
void h.sendTracked(input, { complete: release, fail: fail4 }).then(release, fail4);
|
|
114117
|
-
});
|
|
114639
|
+
await this.deliver(child.id, agentFrame(from.name, opts.text ?? ""), void 0, from, false);
|
|
114118
114640
|
} catch (e) {
|
|
114119
|
-
|
|
114641
|
+
this.delegations.delete(key);
|
|
114642
|
+
if (this.agentRuns.get(child.id) === next.wait) this.agentRuns.delete(child.id);
|
|
114120
114643
|
return { ok: false, error: `Could not send it: ${e.message}` };
|
|
114121
114644
|
}
|
|
114122
|
-
return this.
|
|
114645
|
+
return this.awaitAgent(key, next, signal);
|
|
114123
114646
|
}
|
|
114124
|
-
|
|
114125
|
-
return { ok: false, error:
|
|
114126
|
-
}
|
|
114127
|
-
if (d2) this.delegations.delete(key);
|
|
114128
|
-
const full = this.agentCeilingError(child.projectId ?? "");
|
|
114129
|
-
if (full) return { ok: false, error: full };
|
|
114130
|
-
const project2 = this.store.listProjects().find((p) => p.id === child.projectId);
|
|
114131
|
-
const cwd = this.store.sessionCwd(child.id) ?? child.worktree?.path ?? project2?.path ?? "";
|
|
114132
|
-
const next = this.startDelegation(key, callerId, child.id, { id: child.projectId ?? "", name: project2?.name ?? "", path: cwd }, true);
|
|
114133
|
-
try {
|
|
114134
|
-
await this.deliver(child.id, agentFrame(from.name, opts.text ?? ""), void 0, from, false);
|
|
114135
|
-
} catch (e) {
|
|
114136
|
-
this.delegations.delete(key);
|
|
114137
|
-
if (this.agentRuns.get(child.id) === next.wait) this.agentRuns.delete(child.id);
|
|
114138
|
-
return { ok: false, error: `Could not send it: ${e.message}` };
|
|
114139
|
-
}
|
|
114140
|
-
return this.awaitAgent(key, next, signal);
|
|
114647
|
+
default:
|
|
114648
|
+
return { ok: false, error: "Unknown agent_session action." };
|
|
114141
114649
|
}
|
|
114650
|
+
} finally {
|
|
114651
|
+
releaseObservation?.();
|
|
114142
114652
|
}
|
|
114143
114653
|
}
|
|
114144
114654
|
/**
|
|
@@ -114151,65 +114661,70 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
114151
114661
|
if (!summary.isRepo) return { error: "This project is not a git repository, so it has no worktrees. Start the agent without one." };
|
|
114152
114662
|
const asked = opts.branch?.trim();
|
|
114153
114663
|
if (asked) return await gitValidBranchName(asked) ? { branch: asked } : { error: `Not a valid branch name: ${asked}` };
|
|
114154
|
-
return { branch: agentBranch(opts.task,
|
|
114664
|
+
return { branch: agentBranch(opts.task, randomUUID10()) };
|
|
114155
114665
|
}
|
|
114156
|
-
/**
|
|
114157
|
-
|
|
114158
|
-
|
|
114159
|
-
* changes, and the branch's current tip has landed on the trunk (by ancestry, or by a merged pull request whose head
|
|
114160
|
-
* is that tip). Not the cached merged badge: a commit could have landed since (TOCTOU). Every deletion outside that
|
|
114161
|
-
* proof stays the person's, in the delete dialog. The session goes to the trash like any other (an agent never
|
|
114162
|
-
* deletes one for good), the worktree and branch are removed, the tool's own conversation is kept, and the tip is
|
|
114163
|
-
* logged for reflog recovery.
|
|
114164
|
-
*/
|
|
114666
|
+
/** Prevent a send/resume from racing the snapshot and worktree removal. */
|
|
114667
|
+
worktreeCleanup = /* @__PURE__ */ new Set();
|
|
114668
|
+
/** #608: the parent decides; local work follows the session into the trash in every preset. */
|
|
114165
114669
|
async cleanUpWorktreeChild(target) {
|
|
114166
114670
|
if (!target.worktree?.base || !target.projectId) {
|
|
114167
114671
|
return { ok: false, error: `"${target.name}" works in no worktree of its own, so there is nothing to clean up \u2014 the person deletes a session` };
|
|
114168
114672
|
}
|
|
114169
|
-
if (target.state === "working" || target.state === "waiting_approval" || this.agentRuns.has(target.id)) {
|
|
114170
|
-
return { ok: false, error: `"${target.name}" is still working \u2014
|
|
114673
|
+
if (target.state === "working" || target.state === "waiting_approval" || this.agentRuns.has(target.id) || sessionBusy({ ...target, live: this.handles.has(target.id) })) {
|
|
114674
|
+
return { ok: false, error: `"${target.name}" is still working \u2014 stop it or wait first` };
|
|
114171
114675
|
}
|
|
114172
|
-
|
|
114173
|
-
|
|
114174
|
-
const wt2 = await gitWorktreeDirty(path2, this.gitTrust(target.projectId)).catch(() => ({ dirty: true, changedFiles: -1 }));
|
|
114175
|
-
if (wt2.dirty) {
|
|
114176
|
-
const n = wt2.changedFiles >= 0 ? `${wt2.changedFiles} ` : "";
|
|
114177
|
-
return { ok: false, error: `There are ${n}uncommitted changes in "${target.name}" \u2014 have it commit (or discard) them, then delete again` };
|
|
114676
|
+
if (!existsSync19(target.worktree.path)) {
|
|
114677
|
+
return { ok: false, error: `The worktree folder for "${target.name}" is missing: ${target.worktree.path}. Restore the folder before cleanup; its branch has been left in place.` };
|
|
114178
114678
|
}
|
|
114179
|
-
|
|
114180
|
-
|
|
114181
|
-
|
|
114182
|
-
|
|
114183
|
-
|
|
114184
|
-
const
|
|
114185
|
-
|
|
114186
|
-
|
|
114187
|
-
|
|
114188
|
-
|
|
114189
|
-
|
|
114190
|
-
|
|
114191
|
-
|
|
114679
|
+
if (this.resuming.has(target.id) || this.deferredRestarts.has(target.id)) return { ok: false, error: `"${target.name}" is waking or restarting \u2014 wait for it first` };
|
|
114680
|
+
if (this.worktreeCleanup.has(target.id)) return { ok: false, error: "This worktree is already being removed" };
|
|
114681
|
+
this.worktreeCleanup.add(target.id);
|
|
114682
|
+
const { branch, path: path2 } = target.worktree;
|
|
114683
|
+
try {
|
|
114684
|
+
const cwd = this.cwdOf(target.projectId);
|
|
114685
|
+
const handle = this.handles.get(target.id);
|
|
114686
|
+
if (handle) {
|
|
114687
|
+
await handle.dispose();
|
|
114688
|
+
this.handles.delete(target.id);
|
|
114689
|
+
this.running.delete(target.id);
|
|
114690
|
+
}
|
|
114691
|
+
const trunk = await gitRevParse(cwd, this.trunkOf(target.projectId) ?? "HEAD");
|
|
114692
|
+
const setAside = await saveWorktreeRecovery(target.id, path2, trunk ?? "missing-trunk", branch);
|
|
114693
|
+
if (setAside.changedFiles || setAside.commits) {
|
|
114694
|
+
this.onEvent({
|
|
114695
|
+
type: "notice",
|
|
114696
|
+
sessionId: target.id,
|
|
114697
|
+
level: "info",
|
|
114698
|
+
from: "Centralu",
|
|
114699
|
+
label: "worktree recovery",
|
|
114700
|
+
text: `Local work saved before worktree deletion: ${setAside.changedFiles} uncommitted files, ${setAside.commits} commits not on the trunk. Recovery patches and bundles are at ${worktreeRecoveryDir(target.id)}. Restore from Settings \u2192 Trash or recover these files manually before deleting this session for good.`
|
|
114701
|
+
});
|
|
114192
114702
|
}
|
|
114703
|
+
await this.trashSession(target.id, false, false, setAside);
|
|
114704
|
+
try {
|
|
114705
|
+
await gitWorktreeRemove(cwd, path2, true, true);
|
|
114706
|
+
} catch (e) {
|
|
114707
|
+
return { ok: true, branch, setAside, warning: `The session is in the trash, but worktree ${path2} and branch ${branch} were left: ${e.message}. Recovery files are at ${worktreeRecoveryDir(target.id)}.` };
|
|
114708
|
+
}
|
|
114709
|
+
try {
|
|
114710
|
+
await gitBranchDelete(cwd, branch, this.gitTrust(target.projectId));
|
|
114711
|
+
} catch (e) {
|
|
114712
|
+
return { ok: true, branch, setAside, warning: `The session is in the trash and its worktree was removed, but branch ${branch} was left: ${e.message}. Recovery files are at ${worktreeRecoveryDir(target.id)}.` };
|
|
114713
|
+
}
|
|
114714
|
+
return { ok: true, branch, setAside };
|
|
114715
|
+
} catch (e) {
|
|
114716
|
+
return { ok: false, error: `Could not finish removing "${branch}": ${e.message}. Any saved recovery files are at ${worktreeRecoveryDir(target.id)}.` };
|
|
114717
|
+
} finally {
|
|
114718
|
+
this.worktreeCleanup.delete(target.id);
|
|
114193
114719
|
}
|
|
114194
|
-
if (!proof) {
|
|
114195
|
-
return {
|
|
114196
|
-
ok: false,
|
|
114197
|
-
error: `Could not prove that "${branch}" has landed on the trunk \u2014 it is deleted only once a merge (or a merged PR) is confirmed. Discarding it without proof is the person's job, in the delete dialog`
|
|
114198
|
-
};
|
|
114199
|
-
}
|
|
114200
|
-
const tip = await gitRevParse(cwd, `refs/heads/${branch}`);
|
|
114201
|
-
await this.trashSession(target.id);
|
|
114202
|
-
await gitWorktreeRemove(cwd, path2, true).catch(() => {
|
|
114203
|
-
});
|
|
114204
|
-
await gitBranchDelete(cwd, branch, this.gitTrust(target.projectId)).catch(() => {
|
|
114205
|
-
});
|
|
114206
|
-
console.error(`[worktree] cleaned up ${branch} (tip ${tip?.slice(0, 8) ?? "?"}, proof: ${proof})`);
|
|
114207
|
-
return { ok: true, branch, proof };
|
|
114208
114720
|
}
|
|
114209
|
-
/**
|
|
114210
|
-
agentCeilingError(projectId) {
|
|
114721
|
+
/** A saved ceiling overrides the caller's preset default: auto unlimited, otherwise AGENT_CEILING. */
|
|
114722
|
+
agentCeilingError(callerId, projectId) {
|
|
114723
|
+
const configured = this.uiPreferences().agentCeiling;
|
|
114724
|
+
const ceiling = configured ?? (this.autoBoundaryOpen(callerId) ? 0 : AGENT_CEILING);
|
|
114725
|
+
if (ceiling === 0) return null;
|
|
114211
114726
|
const running = [...this.delegations.values()].filter((d2) => d2.agent && !d2.settled && this.meta.get(d2.sessionId)?.projectId === projectId).length;
|
|
114212
|
-
if (running <
|
|
114727
|
+
if (running < ceiling) return null;
|
|
114213
114728
|
return `${running} agents started by this project's sessions are already working, the most at once. Wait for one (agent_session, action "wait") or stop one, then try again.`;
|
|
114214
114729
|
}
|
|
114215
114730
|
/**
|
|
@@ -114269,8 +114784,8 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
114269
114784
|
* "Working": the caller is told when the turn ends (#561), through the report the orchestrator's reportBack uses, unless
|
|
114270
114785
|
* a call is waiting on it by then (`awaitAgent` takes the marker down while it waits)
|
|
114271
114786
|
*/
|
|
114272
|
-
agentWorking(d2, arm = true) {
|
|
114273
|
-
if (arm) this.awaitingReport.set(d2.sessionId, d2.callerId);
|
|
114787
|
+
agentWorking(d2, arm = true, observing = false) {
|
|
114788
|
+
if (arm && !observing) this.awaitingReport.set(d2.sessionId, d2.callerId);
|
|
114274
114789
|
const notice = d2.notice();
|
|
114275
114790
|
return {
|
|
114276
114791
|
ok: true,
|
|
@@ -114278,7 +114793,7 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
114278
114793
|
sessionId: d2.sessionId,
|
|
114279
114794
|
sessionName: this.meta.get(d2.sessionId)?.name ?? d2.sessionId,
|
|
114280
114795
|
tool: this.meta.get(d2.sessionId)?.tool ?? "",
|
|
114281
|
-
reported: this.awaitingReport.get(d2.sessionId) === d2.callerId,
|
|
114796
|
+
reported: !observing && this.awaitingReport.get(d2.sessionId) === d2.callerId,
|
|
114282
114797
|
...notice ? { notice } : {}
|
|
114283
114798
|
};
|
|
114284
114799
|
}
|
|
@@ -114295,32 +114810,43 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
114295
114810
|
};
|
|
114296
114811
|
}
|
|
114297
114812
|
/** Waits on a child's turn up to the bound (#561), like `awaitDelegation`, and reads its outcome for the caller */
|
|
114298
|
-
async awaitAgent(key, d2, signal) {
|
|
114299
|
-
if (this.awaitingReport.get(d2.sessionId) === d2.callerId) this.awaitingReport.delete(d2.sessionId);
|
|
114813
|
+
async awaitAgent(key, d2, signal, observing = false) {
|
|
114814
|
+
if (!observing && this.awaitingReport.get(d2.sessionId) === d2.callerId) this.awaitingReport.delete(d2.sessionId);
|
|
114300
114815
|
const name = () => this.meta.get(d2.sessionId)?.name ?? d2.sessionId;
|
|
114301
114816
|
let timer;
|
|
114302
114817
|
let onAbort;
|
|
114303
|
-
const bound = new Promise((
|
|
114304
|
-
const aborted2 = new Promise((
|
|
114818
|
+
const bound = new Promise((resolve12) => timer = setTimeout(() => resolve12("bound"), ASK_WAIT_MS));
|
|
114819
|
+
const aborted2 = new Promise((resolve12) => {
|
|
114305
114820
|
if (!signal) return;
|
|
114306
|
-
onAbort = () =>
|
|
114821
|
+
onAbort = () => resolve12("aborted");
|
|
114307
114822
|
if (signal.aborted) onAbort();
|
|
114308
114823
|
else signal.addEventListener("abort", onAbort, { once: true });
|
|
114309
114824
|
});
|
|
114310
|
-
d2.waiters = (d2.waiters ?? 0) + 1;
|
|
114825
|
+
if (!observing) d2.waiters = (d2.waiters ?? 0) + 1;
|
|
114311
114826
|
try {
|
|
114312
114827
|
const r = await Promise.race([d2.outcome, bound, aborted2]);
|
|
114313
|
-
d2.waiters
|
|
114828
|
+
if (!observing) d2.waiters = (d2.waiters ?? 1) - 1;
|
|
114314
114829
|
if (r === "aborted") {
|
|
114830
|
+
if (observing) return { ok: false, error: `The observation was cancelled; "${name()}" was left running.` };
|
|
114315
114831
|
d2.stop("the call that started it was cancelled");
|
|
114316
114832
|
await d2.outcome;
|
|
114317
114833
|
this.delegations.delete(key);
|
|
114318
114834
|
return { ok: false, error: `The call was cancelled, so "${name()}" was stopped.` };
|
|
114319
114835
|
}
|
|
114320
|
-
if (r === "bound") return this.agentWorking(d2);
|
|
114321
|
-
if (this.delegations.get(key) === d2) this.delegations.delete(key);
|
|
114836
|
+
if (r === "bound") return this.agentWorking(d2, !observing, observing);
|
|
114837
|
+
if (!observing && this.delegations.get(key) === d2) this.delegations.delete(key);
|
|
114322
114838
|
if (!r.ok) return { ok: false, error: `"${name()}" [${d2.sessionId}] did not finish: ${r.error}. Tell the person, or give it a clearer task.` };
|
|
114323
114839
|
const answer = r.answer ?? finalAnswer(this.store.loadMessages(d2.sessionId, 50));
|
|
114840
|
+
if (observing) return {
|
|
114841
|
+
ok: true,
|
|
114842
|
+
state: "done",
|
|
114843
|
+
sessionId: d2.sessionId,
|
|
114844
|
+
sessionName: name(),
|
|
114845
|
+
tool: this.meta.get(d2.sessionId)?.tool ?? "",
|
|
114846
|
+
answer: clipAnswer(answer, name()),
|
|
114847
|
+
readable: [],
|
|
114848
|
+
outside: []
|
|
114849
|
+
};
|
|
114324
114850
|
const { granted, outside } = readableGrants(pathsIn(answer), d2.target.path);
|
|
114325
114851
|
if (granted.length) {
|
|
114326
114852
|
const set3 = this.readGrants.get(d2.callerId) ?? /* @__PURE__ */ new Set();
|
|
@@ -114343,39 +114869,39 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
114343
114869
|
}
|
|
114344
114870
|
}
|
|
114345
114871
|
askCapability(q2, signal) {
|
|
114346
|
-
return new Promise((
|
|
114347
|
-
if (signal.aborted) return
|
|
114872
|
+
return new Promise((resolve12) => {
|
|
114873
|
+
if (signal.aborted) return resolve12(null);
|
|
114348
114874
|
const app2 = { appId: q2.app.appId, projectId: q2.app.projectId, name: q2.appName };
|
|
114349
114875
|
if (q2.origin.kind === "session" && this.meta.has(q2.origin.sessionId)) {
|
|
114350
|
-
const requestId = `cap-${
|
|
114876
|
+
const requestId = `cap-${randomUUID10()}`;
|
|
114351
114877
|
const ask = {
|
|
114352
114878
|
requestId,
|
|
114353
114879
|
sessionId: q2.origin.sessionId,
|
|
114354
114880
|
detail: { kind: "capability", app: app2, capability: q2.capability, text: q2.text },
|
|
114355
114881
|
shown: false,
|
|
114356
|
-
resolve: (d2) =>
|
|
114882
|
+
resolve: (d2) => resolve12(d2 === "always" || d2 === "always_project" ? "allow" : d2)
|
|
114357
114883
|
};
|
|
114358
114884
|
this.hostAsks.set(requestId, ask);
|
|
114359
114885
|
signal.addEventListener("abort", () => {
|
|
114360
114886
|
if (!this.hostAsks.delete(requestId)) return;
|
|
114361
114887
|
const m = this.meta.get(ask.sessionId);
|
|
114362
114888
|
if (m?.pendingApproval?.requestId === requestId) this.onEvent({ type: "approval_resolved", sessionId: ask.sessionId, requestId, decision: "deny" });
|
|
114363
|
-
|
|
114889
|
+
resolve12(null);
|
|
114364
114890
|
}, { once: true });
|
|
114365
114891
|
this.raiseHostAsks(ask.sessionId);
|
|
114366
114892
|
return;
|
|
114367
114893
|
}
|
|
114368
114894
|
const origin = q2.origin.kind === "view" ? q2.origin.app : q2.app;
|
|
114369
|
-
const id2 = `q-${
|
|
114895
|
+
const id2 = `q-${randomUUID10()}`;
|
|
114370
114896
|
this.appQuestions.set(id2, {
|
|
114371
114897
|
question: { id: id2, app: app2, capability: q2.capability, text: q2.text, origin: { appId: origin.appId, projectId: origin.projectId }, askedAt: q2.askedAt, expiresAt: q2.expiresAt },
|
|
114372
|
-
resolve:
|
|
114898
|
+
resolve: resolve12
|
|
114373
114899
|
});
|
|
114374
114900
|
this.emit({ type: "external_app_questions_changed" });
|
|
114375
114901
|
signal.addEventListener("abort", () => {
|
|
114376
114902
|
if (!this.appQuestions.delete(id2)) return;
|
|
114377
114903
|
this.emit({ type: "external_app_questions_changed" });
|
|
114378
|
-
|
|
114904
|
+
resolve12(null);
|
|
114379
114905
|
}, { once: true });
|
|
114380
114906
|
});
|
|
114381
114907
|
}
|
|
@@ -114684,6 +115210,7 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
114684
115210
|
this.appsHub?.dispose();
|
|
114685
115211
|
this.appsHub = new SessionAppsHub(rt2, {
|
|
114686
115212
|
...opts,
|
|
115213
|
+
boundaryOpen: (id2) => this.autoBoundaryOpen(id2),
|
|
114687
115214
|
onDemand: {
|
|
114688
115215
|
attached: (id2) => this.appAccess?.attached(id2) ?? [],
|
|
114689
115216
|
/*
|
|
@@ -114694,7 +115221,9 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
114694
115221
|
allowed: (session2, app2) => this.toolProfileOf(session2.id) === "reader" && (this.appAccess?.allowed(session2, app2) ?? false)
|
|
114695
115222
|
}
|
|
114696
115223
|
});
|
|
115224
|
+
rt2.setSessionBoundaryPolicy((id2) => this.autoBoundaryOpen(id2));
|
|
114697
115225
|
this.appAccess = new AppAccess({
|
|
115226
|
+
boundaryOpen: (id2, boundary) => this.autoBoundaryOpen(id2, boundary),
|
|
114698
115227
|
hub: this.appsHub,
|
|
114699
115228
|
store: this.store,
|
|
114700
115229
|
session: (id2) => {
|
|
@@ -115034,8 +115563,8 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
115034
115563
|
return readMessageImage(abs);
|
|
115035
115564
|
}
|
|
115036
115565
|
repliesName(sessionId, written) {
|
|
115037
|
-
const
|
|
115038
|
-
if (
|
|
115566
|
+
const open6 = this.streams.get(sessionId);
|
|
115567
|
+
if (open6?.kind === "text" && mentions(open6.text, written)) return true;
|
|
115039
115568
|
return writtenForms(written).some((form) => this.store.replyTextsContaining(sessionId, form).some((text2) => mentions(text2, written)));
|
|
115040
115569
|
}
|
|
115041
115570
|
/**
|
|
@@ -115048,14 +115577,14 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
115048
115577
|
}
|
|
115049
115578
|
async loadMessages(sessionId, limit, beforeSeq) {
|
|
115050
115579
|
const rows = this.store.loadMessages(sessionId, limit, beforeSeq);
|
|
115051
|
-
const { readFile:
|
|
115580
|
+
const { readFile: readFile6 } = await import("node:fs/promises");
|
|
115052
115581
|
return Promise.all(
|
|
115053
115582
|
rows.map(async (r) => {
|
|
115054
115583
|
if (r.kind === "image") {
|
|
115055
115584
|
const p = r.payload;
|
|
115056
115585
|
if (!p.path || p.note) return r;
|
|
115057
115586
|
try {
|
|
115058
|
-
const data = (await
|
|
115587
|
+
const data = (await readFile6(p.path)).toString("base64");
|
|
115059
115588
|
return { ...r, payload: { ...p, data } };
|
|
115060
115589
|
} catch {
|
|
115061
115590
|
return { ...r, payload: { ...p, note: "The image was cleared and no longer exists (total-size cap)" } };
|
|
@@ -115068,7 +115597,7 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
115068
115597
|
p.attachments.map(async (a) => {
|
|
115069
115598
|
if (a.kind !== "image") return a;
|
|
115070
115599
|
try {
|
|
115071
|
-
return { ...a, data: (await
|
|
115600
|
+
return { ...a, data: (await readFile6(a.path)).toString("base64") };
|
|
115072
115601
|
} catch {
|
|
115073
115602
|
return a;
|
|
115074
115603
|
}
|
|
@@ -115082,6 +115611,7 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
115082
115611
|
}
|
|
115083
115612
|
async disposeAll() {
|
|
115084
115613
|
this.watchers.close();
|
|
115614
|
+
for (const readers of this.turnObservers.values()) for (const observer of readers) observer.fail(new Error("Centralu is stopping"));
|
|
115085
115615
|
this.appsHub?.rt.attachBrokerHost(null);
|
|
115086
115616
|
for (const run of [...this.agentRuns.values()]) run.fail(new Error("Centralu is shutting down"));
|
|
115087
115617
|
for (const ask of [...this.hostAsks.values()]) ask.resolve(null);
|
|
@@ -115112,6 +115642,7 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
115112
115642
|
this.watchers.close();
|
|
115113
115643
|
this.appsHub?.rt.attachBrokerHost(null);
|
|
115114
115644
|
for (const run of [...this.agentRuns.values()]) run.fail(new Error("Centralu is restarting"));
|
|
115645
|
+
for (const readers of this.turnObservers.values()) for (const observer of readers) observer.fail(new Error("Centralu is restarting"));
|
|
115115
115646
|
for (const ask of [...this.hostAsks.values()]) ask.resolve(null);
|
|
115116
115647
|
this.hostAsks.clear();
|
|
115117
115648
|
for (const q2 of [...this.appQuestions.values()]) q2.resolve(null);
|
|
@@ -115143,8 +115674,8 @@ import { relative as relative9 } from "node:path";
|
|
|
115143
115674
|
// packages/agent-host/src/themes.ts
|
|
115144
115675
|
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
115145
115676
|
import { watch as watch2 } from "node:fs";
|
|
115146
|
-
import { mkdir as
|
|
115147
|
-
import { basename as basename6, join as
|
|
115677
|
+
import { mkdir as mkdir7, readdir as readdir9, readFile as readFile5, rename as rename4, stat as stat6, unlink as unlink2, writeFile as writeFile5 } from "node:fs/promises";
|
|
115678
|
+
import { basename as basename6, join as join30 } from "node:path";
|
|
115148
115679
|
var MAX_FILE_BYTES = 256 * 1024;
|
|
115149
115680
|
var SETTLE_MS = 100;
|
|
115150
115681
|
var ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,59}$/;
|
|
@@ -115159,7 +115690,7 @@ var ThemeFiles = class {
|
|
|
115159
115690
|
timer = null;
|
|
115160
115691
|
/** Makes the folder, writes the schema next to the themes, and starts watching */
|
|
115161
115692
|
async start() {
|
|
115162
|
-
await
|
|
115693
|
+
await mkdir7(this.dir, { recursive: true });
|
|
115163
115694
|
await this.writeSchema();
|
|
115164
115695
|
try {
|
|
115165
115696
|
this.watcher = watch2(this.dir, (_event, name) => {
|
|
@@ -115189,15 +115720,15 @@ var ThemeFiles = class {
|
|
|
115189
115720
|
* a token updates it, and an unchanged one does not touch the folder (or wake the watcher).
|
|
115190
115721
|
*/
|
|
115191
115722
|
async writeSchema() {
|
|
115192
|
-
const path2 =
|
|
115723
|
+
const path2 = join30(this.dir, THEME_SCHEMA_FILE);
|
|
115193
115724
|
const text2 = `${JSON.stringify(themeFileJsonSchema(), null, 2)}
|
|
115194
115725
|
`;
|
|
115195
|
-
const current = await
|
|
115726
|
+
const current = await readFile5(path2, "utf8").catch(() => null);
|
|
115196
115727
|
if (current !== text2) await this.atomicWrite(path2, text2);
|
|
115197
115728
|
}
|
|
115198
115729
|
/** Every `<id>.json` in the folder, read and checked. A file that cannot be read is listed with the reason. */
|
|
115199
115730
|
async list() {
|
|
115200
|
-
const names = await
|
|
115731
|
+
const names = await readdir9(this.dir).catch(() => []);
|
|
115201
115732
|
const entries = [];
|
|
115202
115733
|
const ids = names.filter((name) => name.endsWith(".json") && name !== THEME_SCHEMA_FILE).map((name) => name.slice(0, -".json".length)).filter((id2) => ID_PATTERN.test(id2)).sort();
|
|
115203
115734
|
for (const id2 of ids) entries.push(await this.read(id2));
|
|
@@ -115206,11 +115737,11 @@ var ThemeFiles = class {
|
|
|
115206
115737
|
async read(id2) {
|
|
115207
115738
|
const path2 = this.pathOf(id2);
|
|
115208
115739
|
try {
|
|
115209
|
-
const info = await
|
|
115740
|
+
const info = await stat6(path2);
|
|
115210
115741
|
if (info.size > MAX_FILE_BYTES) {
|
|
115211
115742
|
return { id: id2, path: path2, name: id2, base: "dark", tokens: {}, problems: ["The file is too large to be a theme."], broken: true };
|
|
115212
115743
|
}
|
|
115213
|
-
return parseThemeFile(id2, path2, await
|
|
115744
|
+
return parseThemeFile(id2, path2, await readFile5(path2, "utf8"));
|
|
115214
115745
|
} catch (e) {
|
|
115215
115746
|
return { id: id2, path: path2, name: id2, base: "dark", tokens: {}, problems: [`Could not read the file: ${e.message}`], broken: true };
|
|
115216
115747
|
}
|
|
@@ -115218,16 +115749,16 @@ var ThemeFiles = class {
|
|
|
115218
115749
|
/** Writes a theme. `id` null makes a new file named after the theme (`My theme` → `my-theme.json`). */
|
|
115219
115750
|
async save(id2, content) {
|
|
115220
115751
|
const target = id2 ?? await this.freeId(content.name);
|
|
115221
|
-
await
|
|
115752
|
+
await mkdir7(this.dir, { recursive: true });
|
|
115222
115753
|
await this.atomicWrite(this.pathOf(target), formatThemeFile(content));
|
|
115223
115754
|
return this.read(target);
|
|
115224
115755
|
}
|
|
115225
115756
|
/** Copies a file from elsewhere in, under a fresh id if its name is taken. Refuses what does not read as a theme. */
|
|
115226
115757
|
async importFrom(source) {
|
|
115227
|
-
const info = await
|
|
115758
|
+
const info = await stat6(source);
|
|
115228
115759
|
if (!info.isFile()) throw Object.assign(new Error("That is not a file."), { code: "invalid_params" });
|
|
115229
115760
|
if (info.size > MAX_FILE_BYTES) throw Object.assign(new Error("The file is too large to be a theme."), { code: "invalid_params" });
|
|
115230
|
-
const text2 = await
|
|
115761
|
+
const text2 = await readFile5(source, "utf8");
|
|
115231
115762
|
const parsed = parseThemeFile("import", source, text2);
|
|
115232
115763
|
if (parsed.broken) throw Object.assign(new Error(parsed.problems[0] ?? "Not a theme file."), { code: "invalid_params" });
|
|
115233
115764
|
const fromName = basename6(source).replace(/\.json$/i, "");
|
|
@@ -115238,13 +115769,13 @@ var ThemeFiles = class {
|
|
|
115238
115769
|
/** The absolute path for an id, for the shell to trash or reveal. Refuses anything that is not an id. */
|
|
115239
115770
|
pathOf(id2) {
|
|
115240
115771
|
if (!ID_PATTERN.test(id2)) throw Object.assign(new Error(`Not a theme id: ${id2}`), { code: "invalid_params" });
|
|
115241
|
-
return
|
|
115772
|
+
return join30(this.dir, `${id2}.json`);
|
|
115242
115773
|
}
|
|
115243
115774
|
async freeId(name) {
|
|
115244
115775
|
const base2 = name.toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "theme";
|
|
115245
115776
|
for (let n = 1; n < 1e3; n++) {
|
|
115246
115777
|
const id2 = n === 1 ? base2 : `${base2}-${n}`;
|
|
115247
|
-
const taken = await
|
|
115778
|
+
const taken = await stat6(this.pathOf(id2)).then(
|
|
115248
115779
|
() => true,
|
|
115249
115780
|
() => false
|
|
115250
115781
|
);
|
|
@@ -115256,7 +115787,7 @@ var ThemeFiles = class {
|
|
|
115256
115787
|
async atomicWrite(path2, text2) {
|
|
115257
115788
|
const tmp = `${path2}.${randomBytes4(6).toString("hex")}.tmp`;
|
|
115258
115789
|
try {
|
|
115259
|
-
await
|
|
115790
|
+
await writeFile5(tmp, text2, "utf8");
|
|
115260
115791
|
await rename4(tmp, path2);
|
|
115261
115792
|
} catch (e) {
|
|
115262
115793
|
await unlink2(tmp).catch(() => {
|
|
@@ -116235,9 +116766,9 @@ function broadcastAppChanges(send, windowMs = APP_CHANGE_WINDOW_MS) {
|
|
|
116235
116766
|
return {
|
|
116236
116767
|
emit(ref, cause2 = null) {
|
|
116237
116768
|
const key = JSON.stringify([ref.projectId, ref.appId]);
|
|
116238
|
-
const
|
|
116239
|
-
if (
|
|
116240
|
-
if (!sameCause(
|
|
116769
|
+
const open6 = pending.get(key);
|
|
116770
|
+
if (open6) {
|
|
116771
|
+
if (!sameCause(open6.cause, cause2)) open6.cause = null;
|
|
116241
116772
|
return;
|
|
116242
116773
|
}
|
|
116243
116774
|
const timer = setTimeout(() => {
|
|
@@ -116277,7 +116808,7 @@ var sameCause = (a, b2) => a !== null && b2 !== null && JSON.stringify(a) === JS
|
|
|
116277
116808
|
|
|
116278
116809
|
// packages/agent-host/src/dev-services/terminal.ts
|
|
116279
116810
|
import { createRequire } from "node:module";
|
|
116280
|
-
import { existsSync as
|
|
116811
|
+
import { existsSync as existsSync20 } from "node:fs";
|
|
116281
116812
|
import { win32 as win327 } from "node:path";
|
|
116282
116813
|
var require2 = createRequire(import.meta.url);
|
|
116283
116814
|
var SCROLLBACK_BYTES = 256 * 1024;
|
|
@@ -116509,9 +117040,9 @@ function commandShell(command, platform = process.platform, env = process.env) {
|
|
|
116509
117040
|
}
|
|
116510
117041
|
function shellPath() {
|
|
116511
117042
|
const fromEnv = process.env.SHELL;
|
|
116512
|
-
if (fromEnv &&
|
|
117043
|
+
if (fromEnv && existsSync20(fromEnv)) return fromEnv;
|
|
116513
117044
|
for (const candidate of ["/bin/zsh", "/bin/bash", "/bin/sh"]) {
|
|
116514
|
-
if (
|
|
117045
|
+
if (existsSync20(candidate)) return candidate;
|
|
116515
117046
|
}
|
|
116516
117047
|
return process.platform === "win32" ? "powershell.exe" : "/bin/sh";
|
|
116517
117048
|
}
|
|
@@ -116698,17 +117229,19 @@ var CommandRunner = class {
|
|
|
116698
117229
|
|
|
116699
117230
|
// packages/agent-host/src/updates.ts
|
|
116700
117231
|
import { execFile as execFile8 } from "node:child_process";
|
|
116701
|
-
import { existsSync as
|
|
117232
|
+
import { existsSync as existsSync21 } from "node:fs";
|
|
116702
117233
|
var REGISTRY_URL = `https://registry.npmjs.org/${APP_SLUG}/latest`;
|
|
116703
117234
|
var CHECK_INTERVAL_MS = 6 * 60 * 60 * 1e3;
|
|
116704
117235
|
var SCHEDULED_TIMEOUT_MS = 3e3;
|
|
116705
117236
|
var FORCED_TIMEOUT_MS = 8e3;
|
|
117237
|
+
var INSTALL_CHECK_MAX_AGE_MS = 6e4;
|
|
117238
|
+
var STORED_RELEASE_MAX_AGE_MS = 60 * 60 * 1e3;
|
|
116706
117239
|
var INSTALL_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
116707
117240
|
var NPM_RETRY_DELAY_MS = 3e4;
|
|
116708
117241
|
var NPM_RETRY_WINDOW_MS = 10 * 60 * 1e3;
|
|
116709
117242
|
function existingInstalledCopy() {
|
|
116710
117243
|
const path2 = installedCopyPath();
|
|
116711
|
-
return
|
|
117244
|
+
return existsSync21(path2) ? path2 : null;
|
|
116712
117245
|
}
|
|
116713
117246
|
async function fetchLatestFromRegistry(timeoutMs) {
|
|
116714
117247
|
try {
|
|
@@ -116722,16 +117255,16 @@ async function fetchLatestFromRegistry(timeoutMs) {
|
|
|
116722
117255
|
return { ok: false, reason: "Could not reach the registry \u2014 check the network" };
|
|
116723
117256
|
}
|
|
116724
117257
|
}
|
|
116725
|
-
function commandFor(file2, args,
|
|
116726
|
-
const launch2 =
|
|
117258
|
+
function commandFor(file2, args, resolve12 = (f) => resolveCommand(f)) {
|
|
117259
|
+
const launch2 = resolve12(file2);
|
|
116727
117260
|
return { file: launch2.command, args: [...launch2.args, ...args] };
|
|
116728
117261
|
}
|
|
116729
117262
|
function failureDetail(file2, stderr) {
|
|
116730
117263
|
const lines = stderr.split(/\r?\n/).map((l) => l.trim()).filter((l) => l && !/complete log of this run/i.test(l));
|
|
116731
117264
|
return (file2 === "npm" ? lines.at(-1) : lines[0]) ?? null;
|
|
116732
117265
|
}
|
|
116733
|
-
function runCommand(file2, args,
|
|
116734
|
-
const cmd = commandFor(file2, args,
|
|
117266
|
+
function runCommand(file2, args, resolve12) {
|
|
117267
|
+
const cmd = commandFor(file2, args, resolve12);
|
|
116735
117268
|
return new Promise((done, reject) => {
|
|
116736
117269
|
execFile8(cmd.file, cmd.args, { timeout: INSTALL_TIMEOUT_MS, windowsHide: true }, (err, _stdout, stderr) => {
|
|
116737
117270
|
if (!err) return done();
|
|
@@ -116759,7 +117292,7 @@ var UpdateService = class {
|
|
|
116759
117292
|
readAutoApply: deps.readAutoApply ?? (() => false),
|
|
116760
117293
|
writeAutoApply: deps.writeAutoApply ?? (() => {
|
|
116761
117294
|
}),
|
|
116762
|
-
wait: deps.wait ?? ((ms3) => new Promise((
|
|
117295
|
+
wait: deps.wait ?? ((ms3) => new Promise((resolve12) => setTimeout(resolve12, ms3)))
|
|
116763
117296
|
};
|
|
116764
117297
|
this.status = {
|
|
116765
117298
|
/*
|
|
@@ -116783,6 +117316,8 @@ var UpdateService = class {
|
|
|
116783
117316
|
}
|
|
116784
117317
|
publish;
|
|
116785
117318
|
status;
|
|
117319
|
+
stopped = false;
|
|
117320
|
+
installing = false;
|
|
116786
117321
|
timer = null;
|
|
116787
117322
|
deps;
|
|
116788
117323
|
/** One check at a time — the timer and the button can land together */
|
|
@@ -116798,16 +117333,22 @@ var UpdateService = class {
|
|
|
116798
117333
|
* closed while releases happened.
|
|
116799
117334
|
*/
|
|
116800
117335
|
start() {
|
|
117336
|
+
this.stopped = false;
|
|
116801
117337
|
if (!this.status.auto) return;
|
|
116802
117338
|
void this.check(false);
|
|
116803
117339
|
this.startTimer();
|
|
116804
117340
|
}
|
|
117341
|
+
/** Shutdown (including a host swap): no asynchronous continuation may start more work. */
|
|
116805
117342
|
stop() {
|
|
117343
|
+
this.stopped = true;
|
|
117344
|
+
this.stopTimer();
|
|
117345
|
+
}
|
|
117346
|
+
stopTimer() {
|
|
116806
117347
|
if (this.timer) clearInterval(this.timer);
|
|
116807
117348
|
this.timer = null;
|
|
116808
117349
|
}
|
|
116809
117350
|
startTimer() {
|
|
116810
|
-
this.
|
|
117351
|
+
this.stopTimer();
|
|
116811
117352
|
this.timer = setInterval(() => void this.check(false), CHECK_INTERVAL_MS);
|
|
116812
117353
|
this.timer.unref?.();
|
|
116813
117354
|
}
|
|
@@ -116823,7 +117364,7 @@ var UpdateService = class {
|
|
|
116823
117364
|
this.status = { ...this.status, auto: enabled };
|
|
116824
117365
|
this.deps.writeAuto(enabled);
|
|
116825
117366
|
if (!enabled) {
|
|
116826
|
-
this.
|
|
117367
|
+
this.stopTimer();
|
|
116827
117368
|
this.emit();
|
|
116828
117369
|
return this.current();
|
|
116829
117370
|
}
|
|
@@ -116860,32 +117401,36 @@ var UpdateService = class {
|
|
|
116860
117401
|
* failed, nothing about a background failure interrupts anyone.
|
|
116861
117402
|
*/
|
|
116862
117403
|
async check(force) {
|
|
117404
|
+
if (this.stopped || this.installing) return this.current();
|
|
116863
117405
|
if (this.inFlight) return this.inFlight;
|
|
116864
117406
|
if (!force && !this.status.auto) return this.current();
|
|
116865
117407
|
if (!force && this.status.checkedAt !== null && !this.isDue()) return this.current();
|
|
116866
117408
|
if (this.status.phase === "updating" || this.status.phase === "waiting_for_npm" || this.status.phase === "restart_required") return this.current();
|
|
116867
|
-
this.status
|
|
117409
|
+
const failure3 = this.status.phase === "failed" ? { error: this.status.error } : null;
|
|
117410
|
+
if (!failure3) this.status = { ...this.status, phase: "checking" };
|
|
116868
117411
|
this.emit();
|
|
116869
|
-
this.inFlight = this.runCheck(force);
|
|
117412
|
+
this.inFlight = this.runCheck(force, failure3);
|
|
116870
117413
|
try {
|
|
116871
117414
|
return await this.inFlight;
|
|
116872
117415
|
} finally {
|
|
116873
117416
|
this.inFlight = null;
|
|
116874
117417
|
}
|
|
116875
117418
|
}
|
|
116876
|
-
async runCheck(force) {
|
|
117419
|
+
async runCheck(force, failure3) {
|
|
116877
117420
|
const res = await this.deps.fetchLatest(force ? FORCED_TIMEOUT_MS : SCHEDULED_TIMEOUT_MS);
|
|
117421
|
+
if (this.stopped || this.installing) return this.current();
|
|
116878
117422
|
if (!res.ok) {
|
|
116879
|
-
this.status = { ...this.status, phase: "idle", error: res.reason };
|
|
117423
|
+
this.status = { ...this.status, phase: failure3 ? "failed" : "idle", error: failure3 ? failure3.error : res.reason };
|
|
116880
117424
|
this.emit();
|
|
116881
117425
|
return this.current();
|
|
116882
117426
|
}
|
|
117427
|
+
const latest = res.version;
|
|
116883
117428
|
this.status = {
|
|
116884
117429
|
...this.status,
|
|
116885
|
-
latest
|
|
116886
|
-
newer: isNewerVersion(
|
|
116887
|
-
phase: "idle",
|
|
116888
|
-
error: null,
|
|
117430
|
+
latest,
|
|
117431
|
+
newer: isNewerVersion(latest, this.status.current),
|
|
117432
|
+
phase: failure3 ? "failed" : "idle",
|
|
117433
|
+
error: failure3?.error ?? null,
|
|
116889
117434
|
checkedAt: this.deps.now()
|
|
116890
117435
|
};
|
|
116891
117436
|
this.emit();
|
|
@@ -116901,13 +117446,14 @@ var UpdateService = class {
|
|
|
116901
117446
|
* rest of the story arrives as `update_status` events.
|
|
116902
117447
|
*/
|
|
116903
117448
|
apply() {
|
|
116904
|
-
if (this.
|
|
117449
|
+
if (this.stopped || this.installing || this.status.phase === "restart_required") return this.current();
|
|
116905
117450
|
const latest = this.status.latest;
|
|
116906
117451
|
if (!this.status.newer || !latest) {
|
|
116907
117452
|
this.status = { ...this.status, phase: "failed", error: "There is no newer version to install" };
|
|
116908
117453
|
this.emit();
|
|
116909
117454
|
return this.current();
|
|
116910
117455
|
}
|
|
117456
|
+
this.installing = true;
|
|
116911
117457
|
this.status = { ...this.status, phase: "updating", error: null };
|
|
116912
117458
|
this.emit();
|
|
116913
117459
|
void this.runApply(latest);
|
|
@@ -116917,16 +117463,42 @@ var UpdateService = class {
|
|
|
116917
117463
|
let packageInstalled = false;
|
|
116918
117464
|
const copy = this.deps.installedCopy();
|
|
116919
117465
|
try {
|
|
117466
|
+
await this.inFlight;
|
|
117467
|
+
if (this.stopped) return;
|
|
117468
|
+
const age2 = this.status.checkedAt === null ? Infinity : this.deps.now() - this.status.checkedAt;
|
|
117469
|
+
if (age2 < 0 || age2 >= INSTALL_CHECK_MAX_AGE_MS) {
|
|
117470
|
+
const fresh = await this.deps.fetchLatest(FORCED_TIMEOUT_MS);
|
|
117471
|
+
if (this.stopped) return;
|
|
117472
|
+
if (fresh.ok) {
|
|
117473
|
+
const now = this.deps.now();
|
|
117474
|
+
const storedAge = this.status.checkedAt === null ? Infinity : now - this.status.checkedAt;
|
|
117475
|
+
const keepStored = storedAge >= 0 && storedAge < STORED_RELEASE_MAX_AGE_MS && isNewerVersion(version2, fresh.version);
|
|
117476
|
+
if (!keepStored) version2 = fresh.version;
|
|
117477
|
+
this.status = { ...this.status, latest: version2, newer: isNewerVersion(version2, this.status.current), checkedAt: keepStored ? this.status.checkedAt : now };
|
|
117478
|
+
if (!this.status.newer) {
|
|
117479
|
+
this.status = { ...this.status, phase: "idle", error: null };
|
|
117480
|
+
return;
|
|
117481
|
+
}
|
|
117482
|
+
} else {
|
|
117483
|
+
this.status = { ...this.status, error: `Could not refresh latest (${fresh.reason}); using previously checked ${version2}.` };
|
|
117484
|
+
}
|
|
117485
|
+
}
|
|
117486
|
+
this.emit();
|
|
116920
117487
|
await this.installPackage(version2);
|
|
117488
|
+
if (this.stopped) return;
|
|
116921
117489
|
packageInstalled = true;
|
|
116922
117490
|
if (copy) await this.deps.run(APP_SLUG, ["install"]);
|
|
116923
|
-
|
|
117491
|
+
if (this.stopped) return;
|
|
117492
|
+
this.status = { ...this.status, phase: "restart_required" };
|
|
116924
117493
|
} catch (e) {
|
|
117494
|
+
if (this.stopped) return;
|
|
116925
117495
|
const reason = e.message;
|
|
116926
117496
|
const error51 = packageInstalled ? `npm installed ${version2}, but the copy at ${copy} was not replaced (${reason}). Quit ${APP_NAME}, then run "${APP_SLUG} install" in a terminal.` : reason;
|
|
116927
117497
|
this.status = { ...this.status, phase: "failed", error: error51 };
|
|
117498
|
+
} finally {
|
|
117499
|
+
this.installing = false;
|
|
117500
|
+
if (!this.stopped) this.emit();
|
|
116928
117501
|
}
|
|
116929
|
-
this.emit();
|
|
116930
117502
|
}
|
|
116931
117503
|
/**
|
|
116932
117504
|
* A just-published release can be visible from `latest` before its package is on the npm edge
|
|
@@ -116936,16 +117508,20 @@ var UpdateService = class {
|
|
|
116936
117508
|
*/
|
|
116937
117509
|
async installPackage(version2) {
|
|
116938
117510
|
const started = this.deps.now();
|
|
117511
|
+
const notice = this.status.error;
|
|
116939
117512
|
for (; ; ) {
|
|
117513
|
+
if (this.stopped) return;
|
|
116940
117514
|
try {
|
|
116941
117515
|
await this.deps.run("npm", ["i", "-g", "--prefer-online", `${APP_SLUG}@${version2}`]);
|
|
116942
117516
|
return;
|
|
116943
117517
|
} catch (err) {
|
|
117518
|
+
if (this.stopped) return;
|
|
116944
117519
|
if (!npmHasNotServedVersion(err) || this.deps.now() - started >= NPM_RETRY_WINDOW_MS) throw err;
|
|
116945
|
-
this.status = { ...this.status, phase: "waiting_for_npm", error: `Waiting for npm to serve ${version2}` };
|
|
117520
|
+
this.status = { ...this.status, phase: "waiting_for_npm", error: `Waiting for npm to serve ${version2}${notice ? `. ${notice}` : ""}` };
|
|
116946
117521
|
this.emit();
|
|
116947
117522
|
await this.deps.wait(NPM_RETRY_DELAY_MS);
|
|
116948
|
-
|
|
117523
|
+
if (this.stopped) return;
|
|
117524
|
+
this.status = { ...this.status, phase: "updating", error: notice };
|
|
116949
117525
|
this.emit();
|
|
116950
117526
|
}
|
|
116951
117527
|
}
|
|
@@ -117305,7 +117881,7 @@ var SessionSleeper = class {
|
|
|
117305
117881
|
// packages/agent-host/src/dev-services/instance-lock.ts
|
|
117306
117882
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
117307
117883
|
import { readFileSync as readFileSync13, unlinkSync as unlinkSync3, writeFileSync as writeFileSync7 } from "node:fs";
|
|
117308
|
-
import { dirname as dirname10, join as
|
|
117884
|
+
import { dirname as dirname10, join as join31 } from "node:path";
|
|
117309
117885
|
import Database2 from "better-sqlite3";
|
|
117310
117886
|
var OWNERSHIP_FILE = "host-ownership.sqlite";
|
|
117311
117887
|
var held = /* @__PURE__ */ new Set();
|
|
@@ -117339,7 +117915,7 @@ function parseHolder(raw) {
|
|
|
117339
117915
|
function acquireInstanceLock(dbPath2, startOf = processStartTime) {
|
|
117340
117916
|
if (dbPath2 === ":memory:") return { ok: true, release: () => {
|
|
117341
117917
|
} };
|
|
117342
|
-
const lockPath =
|
|
117918
|
+
const lockPath = join31(dirname10(dbPath2), "host.lock");
|
|
117343
117919
|
const readHolder = () => {
|
|
117344
117920
|
try {
|
|
117345
117921
|
return parseHolder(readFileSync13(lockPath, "utf8"));
|
|
@@ -117347,7 +117923,7 @@ function acquireInstanceLock(dbPath2, startOf = processStartTime) {
|
|
|
117347
117923
|
return null;
|
|
117348
117924
|
}
|
|
117349
117925
|
};
|
|
117350
|
-
const owner = takeOwnership(
|
|
117926
|
+
const owner = takeOwnership(join31(dirname10(dbPath2), OWNERSHIP_FILE));
|
|
117351
117927
|
if (!owner) {
|
|
117352
117928
|
const holder = readHolder();
|
|
117353
117929
|
return { ok: false, heldByPid: holder && Number.isInteger(holder.pid) && holder.pid > 0 ? holder.pid : null, lockPath };
|
|
@@ -117668,9 +118244,9 @@ var ControlChannel = class {
|
|
|
117668
118244
|
}
|
|
117669
118245
|
next(op) {
|
|
117670
118246
|
if (this.ended) return Promise.reject(new Error("the keeper closed the control channel"));
|
|
117671
|
-
return new Promise((
|
|
118247
|
+
return new Promise((resolve12, reject) => {
|
|
117672
118248
|
const list = this.waiters.get(op) ?? [];
|
|
117673
|
-
list.push({ resolve:
|
|
118249
|
+
list.push({ resolve: resolve12, reject });
|
|
117674
118250
|
this.waiters.set(op, list);
|
|
117675
118251
|
});
|
|
117676
118252
|
}
|
|
@@ -117775,7 +118351,7 @@ async function runDrain(bound, deps) {
|
|
|
117775
118351
|
}
|
|
117776
118352
|
|
|
117777
118353
|
// packages/agent-host/src/links/install.ts
|
|
117778
|
-
import { existsSync as
|
|
118354
|
+
import { existsSync as existsSync22, readFileSync as readFileSync14 } from "node:fs";
|
|
117779
118355
|
import { hostname as hostname3 } from "node:os";
|
|
117780
118356
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
117781
118357
|
import { gzipSync } from "node:zlib";
|
|
@@ -117836,7 +118412,7 @@ async function verifiedPackage(name, version2, opts = {}) {
|
|
|
117836
118412
|
}
|
|
117837
118413
|
|
|
117838
118414
|
// packages/agent-host/src/links/tunnel.ts
|
|
117839
|
-
import { spawn as
|
|
118415
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
117840
118416
|
import { createConnection as createConnection2, createServer as createServer3 } from "node:net";
|
|
117841
118417
|
function parseConnectionLine(stdout) {
|
|
117842
118418
|
const lines = stdout.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
@@ -118099,8 +118675,8 @@ var SshTunnel = class {
|
|
|
118099
118675
|
if (this.closing) throw new Error(`The link to ${this.opts.target} was closed`);
|
|
118100
118676
|
}
|
|
118101
118677
|
run(args, stdin) {
|
|
118102
|
-
return new Promise((
|
|
118103
|
-
const child =
|
|
118678
|
+
return new Promise((resolve12, reject) => {
|
|
118679
|
+
const child = spawn6(this.opts.ssh ?? "ssh", args, { env: this.opts.env ?? process.env, stdio: [stdin === void 0 ? "ignore" : "pipe", "pipe", "pipe"] });
|
|
118104
118680
|
if (stdin !== void 0) {
|
|
118105
118681
|
child.stdin.on("error", () => {
|
|
118106
118682
|
});
|
|
@@ -118116,7 +118692,7 @@ var SshTunnel = class {
|
|
|
118116
118692
|
this.asking.delete(child);
|
|
118117
118693
|
reject(e);
|
|
118118
118694
|
});
|
|
118119
|
-
child.once("close", (code2) =>
|
|
118695
|
+
child.once("close", (code2) => resolve12({ code: code2, stdout, stderr }));
|
|
118120
118696
|
});
|
|
118121
118697
|
}
|
|
118122
118698
|
config() {
|
|
@@ -118159,11 +118735,11 @@ var SshTunnel = class {
|
|
|
118159
118735
|
this.opts.target,
|
|
118160
118736
|
...keepAlive ? [keepAlive] : []
|
|
118161
118737
|
];
|
|
118162
|
-
const child =
|
|
118738
|
+
const child = spawn6(this.opts.ssh ?? "ssh", args, { env: this.opts.env ?? process.env, stdio: ["ignore", "ignore", "pipe"] });
|
|
118163
118739
|
this.child = child;
|
|
118164
118740
|
let stderr = "";
|
|
118165
118741
|
child.stderr.on("data", (d2) => stderr += String(d2));
|
|
118166
|
-
return new Promise((
|
|
118742
|
+
return new Promise((resolve12, reject) => {
|
|
118167
118743
|
let settled = false;
|
|
118168
118744
|
const deadline = Date.now() + (this.opts.forwardTimeoutMs ?? 2e4);
|
|
118169
118745
|
const fail4 = (message) => {
|
|
@@ -118182,7 +118758,7 @@ var SshTunnel = class {
|
|
|
118182
118758
|
if (await accepts(localPort)) {
|
|
118183
118759
|
settled = true;
|
|
118184
118760
|
this.opts.log?.(`[links] forward up: 127.0.0.1:${localPort} -> ${this.opts.target}:127.0.0.1:${remotePort}`);
|
|
118185
|
-
return
|
|
118761
|
+
return resolve12();
|
|
118186
118762
|
}
|
|
118187
118763
|
if (Date.now() > deadline) {
|
|
118188
118764
|
this.ended.add(child);
|
|
@@ -118200,12 +118776,12 @@ var SshTunnel = class {
|
|
|
118200
118776
|
this.child = null;
|
|
118201
118777
|
if (!child || child.exitCode !== null || child.signalCode !== null) return;
|
|
118202
118778
|
this.ended.add(child);
|
|
118203
|
-
await new Promise((
|
|
118204
|
-
child.once("exit", () =>
|
|
118779
|
+
await new Promise((resolve12) => {
|
|
118780
|
+
child.once("exit", () => resolve12());
|
|
118205
118781
|
child.kill("SIGTERM");
|
|
118206
118782
|
setTimeout(() => {
|
|
118207
118783
|
child.kill("SIGKILL");
|
|
118208
|
-
|
|
118784
|
+
resolve12();
|
|
118209
118785
|
}, 2e3).unref();
|
|
118210
118786
|
});
|
|
118211
118787
|
}
|
|
@@ -118215,29 +118791,29 @@ function lastLine(s) {
|
|
|
118215
118791
|
return [...lines].reverse().find((l) => /error|denied|refused|timed out|not found|cannot|could not/i.test(l)) ?? lines.at(-1) ?? "";
|
|
118216
118792
|
}
|
|
118217
118793
|
function accepts(port2) {
|
|
118218
|
-
return new Promise((
|
|
118794
|
+
return new Promise((resolve12) => {
|
|
118219
118795
|
const s = createConnection2({ host: "127.0.0.1", port: port2 });
|
|
118220
118796
|
s.once("connect", () => {
|
|
118221
118797
|
s.destroy();
|
|
118222
|
-
|
|
118798
|
+
resolve12(true);
|
|
118223
118799
|
});
|
|
118224
|
-
s.once("error", () =>
|
|
118800
|
+
s.once("error", () => resolve12(false));
|
|
118225
118801
|
});
|
|
118226
118802
|
}
|
|
118227
118803
|
function portFree(port2) {
|
|
118228
|
-
return new Promise((
|
|
118804
|
+
return new Promise((resolve12) => {
|
|
118229
118805
|
const probe = createServer3();
|
|
118230
|
-
probe.once("error", () =>
|
|
118231
|
-
probe.listen(port2, "127.0.0.1", () => probe.close(() =>
|
|
118806
|
+
probe.once("error", () => resolve12(false));
|
|
118807
|
+
probe.listen(port2, "127.0.0.1", () => probe.close(() => resolve12(true)));
|
|
118232
118808
|
});
|
|
118233
118809
|
}
|
|
118234
118810
|
function freePort() {
|
|
118235
|
-
return new Promise((
|
|
118811
|
+
return new Promise((resolve12, reject) => {
|
|
118236
118812
|
const probe = createServer3();
|
|
118237
118813
|
probe.once("error", reject);
|
|
118238
118814
|
probe.listen(0, "127.0.0.1", () => {
|
|
118239
118815
|
const { port: port2 } = probe.address();
|
|
118240
|
-
probe.close(() =>
|
|
118816
|
+
probe.close(() => resolve12(port2));
|
|
118241
118817
|
});
|
|
118242
118818
|
});
|
|
118243
118819
|
}
|
|
@@ -118453,7 +119029,7 @@ async function installRemote(o) {
|
|
|
118453
119029
|
function bundled(name, source) {
|
|
118454
119030
|
for (const url2 of [new URL(`./${name}`, import.meta.url), new URL(source, import.meta.url)]) {
|
|
118455
119031
|
const file2 = fileURLToPath6(url2);
|
|
118456
|
-
if (
|
|
119032
|
+
if (existsSync22(file2)) return readFileSync14(file2, "utf8");
|
|
118457
119033
|
}
|
|
118458
119034
|
throw new Error(`${name} is missing from this host`);
|
|
118459
119035
|
}
|
|
@@ -118511,7 +119087,7 @@ async function removeAutostart(c) {
|
|
|
118511
119087
|
}
|
|
118512
119088
|
|
|
118513
119089
|
// packages/agent-host/src/links/probe.ts
|
|
118514
|
-
import { spawn as
|
|
119090
|
+
import { spawn as spawn7 } from "node:child_process";
|
|
118515
119091
|
var PROBE_MARK = "CENTRALU-PROBE";
|
|
118516
119092
|
var PROBE_PS = [
|
|
118517
119093
|
"$ErrorActionPreference = 'SilentlyContinue'",
|
|
@@ -118556,7 +119132,7 @@ function probeTarget(target, opts = {}) {
|
|
|
118556
119132
|
return Promise.reject(Object.assign(new Error("Not an ssh target: use a host alias from ~/.ssh/config, or user@host"), { code: "internal" }));
|
|
118557
119133
|
}
|
|
118558
119134
|
const args = [...opts.configFile ? ["-F", opts.configFile] : [], "-T", "-o", "BatchMode=yes", "-o", "ConnectTimeout=10", "--", t, probeCommand()];
|
|
118559
|
-
return new Promise((
|
|
119135
|
+
return new Promise((resolve12) => {
|
|
118560
119136
|
let stdout = "";
|
|
118561
119137
|
let stderr = "";
|
|
118562
119138
|
let done = false;
|
|
@@ -118564,9 +119140,9 @@ function probeTarget(target, opts = {}) {
|
|
|
118564
119140
|
if (done) return;
|
|
118565
119141
|
done = true;
|
|
118566
119142
|
clearTimeout(timer);
|
|
118567
|
-
|
|
119143
|
+
resolve12(parseProbe(t, run));
|
|
118568
119144
|
};
|
|
118569
|
-
const child =
|
|
119145
|
+
const child = spawn7(opts.ssh ?? "ssh", args, { env: opts.env ?? process.env, stdio: ["ignore", "pipe", "pipe"] });
|
|
118570
119146
|
const timer = setTimeout(() => {
|
|
118571
119147
|
child.kill("SIGTERM");
|
|
118572
119148
|
finish2({ code: 255, stdout: "", stderr: `ssh to ${t} did not answer in time` });
|
|
@@ -118579,10 +119155,10 @@ function probeTarget(target, opts = {}) {
|
|
|
118579
119155
|
}
|
|
118580
119156
|
|
|
118581
119157
|
// packages/agent-host/src/links/ssh-config.ts
|
|
118582
|
-
import { spawn as
|
|
119158
|
+
import { spawn as spawn8 } from "node:child_process";
|
|
118583
119159
|
import { readdirSync as readdirSync8, readFileSync as readFileSync15, statSync as statSync13 } from "node:fs";
|
|
118584
119160
|
import { homedir as homedir13 } from "node:os";
|
|
118585
|
-
import { isAbsolute as isAbsolute10, join as
|
|
119161
|
+
import { isAbsolute as isAbsolute10, join as join32, normalize, parse as parse6, sep as sep12 } from "node:path";
|
|
118586
119162
|
var MAX_DEPTH2 = 16;
|
|
118587
119163
|
function splitConfigLine(raw) {
|
|
118588
119164
|
const m = /^\s*([^\s=#]+)\s*(?:=\s*)?([\s\S]*)$/.exec(raw);
|
|
@@ -118648,7 +119224,7 @@ function expandGlob(path2) {
|
|
|
118648
119224
|
const next = [];
|
|
118649
119225
|
for (const base2 of found) {
|
|
118650
119226
|
if (!MAGIC.test(seg)) {
|
|
118651
|
-
next.push(
|
|
119227
|
+
next.push(join32(base2, seg));
|
|
118652
119228
|
continue;
|
|
118653
119229
|
}
|
|
118654
119230
|
let names;
|
|
@@ -118660,7 +119236,7 @@ function expandGlob(path2) {
|
|
|
118660
119236
|
const re2 = segmentRe(seg);
|
|
118661
119237
|
for (const name of names.sort()) {
|
|
118662
119238
|
if (name.startsWith(".") && !seg.startsWith(".")) continue;
|
|
118663
|
-
if (re2.test(name)) next.push(
|
|
119239
|
+
if (re2.test(name)) next.push(join32(base2, name));
|
|
118664
119240
|
}
|
|
118665
119241
|
}
|
|
118666
119242
|
found = next;
|
|
@@ -118715,11 +119291,11 @@ function readSshConfig(opts) {
|
|
|
118715
119291
|
}
|
|
118716
119292
|
for (const arg of split2.args) {
|
|
118717
119293
|
let path2 = arg;
|
|
118718
|
-
if (path2.startsWith("~/")) path2 =
|
|
119294
|
+
if (path2.startsWith("~/")) path2 = join32(opts.home, path2.slice(2));
|
|
118719
119295
|
else if (path2.startsWith("~")) {
|
|
118720
119296
|
notes.push(`An Include of another user's folder (${where(file2, n + 1)}) was not read`);
|
|
118721
119297
|
continue;
|
|
118722
|
-
} else if (!isAbsolute10(path2)) path2 =
|
|
119298
|
+
} else if (!isAbsolute10(path2)) path2 = join32(opts.home, ".ssh", path2);
|
|
118723
119299
|
for (const f of expandGlob(path2)) read(f, depth + 1);
|
|
118724
119300
|
}
|
|
118725
119301
|
}
|
|
@@ -118742,7 +119318,7 @@ function parseSshG(stdout) {
|
|
|
118742
119318
|
return { hostName: first.get("hostname") || null, user: first.get("user") || null, port: Number.isInteger(port2) && port2 > 0 ? port2 : null };
|
|
118743
119319
|
}
|
|
118744
119320
|
function resolveAlias(alias, opts = {}) {
|
|
118745
|
-
return new Promise((
|
|
119321
|
+
return new Promise((resolve12) => {
|
|
118746
119322
|
const args = [...opts.configFile ? ["-F", opts.configFile] : [], "-G", "--", alias];
|
|
118747
119323
|
let stdout = "";
|
|
118748
119324
|
let stderr = "";
|
|
@@ -118751,10 +119327,10 @@ function resolveAlias(alias, opts = {}) {
|
|
|
118751
119327
|
if (done) return;
|
|
118752
119328
|
done = true;
|
|
118753
119329
|
clearTimeout(timer);
|
|
118754
|
-
|
|
119330
|
+
resolve12(r);
|
|
118755
119331
|
};
|
|
118756
119332
|
const none = (error51) => ({ hostName: null, user: null, port: null, error: error51 });
|
|
118757
|
-
const child =
|
|
119333
|
+
const child = spawn8(opts.ssh ?? "ssh", args, { env: opts.env ?? process.env, stdio: ["ignore", "pipe", "pipe"] });
|
|
118758
119334
|
const timer = setTimeout(() => {
|
|
118759
119335
|
child.kill("SIGKILL");
|
|
118760
119336
|
finish2(none("ssh -G did not answer"));
|
|
@@ -118769,18 +119345,18 @@ var MAX_HOSTS = 100;
|
|
|
118769
119345
|
var RESOLVE_AT_ONCE = 8;
|
|
118770
119346
|
async function listSshHosts(opts = {}) {
|
|
118771
119347
|
const home = opts.home ?? homedir13();
|
|
118772
|
-
const file2 = opts.configFile ??
|
|
119348
|
+
const file2 = opts.configFile ?? join32(home, ".ssh", "config");
|
|
118773
119349
|
const read = readSshConfig({ file: file2, home });
|
|
118774
119350
|
const notes = [...read.notes];
|
|
118775
119351
|
const listed = read.hosts.slice(0, MAX_HOSTS);
|
|
118776
119352
|
if (read.hosts.length > MAX_HOSTS) notes.push(`Only the first ${MAX_HOSTS} of ${read.hosts.length} hosts are listed; type any other below`);
|
|
118777
|
-
const
|
|
119353
|
+
const resolve12 = opts.resolve ?? ((alias) => resolveAlias(alias, opts));
|
|
118778
119354
|
const resolved = new Array(listed.length);
|
|
118779
119355
|
let next = 0;
|
|
118780
119356
|
const worker = async () => {
|
|
118781
119357
|
while (next < listed.length) {
|
|
118782
119358
|
const i = next++;
|
|
118783
|
-
resolved[i] = await
|
|
119359
|
+
resolved[i] = await resolve12(listed[i].alias);
|
|
118784
119360
|
}
|
|
118785
119361
|
};
|
|
118786
119362
|
await Promise.all(Array.from({ length: Math.min(RESOLVE_AT_ONCE, listed.length) }, worker));
|
|
@@ -119269,11 +119845,11 @@ var RemoteClient = class {
|
|
|
119269
119845
|
if (!this.ready || !this.ws) return Promise.reject(linkError("The machine is not reachable right now", true));
|
|
119270
119846
|
const ws2 = this.ws;
|
|
119271
119847
|
const id2 = String(this.nextId++);
|
|
119272
|
-
return new Promise((
|
|
119848
|
+
return new Promise((resolve12, reject) => {
|
|
119273
119849
|
const timer = setTimeout(() => {
|
|
119274
119850
|
if (this.pending.delete(id2)) reject(Object.assign(new Error(`The machine did not answer ${method} in time`), { code: "internal", retryable: true }));
|
|
119275
119851
|
}, this.opts.callTimeoutMs ?? DEFAULT_CALL_TIMEOUT_MS);
|
|
119276
|
-
this.pending.set(id2, { resolve:
|
|
119852
|
+
this.pending.set(id2, { resolve: resolve12, reject, timer });
|
|
119277
119853
|
ws2.send(JSON.stringify({ kind: "rpc", id: id2, method, params }));
|
|
119278
119854
|
});
|
|
119279
119855
|
}
|
|
@@ -119636,14 +120212,14 @@ var LinkedMachine = class {
|
|
|
119636
120212
|
if (this.client?.connected) return this.client.call("host.activity", {}).then(read, () => null);
|
|
119637
120213
|
const e = this.endpoint;
|
|
119638
120214
|
if (!e?.url || this.versions?.compatible === false || this.held) return Promise.resolve(null);
|
|
119639
|
-
return new Promise((
|
|
120215
|
+
return new Promise((resolve12) => {
|
|
119640
120216
|
let settled = false;
|
|
119641
120217
|
const done = (v) => {
|
|
119642
120218
|
if (settled) return;
|
|
119643
120219
|
settled = true;
|
|
119644
120220
|
clearTimeout(timer);
|
|
119645
120221
|
client.close();
|
|
119646
|
-
|
|
120222
|
+
resolve12(v);
|
|
119647
120223
|
};
|
|
119648
120224
|
const client = new RemoteClient(
|
|
119649
120225
|
{ ...this.deps.client, url: e.url, token: e.token, handshakeTimeoutMs: 5e3, callTimeoutMs: 5e3 },
|
|
@@ -119717,16 +120293,16 @@ var LinkedMachine = class {
|
|
|
119717
120293
|
const started = await this.tunnel.startHost();
|
|
119718
120294
|
this.hostStarted = { ...started, at: Date.now() };
|
|
119719
120295
|
},
|
|
119720
|
-
answers: (version2, ms3) => new Promise((
|
|
120296
|
+
answers: (version2, ms3) => new Promise((resolve12) => {
|
|
119721
120297
|
const waiter = (v) => {
|
|
119722
120298
|
if (v !== version2) return;
|
|
119723
120299
|
clearTimeout(timer);
|
|
119724
120300
|
this.helloWaiters.delete(waiter);
|
|
119725
|
-
|
|
120301
|
+
resolve12(true);
|
|
119726
120302
|
};
|
|
119727
120303
|
const timer = setTimeout(() => {
|
|
119728
120304
|
this.helloWaiters.delete(waiter);
|
|
119729
|
-
void hold().then(() =>
|
|
120305
|
+
void hold().then(() => resolve12(false));
|
|
119730
120306
|
}, this.deps.checkMs ?? ms3);
|
|
119731
120307
|
this.helloWaiters.add(waiter);
|
|
119732
120308
|
this.held = false;
|
|
@@ -120875,7 +121451,7 @@ function storeMirror(store2) {
|
|
|
120875
121451
|
var start = new StartSequence();
|
|
120876
121452
|
start.at("launch variables");
|
|
120877
121453
|
var launch = takeLaunchEnv();
|
|
120878
|
-
var BUILD = true ? "
|
|
121454
|
+
var BUILD = true ? "8dd78b6" : "dev";
|
|
120879
121455
|
var { values } = parseArgs({
|
|
120880
121456
|
options: {
|
|
120881
121457
|
port: { type: "string", default: "5175" },
|
|
@@ -120896,12 +121472,12 @@ start.at("data folder");
|
|
|
120896
121472
|
var movedNote = null;
|
|
120897
121473
|
function defaultDbPath() {
|
|
120898
121474
|
const isDev = process.env.CC_DEV === "1";
|
|
120899
|
-
const dir =
|
|
120900
|
-
const legacy =
|
|
121475
|
+
const dir = join33(homedir14(), isDev ? DATA_DIR_DEV : DATA_DIR);
|
|
121476
|
+
const legacy = join33(homedir14(), isDev ? DATA_DIR_LEGACY.dev : DATA_DIR_LEGACY.prod);
|
|
120901
121477
|
movedNote = migrateLegacyDataDir(legacy, dir) ? `[agent-host] data folder moved: ${legacy} -> ${dir}` : null;
|
|
120902
121478
|
mkdirSync11(dir, { recursive: true });
|
|
120903
121479
|
process.env.CC_DATA_DIR = dir;
|
|
120904
|
-
return
|
|
121480
|
+
return join33(dir, "store.db");
|
|
120905
121481
|
}
|
|
120906
121482
|
var dbPath = values.memory ? ":memory:" : values.db ?? defaultDbPath();
|
|
120907
121483
|
start.at("log");
|
|
@@ -120979,7 +121555,7 @@ var mgr = new SessionManager(
|
|
|
120979
121555
|
},
|
|
120980
121556
|
(sessionId) => bridgeAddress(launch.frontDoor, port, token, sessionId),
|
|
120981
121557
|
// Worktrees are created next to the data folder — dev and the packaged app never touch each other's worktrees
|
|
120982
|
-
|
|
121558
|
+
join33(dirname11(dbPath), "worktrees"),
|
|
120983
121559
|
held2 ? { processes: held2.processes, keptSessions: held2.kept.sessionIds } : {}
|
|
120984
121560
|
);
|
|
120985
121561
|
mgr.useProjectsHome(() => projectsHome());
|
|
@@ -121013,7 +121589,7 @@ if (held2) {
|
|
|
121013
121589
|
}
|
|
121014
121590
|
var AUTO_UPDATE_CHECK_KEY = "updates.auto";
|
|
121015
121591
|
var AUTO_APPLY_UPDATES_KEY = "updates.autoApply";
|
|
121016
|
-
var themes = new ThemeFiles(
|
|
121592
|
+
var themes = new ThemeFiles(join33(dataRoot(), "themes"), () => events.push({ type: "themes_changed" }));
|
|
121017
121593
|
await themes.start().catch((e) => console.error(`[themes] the themes folder is unavailable: ${e.message}`));
|
|
121018
121594
|
var updates = new UpdateService((status) => events.push({ type: "update_status", status }), {
|
|
121019
121595
|
// On by default when nothing has been saved yet. Since the check is read-only and every failure
|
|
@@ -121232,7 +121808,7 @@ var endings = wireEndings({
|
|
|
121232
121808
|
heldChildren: held2 !== null,
|
|
121233
121809
|
leave: (mode, handOver) => void shutdown.leave(mode, handOver),
|
|
121234
121810
|
force: (why) => shutdown.force(why),
|
|
121235
|
-
record: crashRecorder(
|
|
121811
|
+
record: crashRecorder(join33(dirname11(dbPath), "host-errors.log"), (line) => console.error(line)),
|
|
121236
121812
|
stdin: process.stdin,
|
|
121237
121813
|
watchParent: values["watch-parent"] === true,
|
|
121238
121814
|
log: (line) => console.error(line)
|