@centralu/win32-x64 0.1.0-beta.20 → 0.1.0-beta.21
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.
|
@@ -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.21";
|
|
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
|
}
|
|
@@ -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'",
|
|
@@ -68738,14 +68737,14 @@ function inputRequiredRoundsExceededMessage(method, maxRounds) {
|
|
|
68738
68737
|
return `Multi-round-trip request '${method}' still required input after ${maxRounds} rounds (inputRequired.maxRounds)`;
|
|
68739
68738
|
}
|
|
68740
68739
|
function sleep(ms3, signal) {
|
|
68741
|
-
return new Promise((
|
|
68740
|
+
return new Promise((resolve12, reject) => {
|
|
68742
68741
|
if (signal?.aborted) {
|
|
68743
68742
|
reject(signal.reason instanceof SdkError ? signal.reason : new SdkError(SdkErrorCode.RequestTimeout, String(signal.reason)));
|
|
68744
68743
|
return;
|
|
68745
68744
|
}
|
|
68746
68745
|
const timer = setTimeout(() => {
|
|
68747
68746
|
signal?.removeEventListener("abort", onAbort);
|
|
68748
|
-
|
|
68747
|
+
resolve12();
|
|
68749
68748
|
}, ms3);
|
|
68750
68749
|
const onAbort = () => {
|
|
68751
68750
|
clearTimeout(timer);
|
|
@@ -69584,7 +69583,7 @@ var Protocol = class {
|
|
|
69584
69583
|
const flowStartedAt = Date.now();
|
|
69585
69584
|
let onAbort;
|
|
69586
69585
|
let cleanupMessageId;
|
|
69587
|
-
return new Promise((
|
|
69586
|
+
return new Promise((resolve12, reject) => {
|
|
69588
69587
|
const earlyReject = (error51) => {
|
|
69589
69588
|
reject(error51);
|
|
69590
69589
|
};
|
|
@@ -69653,7 +69652,7 @@ var Protocol = class {
|
|
|
69653
69652
|
}
|
|
69654
69653
|
if (decoded.kind === "invalid") return reject(decoded.error);
|
|
69655
69654
|
if (decoded.kind === "input_required") {
|
|
69656
|
-
if (options?.allowInputRequired === true) return
|
|
69655
|
+
if (options?.allowInputRequired === true) return resolve12(manualInputRequiredValue(decoded));
|
|
69657
69656
|
const flow = {
|
|
69658
69657
|
codec: codec2,
|
|
69659
69658
|
request,
|
|
@@ -69665,11 +69664,11 @@ var Protocol = class {
|
|
|
69665
69664
|
params
|
|
69666
69665
|
}, resultSchema, legOptions)
|
|
69667
69666
|
};
|
|
69668
|
-
return
|
|
69667
|
+
return resolve12(this._resolveNonCompleteResult(decoded, flow));
|
|
69669
69668
|
}
|
|
69670
69669
|
const result = decoded.result;
|
|
69671
69670
|
validateStandardSchema(resultSchema, result).then((parseResult) => {
|
|
69672
|
-
if (parseResult.success)
|
|
69671
|
+
if (parseResult.success) resolve12(parseResult.data);
|
|
69673
69672
|
else reject(new SdkError(SdkErrorCode.InvalidResult, `Invalid result for ${request.method}: ${parseResult.error}`));
|
|
69674
69673
|
}, reject);
|
|
69675
69674
|
});
|
|
@@ -72592,7 +72591,7 @@ var require_compile = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
72592
72591
|
ref = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, ref);
|
|
72593
72592
|
const schOrFunc = root3.refs[ref];
|
|
72594
72593
|
if (schOrFunc) return schOrFunc;
|
|
72595
|
-
let _sch =
|
|
72594
|
+
let _sch = resolve12.call(this, root3, ref);
|
|
72596
72595
|
if (_sch === void 0) {
|
|
72597
72596
|
const schema = (_a4 = root3.localRefs) === null || _a4 === void 0 ? void 0 : _a4[ref];
|
|
72598
72597
|
const { schemaId } = this.opts;
|
|
@@ -72618,7 +72617,7 @@ var require_compile = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
72618
72617
|
function sameSchemaEnv(s12, s22) {
|
|
72619
72618
|
return s12.schema === s22.schema && s12.root === s22.root && s12.baseId === s22.baseId;
|
|
72620
72619
|
}
|
|
72621
|
-
function
|
|
72620
|
+
function resolve12(root3, ref) {
|
|
72622
72621
|
let sch;
|
|
72623
72622
|
while (typeof (sch = this.refs[ref]) == "string") ref = sch;
|
|
72624
72623
|
return sch || this.schemas[ref] || resolveSchema.call(this, root3, ref);
|
|
@@ -73068,7 +73067,7 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
73068
73067
|
else if (typeof uri === "object") uri = parse7(serialize(uri, options), options);
|
|
73069
73068
|
return uri;
|
|
73070
73069
|
}
|
|
73071
|
-
function
|
|
73070
|
+
function resolve12(baseURI, relativeURI, options) {
|
|
73072
73071
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
73073
73072
|
const resolved = resolveComponent(parse7(baseURI, schemelessOptions), parse7(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
73074
73073
|
schemelessOptions.skipEscape = true;
|
|
@@ -73242,7 +73241,7 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
73242
73241
|
const fastUri = {
|
|
73243
73242
|
SCHEMES,
|
|
73244
73243
|
normalize: normalize2,
|
|
73245
|
-
resolve:
|
|
73244
|
+
resolve: resolve12,
|
|
73246
73245
|
resolveComponent,
|
|
73247
73246
|
equal,
|
|
73248
73247
|
serialize,
|
|
@@ -77857,14 +77856,14 @@ var ProbeWindow = class ProbeWindow2 {
|
|
|
77857
77856
|
*/
|
|
77858
77857
|
async exchange(buildRequest, timeoutMs) {
|
|
77859
77858
|
const id2 = `server-discover-probe-${++this._probeCounter}`;
|
|
77860
|
-
return new Promise((
|
|
77859
|
+
return new Promise((resolve12) => {
|
|
77861
77860
|
let settled = false;
|
|
77862
77861
|
const settle = (reply) => {
|
|
77863
77862
|
if (settled) return;
|
|
77864
77863
|
settled = true;
|
|
77865
77864
|
clearTimeout(timer);
|
|
77866
77865
|
if (this._pending?.id === id2) this._pending = void 0;
|
|
77867
|
-
|
|
77866
|
+
resolve12(reply);
|
|
77868
77867
|
};
|
|
77869
77868
|
const timer = setTimeout(() => settle({ kind: "timeout" }), timeoutMs);
|
|
77870
77869
|
this._pending = {
|
|
@@ -79070,13 +79069,13 @@ var Client = class extends Protocol {
|
|
|
79070
79069
|
let onCallerAbort;
|
|
79071
79070
|
let resolveOpening;
|
|
79072
79071
|
let rejectOpening;
|
|
79073
|
-
const opening = new Promise((
|
|
79074
|
-
resolveOpening =
|
|
79072
|
+
const opening = new Promise((resolve12, reject) => {
|
|
79073
|
+
resolveOpening = resolve12;
|
|
79075
79074
|
rejectOpening = reject;
|
|
79076
79075
|
});
|
|
79077
79076
|
let resolveClosed;
|
|
79078
|
-
const closed = new Promise((
|
|
79079
|
-
resolveClosed =
|
|
79077
|
+
const closed = new Promise((resolve12) => {
|
|
79078
|
+
resolveClosed = resolve12;
|
|
79080
79079
|
});
|
|
79081
79080
|
const settle = (outcome) => {
|
|
79082
79081
|
if (state === "closed") return;
|
|
@@ -79848,8 +79847,8 @@ var StreamTransport = class {
|
|
|
79848
79847
|
}
|
|
79849
79848
|
send(message) {
|
|
79850
79849
|
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) :
|
|
79850
|
+
return new Promise((resolve12, reject) => {
|
|
79851
|
+
this.writable.write(serializeMessage(message), (err) => err ? reject(err) : resolve12());
|
|
79853
79852
|
});
|
|
79854
79853
|
}
|
|
79855
79854
|
/** Closes only our own state. Closing the pipes is the shutdown rule's job (AppProcess.stop). */
|
|
@@ -80061,12 +80060,12 @@ ${tail}` : head;
|
|
|
80061
80060
|
}
|
|
80062
80061
|
waitExit(ms3) {
|
|
80063
80062
|
if (!this.alive) return Promise.resolve(true);
|
|
80064
|
-
return new Promise((
|
|
80065
|
-
const t = setTimeout(() =>
|
|
80063
|
+
return new Promise((resolve12) => {
|
|
80064
|
+
const t = setTimeout(() => resolve12(false), ms3);
|
|
80066
80065
|
t.unref();
|
|
80067
80066
|
this.exitWaiters.push(() => {
|
|
80068
80067
|
clearTimeout(t);
|
|
80069
|
-
|
|
80068
|
+
resolve12(true);
|
|
80070
80069
|
});
|
|
80071
80070
|
});
|
|
80072
80071
|
}
|
|
@@ -83653,14 +83652,14 @@ function inputRequiredRoundsExceededMessage2(method, maxRounds) {
|
|
|
83653
83652
|
return `Multi-round-trip request '${method}' still required input after ${maxRounds} rounds (inputRequired.maxRounds)`;
|
|
83654
83653
|
}
|
|
83655
83654
|
function sleep2(ms3, signal) {
|
|
83656
|
-
return new Promise((
|
|
83655
|
+
return new Promise((resolve12, reject) => {
|
|
83657
83656
|
if (signal?.aborted) {
|
|
83658
83657
|
reject(signal.reason instanceof SdkError2 ? signal.reason : new SdkError2(SdkErrorCode2.RequestTimeout, String(signal.reason)));
|
|
83659
83658
|
return;
|
|
83660
83659
|
}
|
|
83661
83660
|
const timer = setTimeout(() => {
|
|
83662
83661
|
signal?.removeEventListener("abort", onAbort);
|
|
83663
|
-
|
|
83662
|
+
resolve12();
|
|
83664
83663
|
}, ms3);
|
|
83665
83664
|
const onAbort = () => {
|
|
83666
83665
|
clearTimeout(timer);
|
|
@@ -84454,7 +84453,7 @@ var Protocol2 = class {
|
|
|
84454
84453
|
const flowStartedAt = Date.now();
|
|
84455
84454
|
let onAbort;
|
|
84456
84455
|
let cleanupMessageId;
|
|
84457
|
-
return new Promise((
|
|
84456
|
+
return new Promise((resolve12, reject) => {
|
|
84458
84457
|
const earlyReject = (error51) => {
|
|
84459
84458
|
reject(error51);
|
|
84460
84459
|
};
|
|
@@ -84523,7 +84522,7 @@ var Protocol2 = class {
|
|
|
84523
84522
|
}
|
|
84524
84523
|
if (decoded.kind === "invalid") return reject(decoded.error);
|
|
84525
84524
|
if (decoded.kind === "input_required") {
|
|
84526
|
-
if (options?.allowInputRequired === true) return
|
|
84525
|
+
if (options?.allowInputRequired === true) return resolve12(manualInputRequiredValue2(decoded));
|
|
84527
84526
|
const flow = {
|
|
84528
84527
|
codec: codec2,
|
|
84529
84528
|
request,
|
|
@@ -84535,11 +84534,11 @@ var Protocol2 = class {
|
|
|
84535
84534
|
params
|
|
84536
84535
|
}, resultSchema, legOptions)
|
|
84537
84536
|
};
|
|
84538
|
-
return
|
|
84537
|
+
return resolve12(this._resolveNonCompleteResult(decoded, flow));
|
|
84539
84538
|
}
|
|
84540
84539
|
const result = decoded.result;
|
|
84541
84540
|
validateStandardSchema2(resultSchema, result).then((parseResult) => {
|
|
84542
|
-
if (parseResult.success)
|
|
84541
|
+
if (parseResult.success) resolve12(parseResult.data);
|
|
84543
84542
|
else reject(new SdkError2(SdkErrorCode2.InvalidResult, `Invalid result for ${request.method}: ${parseResult.error}`));
|
|
84544
84543
|
}, reject);
|
|
84545
84544
|
});
|
|
@@ -87451,7 +87450,7 @@ var require_compile2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
|
|
|
87451
87450
|
ref = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, ref);
|
|
87452
87451
|
const schOrFunc = root3.refs[ref];
|
|
87453
87452
|
if (schOrFunc) return schOrFunc;
|
|
87454
|
-
let _sch =
|
|
87453
|
+
let _sch = resolve12.call(this, root3, ref);
|
|
87455
87454
|
if (_sch === void 0) {
|
|
87456
87455
|
const schema = (_a4 = root3.localRefs) === null || _a4 === void 0 ? void 0 : _a4[ref];
|
|
87457
87456
|
const { schemaId } = this.opts;
|
|
@@ -87477,7 +87476,7 @@ var require_compile2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
|
|
|
87477
87476
|
function sameSchemaEnv(s12, s22) {
|
|
87478
87477
|
return s12.schema === s22.schema && s12.root === s22.root && s12.baseId === s22.baseId;
|
|
87479
87478
|
}
|
|
87480
|
-
function
|
|
87479
|
+
function resolve12(root3, ref) {
|
|
87481
87480
|
let sch;
|
|
87482
87481
|
while (typeof (sch = this.refs[ref]) == "string") ref = sch;
|
|
87483
87482
|
return sch || this.schemas[ref] || resolveSchema.call(this, root3, ref);
|
|
@@ -87927,7 +87926,7 @@ var require_fast_uri2 = /* @__PURE__ */ __commonJSMin2(((exports, module) => {
|
|
|
87927
87926
|
else if (typeof uri === "object") uri = parse7(serialize(uri, options), options);
|
|
87928
87927
|
return uri;
|
|
87929
87928
|
}
|
|
87930
|
-
function
|
|
87929
|
+
function resolve12(baseURI, relativeURI, options) {
|
|
87931
87930
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
87932
87931
|
const resolved = resolveComponent(parse7(baseURI, schemelessOptions), parse7(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
87933
87932
|
schemelessOptions.skipEscape = true;
|
|
@@ -88101,7 +88100,7 @@ var require_fast_uri2 = /* @__PURE__ */ __commonJSMin2(((exports, module) => {
|
|
|
88101
88100
|
const fastUri = {
|
|
88102
88101
|
SCHEMES,
|
|
88103
88102
|
normalize: normalize2,
|
|
88104
|
-
resolve:
|
|
88103
|
+
resolve: resolve12,
|
|
88105
88104
|
resolveComponent,
|
|
88106
88105
|
equal,
|
|
88107
88106
|
serialize,
|
|
@@ -93534,7 +93533,7 @@ var StdioServerTransport = class {
|
|
|
93534
93533
|
}
|
|
93535
93534
|
send(message) {
|
|
93536
93535
|
if (this._closed) return Promise.reject(/* @__PURE__ */ new Error("StdioServerTransport is closed"));
|
|
93537
|
-
return new Promise((
|
|
93536
|
+
return new Promise((resolve12, reject) => {
|
|
93538
93537
|
const json2 = serializeMessage2(message);
|
|
93539
93538
|
let settled = false;
|
|
93540
93539
|
const onError = (error51) => {
|
|
@@ -93549,14 +93548,14 @@ var StdioServerTransport = class {
|
|
|
93549
93548
|
settled = true;
|
|
93550
93549
|
this._stdout.off("error", onError);
|
|
93551
93550
|
this._stdout.off("drain", onDrain2);
|
|
93552
|
-
|
|
93551
|
+
resolve12();
|
|
93553
93552
|
};
|
|
93554
93553
|
this._stdout.once("error", onError);
|
|
93555
93554
|
if (this._stdout.write(json2)) {
|
|
93556
93555
|
if (settled) return;
|
|
93557
93556
|
settled = true;
|
|
93558
93557
|
this._stdout.off("error", onError);
|
|
93559
|
-
|
|
93558
|
+
resolve12();
|
|
93560
93559
|
} else if (!settled) this._stdout.once("drain", onDrain2);
|
|
93561
93560
|
});
|
|
93562
93561
|
}
|
|
@@ -93610,14 +93609,14 @@ var StdioConnectionChannel = class {
|
|
|
93610
93609
|
*/
|
|
93611
93610
|
async whenRequestsAnswered(timeoutMs) {
|
|
93612
93611
|
if (this._closed || this._pendingRequests.size === 0) return true;
|
|
93613
|
-
return await new Promise((
|
|
93612
|
+
return await new Promise((resolve12) => {
|
|
93614
93613
|
const waiter = () => {
|
|
93615
93614
|
clearTimeout(timer);
|
|
93616
|
-
|
|
93615
|
+
resolve12(true);
|
|
93617
93616
|
};
|
|
93618
93617
|
const timer = setTimeout(() => {
|
|
93619
93618
|
this._drainWaiters = this._drainWaiters.filter((pending) => pending !== waiter);
|
|
93620
|
-
|
|
93619
|
+
resolve12(false);
|
|
93621
93620
|
}, timeoutMs);
|
|
93622
93621
|
this._drainWaiters.push(waiter);
|
|
93623
93622
|
});
|
|
@@ -94530,7 +94529,7 @@ var BrokerDesk = class {
|
|
|
94530
94529
|
try {
|
|
94531
94530
|
answer = await Promise.race([
|
|
94532
94531
|
asked.answer,
|
|
94533
|
-
new Promise((
|
|
94532
|
+
new Promise((resolve12) => call2.signal.aborted ? resolve12(null) : call2.signal.addEventListener("abort", () => resolve12(null), { once: true }))
|
|
94534
94533
|
]);
|
|
94535
94534
|
} finally {
|
|
94536
94535
|
call2.signal.removeEventListener("abort", leave);
|
|
@@ -96454,6 +96453,11 @@ var ExternalApps = class {
|
|
|
96454
96453
|
}
|
|
96455
96454
|
return out;
|
|
96456
96455
|
}
|
|
96456
|
+
sessionBoundaryOpen = () => false;
|
|
96457
|
+
/** Host-owned policy; a request cannot claim an auto preset in its arguments. */
|
|
96458
|
+
setSessionBoundaryPolicy(policy) {
|
|
96459
|
+
this.sessionBoundaryOpen = policy;
|
|
96460
|
+
}
|
|
96457
96461
|
/**
|
|
96458
96462
|
* An app's tool list — if the app is down, **this is where it starts** (the first time it is
|
|
96459
96463
|
* needed).
|
|
@@ -96462,9 +96466,9 @@ var ExternalApps = class {
|
|
|
96462
96466
|
* read is the place it is enforced: this list becomes the source of truth both for what attaches to
|
|
96463
96467
|
* a session (A-5) and for the tools a screen can call.
|
|
96464
96468
|
*/
|
|
96465
|
-
async tools(ref, audience) {
|
|
96469
|
+
async tools(ref, audience, sessionId) {
|
|
96466
96470
|
const e = this.require(ref);
|
|
96467
|
-
const all = await this.use(e, async () => e.life.tools ?? []);
|
|
96471
|
+
const all = await this.use(e, async () => e.life.tools ?? [], !!sessionId && this.sessionBoundaryOpen(sessionId));
|
|
96468
96472
|
return all.filter((t) => !audience || t.visibility.includes(audience)).map((t) => t.tool);
|
|
96469
96473
|
}
|
|
96470
96474
|
/**
|
|
@@ -96596,7 +96600,8 @@ var ExternalApps = class {
|
|
|
96596
96600
|
return { runId, status, result, error: error51, durationMs };
|
|
96597
96601
|
};
|
|
96598
96602
|
if (!e.manifest) return done("rejected", null, `This app's manifest is invalid: ${e.error}`);
|
|
96599
|
-
|
|
96603
|
+
const open5 = caller.kind === "session" && this.sessionBoundaryOpen(caller.sessionId);
|
|
96604
|
+
if (!e.scope.trusted && !open5) return done("rejected", null, "This app's project is not trusted, so Centralu does not call its apps");
|
|
96600
96605
|
const held3 = this.held(e);
|
|
96601
96606
|
if (held3) return done("rejected", null, held3);
|
|
96602
96607
|
if (e.life.gaveUp) return done("rejected", null, `This app stopped after failing ${this.timing.maxFailures} times in a row`);
|
|
@@ -96644,7 +96649,7 @@ var ExternalApps = class {
|
|
|
96644
96649
|
abort.abort();
|
|
96645
96650
|
for (const sig of upstream) sig.removeEventListener("abort", onUp);
|
|
96646
96651
|
}
|
|
96647
|
-
});
|
|
96652
|
+
}, open5);
|
|
96648
96653
|
} catch (err) {
|
|
96649
96654
|
return done("error", null, err.message);
|
|
96650
96655
|
}
|
|
@@ -97248,11 +97253,11 @@ var ExternalApps = class {
|
|
|
97248
97253
|
}
|
|
97249
97254
|
// ── Lifecycle ──────────────────────────────────────────────────────────────────────
|
|
97250
97255
|
/** An app is never idle while in use — the idle timer is set again once it is done */
|
|
97251
|
-
async use(e, fn2) {
|
|
97256
|
+
async use(e, fn2, boundaryOpen = false) {
|
|
97252
97257
|
e.life.inflight += 1;
|
|
97253
97258
|
this.clearIdle(e);
|
|
97254
97259
|
try {
|
|
97255
|
-
return await fn2(await this.ensureRunning(e));
|
|
97260
|
+
return await fn2(await this.ensureRunning(e, boundaryOpen));
|
|
97256
97261
|
} finally {
|
|
97257
97262
|
e.life.inflight -= 1;
|
|
97258
97263
|
if (e.life.inflight === 0) for (const w of e.life.idleWaiters.splice(0)) w();
|
|
@@ -97266,14 +97271,14 @@ var ExternalApps = class {
|
|
|
97266
97271
|
*/
|
|
97267
97272
|
drain(e, ms3) {
|
|
97268
97273
|
if (e.life.inflight === 0) return Promise.resolve(true);
|
|
97269
|
-
return new Promise((
|
|
97274
|
+
return new Promise((resolve12) => {
|
|
97270
97275
|
const waiter = () => {
|
|
97271
97276
|
clearTimeout(t);
|
|
97272
|
-
|
|
97277
|
+
resolve12(true);
|
|
97273
97278
|
};
|
|
97274
97279
|
const t = setTimeout(() => {
|
|
97275
97280
|
e.life.idleWaiters = e.life.idleWaiters.filter((w) => w !== waiter);
|
|
97276
|
-
|
|
97281
|
+
resolve12(false);
|
|
97277
97282
|
}, ms3);
|
|
97278
97283
|
t.unref();
|
|
97279
97284
|
e.life.idleWaiters.push(waiter);
|
|
@@ -97287,10 +97292,10 @@ var ExternalApps = class {
|
|
|
97287
97292
|
* encountered restoring sessions (see the `resuming` comment in `manager.ts`). Everyone waits on the
|
|
97288
97293
|
* same in-progress startup promise instead.
|
|
97289
97294
|
*/
|
|
97290
|
-
ensureRunning(e) {
|
|
97295
|
+
ensureRunning(e, boundaryOpen = false) {
|
|
97291
97296
|
const L2 = e.life;
|
|
97292
97297
|
if (!e.manifest) throw new AppUnavailableError(`The app cannot start \u2014 its manifest is invalid: ${e.error}`);
|
|
97293
|
-
if (!e.scope.trusted) {
|
|
97298
|
+
if (!e.scope.trusted && !boundaryOpen) {
|
|
97294
97299
|
throw new AppUnavailableError("This app's project is not trusted, so the app does not start \u2014 trust the project and it will");
|
|
97295
97300
|
}
|
|
97296
97301
|
const held3 = this.held(e);
|
|
@@ -98092,12 +98097,12 @@ function readBook(raw) {
|
|
|
98092
98097
|
return book;
|
|
98093
98098
|
}
|
|
98094
98099
|
function listenOn(port2, handler) {
|
|
98095
|
-
return new Promise((
|
|
98100
|
+
return new Promise((resolve12, reject) => {
|
|
98096
98101
|
const server2 = createServer2(handler);
|
|
98097
98102
|
server2.once("error", reject);
|
|
98098
98103
|
server2.listen(port2, "127.0.0.1", () => {
|
|
98099
98104
|
server2.removeListener("error", reject);
|
|
98100
|
-
|
|
98105
|
+
resolve12(server2);
|
|
98101
98106
|
});
|
|
98102
98107
|
});
|
|
98103
98108
|
}
|
|
@@ -98165,8 +98170,155 @@ var OriginPorts = class {
|
|
|
98165
98170
|
}
|
|
98166
98171
|
};
|
|
98167
98172
|
|
|
98173
|
+
// packages/agent-host/src/dev-services/worktree-recovery.ts
|
|
98174
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
98175
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
98176
|
+
import { chmod, lstat as lstat3, mkdir as mkdir2, readFile, readdir as readdir2, realpath as realpath3, rm as rm2, stat as stat3, writeFile as writeFile2 } from "node:fs/promises";
|
|
98177
|
+
import { join as join16, resolve as resolve2 } from "node:path";
|
|
98178
|
+
var WORKTREE_RECOVERY_MAX_BYTES = 256 * 1024 * 1024;
|
|
98179
|
+
var RECOVERY_TIMEOUT = 12e4;
|
|
98180
|
+
function worktreeRecoveryDir(sessionId) {
|
|
98181
|
+
if (!isSessionId(sessionId)) throw new Error("Invalid session id for worktree recovery");
|
|
98182
|
+
return join16(dataRoot(), "worktree-recovery", sessionId);
|
|
98183
|
+
}
|
|
98184
|
+
async function clearWorktreeRecovery(sessionId) {
|
|
98185
|
+
await rm2(worktreeRecoveryDir(sessionId), { recursive: true, force: true });
|
|
98186
|
+
}
|
|
98187
|
+
async function saveWorktreeRecovery(sessionId, cwd, trunk, branch) {
|
|
98188
|
+
const plans = [];
|
|
98189
|
+
let bytes2 = 0;
|
|
98190
|
+
const account = (size) => {
|
|
98191
|
+
bytes2 += size;
|
|
98192
|
+
if (bytes2 > WORKTREE_RECOVERY_MAX_BYTES) {
|
|
98193
|
+
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.`);
|
|
98194
|
+
}
|
|
98195
|
+
};
|
|
98196
|
+
const prepare = async (repo, path2) => {
|
|
98197
|
+
const tip = (await runGit(repo, ["rev-parse", "--verify", "HEAD"])).trim();
|
|
98198
|
+
const refs = ["HEAD"];
|
|
98199
|
+
if (!path2 && branch) {
|
|
98200
|
+
const ref = `refs/heads/${branch}`;
|
|
98201
|
+
if (await runGit(repo, ["rev-parse", "--verify", ref]).then(() => true).catch(() => false)) refs.push(ref);
|
|
98202
|
+
} else if (path2) {
|
|
98203
|
+
refs.push(...(await runGit(repo, ["for-each-ref", "--format=%(refname)", "refs/heads/"])).trim().split("\n").filter(Boolean));
|
|
98204
|
+
}
|
|
98205
|
+
const baselineOid = !path2 ? await runGit(repo, ["rev-parse", "--verify", `${trunk}^{commit}`]).then((s) => s.trim()).catch(() => null) : null;
|
|
98206
|
+
const exclude = path2 ? ["--not", "--remotes"] : baselineOid ? ["--not", baselineOid] : [];
|
|
98207
|
+
const commits = Number((await runGit(repo, ["rev-list", "--count", ...refs, ...exclude])).trim());
|
|
98208
|
+
plans.push({ repo, path: path2, tip, refs, commits });
|
|
98209
|
+
const stagedNames = new Set((await runGit(repo, ["diff", "--cached", "--name-only", "-z", "--no-renames", tip], { timeout: RECOVERY_TIMEOUT })).split("\0"));
|
|
98210
|
+
const modules = [];
|
|
98211
|
+
const stagedObjects = [];
|
|
98212
|
+
for (const row of (await runGit(repo, ["ls-files", "--stage", "-z"])).split("\0")) {
|
|
98213
|
+
if (!row) continue;
|
|
98214
|
+
const tab = row.indexOf(" ");
|
|
98215
|
+
const rel = row.slice(tab + 1);
|
|
98216
|
+
if (row.startsWith("160000 ")) modules.push(rel);
|
|
98217
|
+
else if (stagedNames.has(rel)) stagedObjects.push(row.split(" ")[1]);
|
|
98218
|
+
}
|
|
98219
|
+
if (stagedObjects.length) {
|
|
98220
|
+
const sizes = await runGit(repo, ["cat-file", "--batch-check=%(objectsize)"], { input: stagedObjects.join("\n") + "\n" });
|
|
98221
|
+
for (const size of sizes.trim().split("\n")) {
|
|
98222
|
+
if (!/^\d+$/.test(size)) throw new Error("Could not measure staged recovery data");
|
|
98223
|
+
account(Number(size));
|
|
98224
|
+
}
|
|
98225
|
+
}
|
|
98226
|
+
const files = new Set((await runGit(repo, ["ls-files", "--cached", "--others", "--exclude-standard", "-z"])).split("\0").filter(Boolean));
|
|
98227
|
+
for (const rel of files) {
|
|
98228
|
+
const file2 = await lstat3(join16(repo, rel)).catch((e) => {
|
|
98229
|
+
if (e.code !== "ENOENT") throw e;
|
|
98230
|
+
return null;
|
|
98231
|
+
});
|
|
98232
|
+
if (file2?.isFile() || file2?.isSymbolicLink()) account(file2.size);
|
|
98233
|
+
else if (file2?.isDirectory() && existsSync13(join16(repo, rel, ".git"))) modules.push(rel);
|
|
98234
|
+
}
|
|
98235
|
+
for (const rel of new Set(modules)) {
|
|
98236
|
+
await assertExistingPath(repo, rel).catch((e) => {
|
|
98237
|
+
if (existsSync13(join16(repo, rel))) throw e;
|
|
98238
|
+
});
|
|
98239
|
+
if (existsSync13(join16(repo, rel, ".git"))) await prepare(join16(repo, rel), path2 ? `${path2}/${rel}` : rel);
|
|
98240
|
+
}
|
|
98241
|
+
};
|
|
98242
|
+
await prepare(cwd, "");
|
|
98243
|
+
const snapshotId = randomUUID6();
|
|
98244
|
+
const dir = join16(worktreeRecoveryDir(sessionId), snapshotId);
|
|
98245
|
+
await mkdir2(dir, { recursive: true, mode: 448 });
|
|
98246
|
+
const snapshot2 = { snapshot: snapshotId, tip: plans[0].tip, changedFiles: 0, commits: 0, repositories: [] };
|
|
98247
|
+
const patchFile = async (repo, tip, file2, env) => {
|
|
98248
|
+
await runGit(repo, ["diff", "--cached", "--binary", "--full-index", "--no-renames", `--output=${file2}`, tip], { env, timeout: RECOVERY_TIMEOUT });
|
|
98249
|
+
await chmod(file2, 384);
|
|
98250
|
+
if ((await stat3(file2)).size > 0) return true;
|
|
98251
|
+
await rm2(file2);
|
|
98252
|
+
return false;
|
|
98253
|
+
};
|
|
98254
|
+
try {
|
|
98255
|
+
for (const [n, plan] of plans.entries()) {
|
|
98256
|
+
const { repo, path: path2, tip, refs, commits } = plan;
|
|
98257
|
+
const entry = { path: path2, tip, refs, patch: false, staged: false, bundle: commits > 0 };
|
|
98258
|
+
snapshot2.repositories.push(entry);
|
|
98259
|
+
snapshot2.commits += commits;
|
|
98260
|
+
if (entry.bundle) await runGit(repo, ["bundle", "create", join16(dir, `${n}.bundle`), ...refs], { timeout: RECOVERY_TIMEOUT });
|
|
98261
|
+
entry.staged = await patchFile(repo, tip, join16(dir, `${n}.staged.patch`));
|
|
98262
|
+
const stagedNames = await runGit(repo, ["diff", "--cached", "--name-only", "-z", "--no-renames", tip], { timeout: RECOVERY_TIMEOUT });
|
|
98263
|
+
const env = { GIT_INDEX_FILE: join16(dir, `${n}.index`) };
|
|
98264
|
+
try {
|
|
98265
|
+
await runGit(repo, ["read-tree", tip], { env });
|
|
98266
|
+
await runGit(repo, ["add", "-A", "--", "."], { env, timeout: RECOVERY_TIMEOUT });
|
|
98267
|
+
const names = await runGit(repo, ["diff", "--cached", "--name-only", "-z", "--no-renames", tip], { env, timeout: RECOVERY_TIMEOUT });
|
|
98268
|
+
snapshot2.changedFiles += new Set([...names.split("\0"), ...stagedNames.split("\0")].filter(Boolean)).size;
|
|
98269
|
+
entry.patch = await patchFile(repo, tip, join16(dir, `${n}.patch`), env);
|
|
98270
|
+
} finally {
|
|
98271
|
+
await rm2(env.GIT_INDEX_FILE, { force: true });
|
|
98272
|
+
}
|
|
98273
|
+
}
|
|
98274
|
+
await writeFile2(join16(dir, "snapshot.json"), JSON.stringify(snapshot2), { mode: 384 });
|
|
98275
|
+
return { snapshot: snapshotId, tip: snapshot2.tip, changedFiles: snapshot2.changedFiles, commits: snapshot2.commits };
|
|
98276
|
+
} catch (e) {
|
|
98277
|
+
await rm2(dir, { recursive: true, force: true });
|
|
98278
|
+
throw e;
|
|
98279
|
+
}
|
|
98280
|
+
}
|
|
98281
|
+
async function worktreeRecoveryBytes(sessionId) {
|
|
98282
|
+
const dir = worktreeRecoveryDir(sessionId);
|
|
98283
|
+
if (!existsSync13(dir)) return 0;
|
|
98284
|
+
const sizes = await Promise.all((await readdir2(dir, { recursive: true })).map(async (name) => {
|
|
98285
|
+
const s = await stat3(join16(dir, name));
|
|
98286
|
+
return s.isFile() ? s.size : 0;
|
|
98287
|
+
}));
|
|
98288
|
+
return sizes.reduce((a, b2) => a + b2, 0);
|
|
98289
|
+
}
|
|
98290
|
+
async function restoreWorktreeRecovery(sessionId, repo, path2, branch, recovery, trust) {
|
|
98291
|
+
if (!isSessionId(recovery.snapshot)) throw new Error("Invalid recovery snapshot");
|
|
98292
|
+
const dir = join16(worktreeRecoveryDir(sessionId), recovery.snapshot);
|
|
98293
|
+
const snapshot2 = JSON.parse(await readFile(join16(dir, "snapshot.json"), "utf8"));
|
|
98294
|
+
const root3 = snapshot2.repositories[0];
|
|
98295
|
+
if (!existsSync13(path2)) {
|
|
98296
|
+
if (await runGit(repo, ["show-ref", "--verify", `refs/heads/${branch}`]).then(() => true).catch(() => false)) {
|
|
98297
|
+
branch = `recovered/${sessionId}`;
|
|
98298
|
+
}
|
|
98299
|
+
if (root3.bundle) await runGit(repo, ["fetch", join16(dir, "0.bundle"), ...root3.refs ?? ["HEAD"]], { allowRepoPrograms: trust.trusted === true });
|
|
98300
|
+
await runGit(repo, ["worktree", "add", "-b", branch, path2, recovery.tip], { allowRepoPrograms: trust.trusted === true });
|
|
98301
|
+
if (root3.patch || root3.staged) {
|
|
98302
|
+
try {
|
|
98303
|
+
if (root3.staged) await runGit(path2, ["apply", "--cached", "--binary", join16(dir, "0.staged.patch")]);
|
|
98304
|
+
if (root3.patch) await runGit(path2, ["apply", "--binary", join16(dir, "0.patch")]);
|
|
98305
|
+
} catch {
|
|
98306
|
+
return { branch, notice: `Restored branch ${branch}. 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.` };
|
|
98307
|
+
}
|
|
98308
|
+
}
|
|
98309
|
+
} else {
|
|
98310
|
+
const common = async (cwd) => realpath3(resolve2(cwd, (await runGit(cwd, ["rev-parse", "--git-common-dir"])).trim()));
|
|
98311
|
+
if (await common(path2) !== await common(repo)) throw new Error(`The worktree path belongs to another repository. Recovery files remain at ${dir}`);
|
|
98312
|
+
branch = (await runGit(path2, ["symbolic-ref", "--quiet", "--short", "HEAD"])).trim();
|
|
98313
|
+
}
|
|
98314
|
+
return {
|
|
98315
|
+
branch,
|
|
98316
|
+
notice: `Restored branch ${branch}. 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.`
|
|
98317
|
+
};
|
|
98318
|
+
}
|
|
98319
|
+
|
|
98168
98320
|
// packages/agent-host/src/sessions/manager.ts
|
|
98169
|
-
import { randomUUID as
|
|
98321
|
+
import { randomUUID as randomUUID10 } from "node:crypto";
|
|
98170
98322
|
|
|
98171
98323
|
// packages/agent-host/src/sessions/tool-cost.ts
|
|
98172
98324
|
import { createHash as createHash8 } from "node:crypto";
|
|
@@ -98484,8 +98636,8 @@ var READER_TOOLS = [
|
|
|
98484
98636
|
var DELEGATE_TOOLS = [
|
|
98485
98637
|
{
|
|
98486
98638
|
name: "ask_project",
|
|
98487
|
-
description: "
|
|
98488
|
-
schema: external_exports.object({ project: external_exports.string(), task: external_exports.string().optional() })
|
|
98639
|
+
description: "Asks another project's existing session; returns its answer. For another project's apps, use find_apps.",
|
|
98640
|
+
schema: external_exports.object({ project: external_exports.string(), session: external_exports.string().optional(), task: external_exports.string().optional() })
|
|
98489
98641
|
}
|
|
98490
98642
|
];
|
|
98491
98643
|
var DELEGATE_BUDGET_CHARS = 400;
|
|
@@ -98609,7 +98761,7 @@ function askProjectText(r) {
|
|
|
98609
98761
|
const where = `the session "${r.sessionName}" [${r.sessionId}] in ${r.project}`;
|
|
98610
98762
|
if (r.state === "working") {
|
|
98611
98763
|
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.`
|
|
98764
|
+
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
98765
|
};
|
|
98614
98766
|
}
|
|
98615
98767
|
const lines = [`${r.project} answered (from ${where}), as JSON:`, trustedJsonText(r.answer || "(no final text)")];
|
|
@@ -98629,7 +98781,7 @@ function agentRunText(r) {
|
|
|
98629
98781
|
return { text: `Stopped ${who}.` };
|
|
98630
98782
|
case "deleted":
|
|
98631
98783
|
return {
|
|
98632
|
-
text: `
|
|
98784
|
+
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
98785
|
};
|
|
98634
98786
|
case "at_rest":
|
|
98635
98787
|
return {
|
|
@@ -98665,7 +98817,8 @@ async function runOrchestratorTool(tools, name, args, caller = { sessionId: null
|
|
|
98665
98817
|
const project2 = typeof args.project === "string" ? args.project.trim() : "";
|
|
98666
98818
|
if (!project2) return { text: "Give a project \u2014 the name of another Centralu project.", isError: true };
|
|
98667
98819
|
const task = typeof args.task === "string" && args.task.trim() ? args.task : void 0;
|
|
98668
|
-
|
|
98820
|
+
const session2 = typeof args.session === "string" && args.session.trim() ? args.session.trim() : void 0;
|
|
98821
|
+
return askProjectText(await tools.askProject({ project: project2, ...session2 ? { session: session2 } : {}, ...task ? { task } : {} }, signal));
|
|
98669
98822
|
}
|
|
98670
98823
|
if (name === "start_agent" || name === "agent_session") {
|
|
98671
98824
|
if (caller.profile !== "reader") return { text: `Not a tool of this session: ${name}`, isError: true };
|
|
@@ -99077,7 +99230,7 @@ function shortHash(value) {
|
|
|
99077
99230
|
|
|
99078
99231
|
// packages/agent-host/src/sessions/orchestrator-home.ts
|
|
99079
99232
|
import { mkdirSync as mkdirSync7 } from "node:fs";
|
|
99080
|
-
import { join as
|
|
99233
|
+
import { join as join17 } from "node:path";
|
|
99081
99234
|
var preamble = true;
|
|
99082
99235
|
function setOrchestratorPreamble(on2) {
|
|
99083
99236
|
preamble = on2;
|
|
@@ -99093,7 +99246,7 @@ function orchestratorRole() {
|
|
|
99093
99246
|
${ORCHESTRATOR_ROLE}` : ORCHESTRATOR_ROLE;
|
|
99094
99247
|
}
|
|
99095
99248
|
function orchestratorHome() {
|
|
99096
|
-
const dir =
|
|
99249
|
+
const dir = join17(dataRoot(), "orchestrator");
|
|
99097
99250
|
mkdirSync7(dir, { recursive: true });
|
|
99098
99251
|
return dir;
|
|
99099
99252
|
}
|
|
@@ -99641,7 +99794,7 @@ var SessionAppsHub = class {
|
|
|
99641
99794
|
* keep answering "yes" even after trust is revoked.
|
|
99642
99795
|
*/
|
|
99643
99796
|
refsFor(session2) {
|
|
99644
|
-
const usable = this.rt.list().filter((a) => !UNUSABLE.has(a.status));
|
|
99797
|
+
const usable = this.rt.list().filter((a) => !UNUSABLE.has(a.status) || a.status === "untrusted" && !!this.opts.boundaryOpen?.(session2.id));
|
|
99645
99798
|
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
99799
|
const taken = new Set(hits.map((h) => h.server));
|
|
99647
99800
|
for (const x of this.opts.onDemand?.attached(session2.id) ?? []) {
|
|
@@ -99668,8 +99821,8 @@ var SessionAppsHub = class {
|
|
|
99668
99821
|
const own = session2?.id ? this.opts.onDemand?.attached(session2.id).find((x) => sameRef(x.ref, ref)) : void 0;
|
|
99669
99822
|
const onDemand = !!session2 && !!own && this.opts.onDemand.allowed(session2, app2);
|
|
99670
99823
|
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 };
|
|
99824
|
+
if (app2.status === "untrusted" && !this.opts.boundaryOpen?.(session2.id)) return { reachable: false, reason: "untrusted" };
|
|
99825
|
+
if (UNUSABLE.has(app2.status) && !(app2.status === "untrusted" && this.opts.boundaryOpen?.(session2.id))) return { reachable: false, reason: "app-unusable", status: app2.status };
|
|
99673
99826
|
const server2 = givesApp(session2, app2) ? appMcpServerName(app2.appId) : own.server;
|
|
99674
99827
|
const attached = live?.appAttachment?.(server2) ?? "attached";
|
|
99675
99828
|
if (attached === "restart") return { reachable: false, reason: "restart" };
|
|
@@ -99751,7 +99904,7 @@ var Attachment2 = class {
|
|
|
99751
99904
|
let timer;
|
|
99752
99905
|
try {
|
|
99753
99906
|
const listed = await Promise.race([
|
|
99754
|
-
this.hub.rt.tools(hit.ref, "model"),
|
|
99907
|
+
this.hub.rt.tools(hit.ref, "model", this.session.id),
|
|
99755
99908
|
new Promise((_, reject) => {
|
|
99756
99909
|
timer = setTimeout(() => reject(new Error(`did not list its tools within ${Math.round(waitMs / 1e3)}s`)), waitMs);
|
|
99757
99910
|
})
|
|
@@ -99796,7 +99949,7 @@ var Attachment2 = class {
|
|
|
99796
99949
|
let timer;
|
|
99797
99950
|
const first = await Promise.race([
|
|
99798
99951
|
pending.then((o) => ({ o })),
|
|
99799
|
-
new Promise((
|
|
99952
|
+
new Promise((resolve12) => timer = setTimeout(() => resolve12(null), opts.waitMs))
|
|
99800
99953
|
]);
|
|
99801
99954
|
clearTimeout(timer);
|
|
99802
99955
|
if (first) return toResult(first.o);
|
|
@@ -99902,15 +100055,15 @@ The call has not stopped and carries on. Do not call it again: pass this id as r
|
|
|
99902
100055
|
if (i !== -1) return Promise.resolve(this.noted.splice(i, 1)[0].callId);
|
|
99903
100056
|
if (this.closed) return Promise.resolve(null);
|
|
99904
100057
|
const waitMs = this.hub.opts.callJoinWaitMs ?? CALL_JOIN_WAIT_MS;
|
|
99905
|
-
return new Promise((
|
|
100058
|
+
return new Promise((resolve12) => {
|
|
99906
100059
|
const waiter = {
|
|
99907
100060
|
server: server2,
|
|
99908
100061
|
tool,
|
|
99909
100062
|
args: key,
|
|
99910
|
-
resolve:
|
|
100063
|
+
resolve: resolve12,
|
|
99911
100064
|
timer: setTimeout(() => {
|
|
99912
100065
|
this.waiters = this.waiters.filter((x) => x !== waiter);
|
|
99913
|
-
|
|
100066
|
+
resolve12(null);
|
|
99914
100067
|
}, waitMs)
|
|
99915
100068
|
};
|
|
99916
100069
|
waiter.timer.unref?.();
|
|
@@ -100021,12 +100174,12 @@ var AppAccess = class {
|
|
|
100021
100174
|
}
|
|
100022
100175
|
allowed(session2, app2) {
|
|
100023
100176
|
const from = session2.projectId;
|
|
100024
|
-
if (from === null || !this.projectTrusted(from)) return false;
|
|
100177
|
+
if (from === null || !this.projectTrusted(from) && !this.deps.boundaryOpen?.(session2.id)) return false;
|
|
100025
100178
|
if (app2.projectId === null) return true;
|
|
100026
100179
|
if (app2.projectId === from || app2.shared !== true) return false;
|
|
100027
100180
|
const r = this.records(session2.id).find((x) => x.appId === app2.appId && x.projectId === app2.projectId);
|
|
100028
100181
|
if (!r) return false;
|
|
100029
|
-
return r.consent === "once" || !!this.deps.store.getProjectConsent(from, app2.projectId, "apps");
|
|
100182
|
+
return !!this.deps.boundaryOpen?.(session2.id) || r.consent === "once" || !!this.deps.store.getProjectConsent(from, app2.projectId, "apps");
|
|
100030
100183
|
}
|
|
100031
100184
|
// ── The three tools ──
|
|
100032
100185
|
find(sessionId, query) {
|
|
@@ -100049,7 +100202,7 @@ var AppAccess = class {
|
|
|
100049
100202
|
const app2 = named.app;
|
|
100050
100203
|
const target = { projectId: app2.projectId, appId: app2.appId };
|
|
100051
100204
|
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 };
|
|
100205
|
+
if (has) return { ok: true, server: has.server, tools: await this.toolNames(target, sessionId), when: "now", already: true };
|
|
100053
100206
|
if (!this.candidates(session2).some((a) => a.appId === app2.appId && a.projectId === app2.projectId)) {
|
|
100054
100207
|
return { ok: false, error: this.whyNot(session2, app2) };
|
|
100055
100208
|
}
|
|
@@ -100062,7 +100215,7 @@ var AppAccess = class {
|
|
|
100062
100215
|
signal
|
|
100063
100216
|
);
|
|
100064
100217
|
if (!res.ok) return res;
|
|
100065
|
-
consent = this.deps.store.getProjectConsent(session2.projectId, app2.projectId, "apps") ? "always" : "once";
|
|
100218
|
+
consent = this.deps.boundaryOpen?.(sessionId) ? "auto" : this.deps.store.getProjectConsent(session2.projectId, app2.projectId, "apps") ? "always" : "once";
|
|
100066
100219
|
const again = this.gate(sessionId);
|
|
100067
100220
|
if (!again.ok) return again;
|
|
100068
100221
|
if (!this.candidates(again.session).some((a) => a.appId === app2.appId && a.projectId === app2.projectId)) {
|
|
@@ -100074,7 +100227,7 @@ var AppAccess = class {
|
|
|
100074
100227
|
const server2 = this.freeServer(app2.appId, taken);
|
|
100075
100228
|
this.save(sessionId, [...kept, { projectId: app2.projectId, appId: app2.appId, server: server2, consent, at: Date.now() }]);
|
|
100076
100229
|
const when = this.deps.attachedChanged(sessionId);
|
|
100077
|
-
return { ok: true, server: server2, tools: await this.toolNames(target), when };
|
|
100230
|
+
return { ok: true, server: server2, tools: await this.toolNames(target, sessionId), when };
|
|
100078
100231
|
}
|
|
100079
100232
|
detach(sessionId, ref) {
|
|
100080
100233
|
const mine = this.records(sessionId);
|
|
@@ -100118,7 +100271,7 @@ var AppAccess = class {
|
|
|
100118
100271
|
const session2 = this.deps.session(sessionId);
|
|
100119
100272
|
if (!session2) return { ok: false, error: "This session cannot attach apps." };
|
|
100120
100273
|
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)) {
|
|
100274
|
+
if (!this.projectTrusted(session2.projectId) && !this.deps.boundaryOpen?.(sessionId, "attach from an untrusted project")) {
|
|
100122
100275
|
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
100276
|
}
|
|
100124
100277
|
return { ok: true, session: session2 };
|
|
@@ -100126,7 +100279,7 @@ var AppAccess = class {
|
|
|
100126
100279
|
/** What this session could attach now: shared apps of other trusted projects, and user-folder apps, minus what it has */
|
|
100127
100280
|
candidates(session2) {
|
|
100128
100281
|
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)));
|
|
100282
|
+
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
100283
|
}
|
|
100131
100284
|
whyNot(session2, app2) {
|
|
100132
100285
|
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 +100335,13 @@ var AppAccess = class {
|
|
|
100182
100335
|
for (let n = 2; ; n++) if (!taken.includes(`${base2}-${n}`)) return `${base2}-${n}`;
|
|
100183
100336
|
}
|
|
100184
100337
|
/** 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) {
|
|
100338
|
+
async toolNames(ref, sessionId) {
|
|
100186
100339
|
const known = this.deps.hub.rt.knownTools(ref, "model");
|
|
100187
100340
|
if (known) return known.map((t) => t.name);
|
|
100188
100341
|
let timer;
|
|
100189
100342
|
try {
|
|
100190
100343
|
const listed = await Promise.race([
|
|
100191
|
-
this.deps.hub.rt.tools(ref, "model"),
|
|
100344
|
+
this.deps.hub.rt.tools(ref, "model", sessionId),
|
|
100192
100345
|
new Promise((_, reject) => timer = setTimeout(() => reject(new Error("timeout")), this.deps.toolListWaitMs ?? TOOL_LIST_WAIT_MS2))
|
|
100193
100346
|
]);
|
|
100194
100347
|
return listed.map((t) => t.name);
|
|
@@ -100222,7 +100375,7 @@ function parse3(raw) {
|
|
|
100222
100375
|
const v = JSON.parse(raw);
|
|
100223
100376
|
if (!Array.isArray(v)) return [];
|
|
100224
100377
|
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")
|
|
100378
|
+
(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
100379
|
);
|
|
100227
100380
|
} catch {
|
|
100228
100381
|
return [];
|
|
@@ -100250,7 +100403,7 @@ var AgentRunWait = class {
|
|
|
100250
100403
|
this.sessionName = sessionName;
|
|
100251
100404
|
this.onUsage = onUsage;
|
|
100252
100405
|
this.readAnswer = readAnswer2;
|
|
100253
|
-
this.done = new Promise((
|
|
100406
|
+
this.done = new Promise((resolve12, reject) => this.settle = { resolve: resolve12, reject });
|
|
100254
100407
|
this.done.catch(() => {
|
|
100255
100408
|
});
|
|
100256
100409
|
}
|
|
@@ -100465,7 +100618,7 @@ function windowOf(list, id2, label, since, writes, nameOf) {
|
|
|
100465
100618
|
|
|
100466
100619
|
// packages/agent-host/src/sessions/ask-project.ts
|
|
100467
100620
|
import { realpathSync as realpathSync3, statSync as statSync8 } from "node:fs";
|
|
100468
|
-
import { isAbsolute as isAbsolute4, relative as relative4, resolve as
|
|
100621
|
+
import { isAbsolute as isAbsolute4, relative as relative4, resolve as resolve3, sep as sep4 } from "node:path";
|
|
100469
100622
|
var ASK_WAIT_MS = 24e4;
|
|
100470
100623
|
var ANSWER_MAX_CHARS = 6e3;
|
|
100471
100624
|
var ANSWER_HEAD_CHARS = 4500;
|
|
@@ -100534,7 +100687,7 @@ function readableGrants(paths, projectRoot) {
|
|
|
100534
100687
|
return { granted, outside };
|
|
100535
100688
|
}
|
|
100536
100689
|
function underGrant(path2, grants, cwd) {
|
|
100537
|
-
const abs =
|
|
100690
|
+
const abs = resolve3(cwd, path2);
|
|
100538
100691
|
const r = real(abs) ?? abs;
|
|
100539
100692
|
for (const g5 of grants) {
|
|
100540
100693
|
if (r === g5 || r.startsWith(g5.endsWith(sep4) ? g5 : g5 + sep4)) return true;
|
|
@@ -100835,32 +100988,32 @@ function orchestratorMemory(reader, sessionId) {
|
|
|
100835
100988
|
|
|
100836
100989
|
// packages/agent-host/src/sessions/manager.ts
|
|
100837
100990
|
import { homedir as homedir12 } from "node:os";
|
|
100838
|
-
import { basename as basename5, isAbsolute as isAbsolute9, join as
|
|
100839
|
-
import { existsSync as
|
|
100991
|
+
import { basename as basename5, isAbsolute as isAbsolute9, join as join29, relative as relative8, resolve as resolve11, sep as sep11 } from "node:path";
|
|
100992
|
+
import { existsSync as existsSync19, rmSync as rmSync7, statSync as statSync12 } from "node:fs";
|
|
100840
100993
|
import { exec as exec6 } from "node:child_process";
|
|
100841
100994
|
|
|
100842
100995
|
// packages/agent-host/src/adapters/claude/index.ts
|
|
100843
100996
|
init_sdk();
|
|
100844
100997
|
import { homedir as homedir5 } from "node:os";
|
|
100845
|
-
import { randomUUID as
|
|
100998
|
+
import { randomUUID as randomUUID9 } from "node:crypto";
|
|
100846
100999
|
|
|
100847
101000
|
// packages/agent-host/src/adapters/input-receipts.ts
|
|
100848
|
-
import { randomUUID as
|
|
101001
|
+
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
100849
101002
|
var InputReceipts = class {
|
|
100850
101003
|
pending = /* @__PURE__ */ new Map();
|
|
100851
101004
|
ended = /* @__PURE__ */ new Set();
|
|
100852
101005
|
create(observer) {
|
|
100853
|
-
const id2 =
|
|
100854
|
-
let
|
|
101006
|
+
const id2 = randomUUID8();
|
|
101007
|
+
let resolve12;
|
|
100855
101008
|
let reject;
|
|
100856
101009
|
const done = new Promise((yes, no2) => {
|
|
100857
|
-
|
|
101010
|
+
resolve12 = yes;
|
|
100858
101011
|
reject = no2;
|
|
100859
101012
|
});
|
|
100860
101013
|
done.catch(() => {
|
|
100861
101014
|
});
|
|
100862
101015
|
const receipt = { id: id2, done, cancelled: false, completed: false };
|
|
100863
|
-
this.pending.set(id2, { receipt, resolve:
|
|
101016
|
+
this.pending.set(id2, { receipt, resolve: resolve12, reject, observer });
|
|
100864
101017
|
return receipt;
|
|
100865
101018
|
}
|
|
100866
101019
|
get size() {
|
|
@@ -100971,7 +101124,7 @@ async function installedCliVersion(name, packageName, deps = realDeps2()) {
|
|
|
100971
101124
|
// packages/agent-host/src/adapters/claude/exe-link.ts
|
|
100972
101125
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
100973
101126
|
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
|
|
101127
|
+
import { basename as basename4, dirname as dirname8, join as join22 } from "node:path";
|
|
100975
101128
|
var PLACEHOLDER_MAX_BYTES = 1e6;
|
|
100976
101129
|
var ClaudePlaceholderError = class extends Error {
|
|
100977
101130
|
name = "ClaudePlaceholderError";
|
|
@@ -101007,7 +101160,7 @@ function placeholderProblem(exe, size, fs2 = realExeFs) {
|
|
|
101007
101160
|
}
|
|
101008
101161
|
if (head.toString("latin1") === "MZ") return null;
|
|
101009
101162
|
const pkg = dirname8(dirname8(exe));
|
|
101010
|
-
const installer = basename4(dirname8(exe)).toLowerCase() === "bin" ?
|
|
101163
|
+
const installer = basename4(dirname8(exe)).toLowerCase() === "bin" ? join22(pkg, "install.cjs") : null;
|
|
101011
101164
|
let canRerun = false;
|
|
101012
101165
|
if (installer) {
|
|
101013
101166
|
try {
|
|
@@ -101022,14 +101175,14 @@ function placeholderProblem(exe, size, fs2 = realExeFs) {
|
|
|
101022
101175
|
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
101176
|
].join(" ");
|
|
101024
101177
|
}
|
|
101025
|
-
function claudeKey(exe,
|
|
101178
|
+
function claudeKey(exe, stat7, fs2 = realExeFs) {
|
|
101026
101179
|
let version2 = null;
|
|
101027
101180
|
try {
|
|
101028
|
-
const pkg = JSON.parse(fs2.read(
|
|
101181
|
+
const pkg = JSON.parse(fs2.read(join22(dirname8(dirname8(exe)), "package.json")));
|
|
101029
101182
|
if (pkg.name === "@anthropic-ai/claude-code" && typeof pkg.version === "string" && pkg.version) version2 = pkg.version;
|
|
101030
101183
|
} catch {
|
|
101031
101184
|
}
|
|
101032
|
-
const raw = version2 ? `${version2}-${
|
|
101185
|
+
const raw = version2 ? `${version2}-${stat7.size}` : `${stat7.size}-${Math.floor(stat7.mtimeMs)}`;
|
|
101033
101186
|
return raw.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
101034
101187
|
}
|
|
101035
101188
|
function windowsStart(platform = process.platform) {
|
|
@@ -101051,7 +101204,7 @@ var ClaudeLinks = class {
|
|
|
101051
101204
|
return windowsStart(this.opts.platform);
|
|
101052
101205
|
}
|
|
101053
101206
|
base() {
|
|
101054
|
-
return
|
|
101207
|
+
return join22(this.opts.root(), "tools", "claude");
|
|
101055
101208
|
}
|
|
101056
101209
|
/**
|
|
101057
101210
|
* What to start for the `claude.exe` found at `exe`. Off Windows, or for anything but an `.exe`
|
|
@@ -101062,26 +101215,26 @@ var ClaudeLinks = class {
|
|
|
101062
101215
|
prepare(exe) {
|
|
101063
101216
|
const direct = { path: exe, key: null };
|
|
101064
101217
|
if (!this.enabled || !/\.exe$/i.test(exe)) return direct;
|
|
101065
|
-
let
|
|
101218
|
+
let stat7;
|
|
101066
101219
|
try {
|
|
101067
|
-
|
|
101220
|
+
stat7 = this.fs.stat(exe);
|
|
101068
101221
|
} catch {
|
|
101069
101222
|
return direct;
|
|
101070
101223
|
}
|
|
101071
|
-
const problem2 = placeholderProblem(exe,
|
|
101224
|
+
const problem2 = placeholderProblem(exe, stat7.size, this.fs);
|
|
101072
101225
|
if (problem2) throw new ClaudePlaceholderError(problem2);
|
|
101073
|
-
const key = claudeKey(exe,
|
|
101074
|
-
const dir =
|
|
101075
|
-
const target =
|
|
101226
|
+
const key = claudeKey(exe, stat7, this.fs);
|
|
101227
|
+
const dir = join22(this.base(), key);
|
|
101228
|
+
const target = join22(dir, "claude.exe");
|
|
101076
101229
|
const ready = () => {
|
|
101077
101230
|
try {
|
|
101078
|
-
return this.fs.stat(target).size ===
|
|
101231
|
+
return this.fs.stat(target).size === stat7.size;
|
|
101079
101232
|
} catch {
|
|
101080
101233
|
return false;
|
|
101081
101234
|
}
|
|
101082
101235
|
};
|
|
101083
101236
|
if (ready()) return this.use(key, target);
|
|
101084
|
-
const tmp =
|
|
101237
|
+
const tmp = join22(dir, `claude-${randomBytes2(4).toString("hex")}.tmp`);
|
|
101085
101238
|
try {
|
|
101086
101239
|
this.fs.mkdir(dir);
|
|
101087
101240
|
try {
|
|
@@ -101141,7 +101294,7 @@ var ClaudeLinks = class {
|
|
|
101141
101294
|
for (const name of names) {
|
|
101142
101295
|
if (name === this.current || this.uses.has(name)) continue;
|
|
101143
101296
|
try {
|
|
101144
|
-
this.fs.removeDir(
|
|
101297
|
+
this.fs.removeDir(join22(this.base(), name));
|
|
101145
101298
|
} catch {
|
|
101146
101299
|
}
|
|
101147
101300
|
}
|
|
@@ -102595,7 +102748,14 @@ function alwaysAllowKey(detail, opts = {}) {
|
|
|
102595
102748
|
}
|
|
102596
102749
|
|
|
102597
102750
|
// packages/agent-host/src/adapters/claude/cross-session.ts
|
|
102598
|
-
function withoutCrossSessionMessaging(options) {
|
|
102751
|
+
function withoutCrossSessionMessaging(options, preset = "normal") {
|
|
102752
|
+
if (preset === "auto")
|
|
102753
|
+
return {
|
|
102754
|
+
...options,
|
|
102755
|
+
settings: options.settings ?? {},
|
|
102756
|
+
disallowedTools: options.disallowedTools ?? [],
|
|
102757
|
+
env: { ...process.env, ...options.env }
|
|
102758
|
+
};
|
|
102599
102759
|
const env = {
|
|
102600
102760
|
...process.env,
|
|
102601
102761
|
...options.env,
|
|
@@ -102695,7 +102855,7 @@ var ClaudeSession = class {
|
|
|
102695
102855
|
this.opts = opts;
|
|
102696
102856
|
this.emit = emit;
|
|
102697
102857
|
this.launch = launch2;
|
|
102698
|
-
this.ended = new Promise((
|
|
102858
|
+
this.ended = new Promise((resolve12) => this.markEnded = resolve12);
|
|
102699
102859
|
this.stream = new ClaudeStreamNormalizer(sessionId, () => ({
|
|
102700
102860
|
model: opts.model ?? null,
|
|
102701
102861
|
effort: opts.effort ?? null,
|
|
@@ -102946,8 +103106,8 @@ var ClaudeSession = class {
|
|
|
102946
103106
|
if (questions.length === 0) return { behavior: "allow", updatedInput: toolInput };
|
|
102947
103107
|
const requestId2 = `q-${++self2.reqCounter}`;
|
|
102948
103108
|
self2.emit({ type: "question_request", sessionId: self2.sessionId, requestId: requestId2, questions });
|
|
102949
|
-
return new Promise((
|
|
102950
|
-
self2.questions.set(requestId2, { resolve:
|
|
103109
|
+
return new Promise((resolve12) => {
|
|
103110
|
+
self2.questions.set(requestId2, { resolve: resolve12, input: toolInput });
|
|
102951
103111
|
self2.releaseOnCancel(requestId2, options?.signal);
|
|
102952
103112
|
});
|
|
102953
103113
|
}
|
|
@@ -102956,17 +103116,17 @@ var ClaudeSession = class {
|
|
|
102956
103116
|
if (key && self2.alwaysAllow.allows(key)) return { behavior: "allow", updatedInput: toolInput };
|
|
102957
103117
|
const requestId = `req-${++self2.reqCounter}`;
|
|
102958
103118
|
self2.emit({ type: "approval_request", sessionId: self2.sessionId, requestId, detail });
|
|
102959
|
-
return new Promise((
|
|
102960
|
-
self2.pending.set(requestId, { resolve:
|
|
103119
|
+
return new Promise((resolve12) => {
|
|
103120
|
+
self2.pending.set(requestId, { resolve: resolve12, input: toolInput });
|
|
102961
103121
|
self2.releaseOnCancel(requestId, options?.signal);
|
|
102962
103122
|
});
|
|
102963
103123
|
}
|
|
102964
|
-
})
|
|
103124
|
+
}, preset)
|
|
102965
103125
|
});
|
|
102966
103126
|
ClaudeAdapter.liveQueries.add(q2);
|
|
102967
103127
|
ClaudeAdapter.querySessions.set(q2, this.sessionId);
|
|
102968
103128
|
this.releaseLostCalls(q2);
|
|
102969
|
-
this.goalKnown = new Promise((
|
|
103129
|
+
this.goalKnown = new Promise((resolve12) => this.settleGoalKnown = resolve12);
|
|
102970
103130
|
q2.supportedCommands().then((list) => {
|
|
102971
103131
|
if (this.goalCommand === void 0 && Array.isArray(list)) this.goalCommand = list.some((c) => c?.name === "goal");
|
|
102972
103132
|
}).catch(() => {
|
|
@@ -103194,7 +103354,7 @@ var ClaudeSession = class {
|
|
|
103194
103354
|
send(text2) {
|
|
103195
103355
|
this.sendInput(text2);
|
|
103196
103356
|
}
|
|
103197
|
-
sendInput(text2, uuid3 =
|
|
103357
|
+
sendInput(text2, uuid3 = randomUUID9(), note) {
|
|
103198
103358
|
const goal = isGoalCommand(text2);
|
|
103199
103359
|
if (!this.sendGate && !(goal && this.goalCommand === void 0)) {
|
|
103200
103360
|
this.deliver(text2, uuid3, !!note);
|
|
@@ -103655,7 +103815,7 @@ var ClaudeAdapter = class _ClaudeAdapter {
|
|
|
103655
103815
|
sleep: true
|
|
103656
103816
|
};
|
|
103657
103817
|
launchSettingsFingerprint(settings) {
|
|
103658
|
-
return JSON.stringify({ v: 1, cwd: settings.cwd, permission: settings.permissionPreset, trusted: !!settings.projectTrusted, noSettings: !!settings.noSettingFiles });
|
|
103818
|
+
return JSON.stringify({ v: settings.permissionPreset === "auto" ? 2 : 1, cwd: settings.cwd, permission: settings.permissionPreset, trusted: !!settings.projectTrusted, noSettings: !!settings.noSettingFiles });
|
|
103659
103819
|
}
|
|
103660
103820
|
async detect() {
|
|
103661
103821
|
const path2 = whichTool("claude");
|
|
@@ -103742,7 +103902,7 @@ var ClaudeAdapter = class _ClaudeAdapter {
|
|
|
103742
103902
|
let timer;
|
|
103743
103903
|
await Promise.race([
|
|
103744
103904
|
Promise.allSettled([...this.launch.closing]),
|
|
103745
|
-
new Promise((
|
|
103905
|
+
new Promise((resolve12) => timer = setTimeout(resolve12, this.launch.exitGraceMs))
|
|
103746
103906
|
]);
|
|
103747
103907
|
clearTimeout(timer);
|
|
103748
103908
|
}
|
|
@@ -103752,7 +103912,7 @@ var ClaudeAdapter = class _ClaudeAdapter {
|
|
|
103752
103912
|
import { execFile as execFile5 } from "node:child_process";
|
|
103753
103913
|
|
|
103754
103914
|
// packages/agent-host/src/adapters/codex/bridge-path.ts
|
|
103755
|
-
import { existsSync as
|
|
103915
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
103756
103916
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
103757
103917
|
function bridgePath() {
|
|
103758
103918
|
const candidates = [
|
|
@@ -103761,23 +103921,23 @@ function bridgePath() {
|
|
|
103761
103921
|
new URL("./codex-orchestrator-bridge.mjs", import.meta.url)
|
|
103762
103922
|
// the bundled build-output layout
|
|
103763
103923
|
].map((u) => fileURLToPath3(u));
|
|
103764
|
-
const found = candidates.find((p) =>
|
|
103924
|
+
const found = candidates.find((p) => existsSync15(p));
|
|
103765
103925
|
if (!found) throw new Error(`orchestrator bridge not found: ${candidates.join(", ")}`);
|
|
103766
103926
|
return found;
|
|
103767
103927
|
}
|
|
103768
103928
|
|
|
103769
103929
|
// packages/agent-host/src/adapters/codex/index.ts
|
|
103770
|
-
import { existsSync as
|
|
103930
|
+
import { existsSync as existsSync17, realpathSync as realpathSync7 } from "node:fs";
|
|
103771
103931
|
import { homedir as homedir10 } from "node:os";
|
|
103772
|
-
import { dirname as dirname9, join as
|
|
103932
|
+
import { dirname as dirname9, join as join24, resolve as resolve9 } from "node:path";
|
|
103773
103933
|
import { promisify as promisify5 } from "node:util";
|
|
103774
103934
|
|
|
103775
103935
|
// packages/agent-host/src/adapters/codex/native.ts
|
|
103776
|
-
import { existsSync as
|
|
103936
|
+
import { existsSync as existsSync16, readFileSync as readFileSync11, realpathSync as realpathSync6 } from "node:fs";
|
|
103777
103937
|
var realDeps3 = () => ({
|
|
103778
103938
|
platform: HOST_PLATFORM,
|
|
103779
103939
|
arch: process.arch,
|
|
103780
|
-
exists:
|
|
103940
|
+
exists: existsSync16,
|
|
103781
103941
|
read: (p) => readFileSync11(p, "utf8"),
|
|
103782
103942
|
realpath: (p) => realpathSync6(p),
|
|
103783
103943
|
launch: (p) => launchFor(p),
|
|
@@ -103946,14 +104106,14 @@ var CodexClient = class {
|
|
|
103946
104106
|
if (this.closed) return Promise.reject(new Error("codex app-server has already exited"));
|
|
103947
104107
|
const id2 = `${this.idPrefix}${this.nextId++}`;
|
|
103948
104108
|
this.write({ id: id2, method, params });
|
|
103949
|
-
return new Promise((
|
|
104109
|
+
return new Promise((resolve12, reject) => {
|
|
103950
104110
|
const timer = setTimeout(() => {
|
|
103951
104111
|
if (this.pending.delete(id2)) reject(new Error(`No response for ${method}`));
|
|
103952
104112
|
}, timeoutMs);
|
|
103953
104113
|
this.pending.set(id2, {
|
|
103954
104114
|
resolve: (v) => {
|
|
103955
104115
|
clearTimeout(timer);
|
|
103956
|
-
|
|
104116
|
+
resolve12(v);
|
|
103957
104117
|
},
|
|
103958
104118
|
reject: (e) => {
|
|
103959
104119
|
clearTimeout(timer);
|
|
@@ -103977,12 +104137,12 @@ var CodexClient = class {
|
|
|
103977
104137
|
if (this.closed) return;
|
|
103978
104138
|
this.closed = true;
|
|
103979
104139
|
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(() =>
|
|
104140
|
+
const exited = await new Promise((resolve12) => {
|
|
104141
|
+
if (this.proc.exitCode !== null || this.proc.signalCode != null || this.finished) return resolve12(true);
|
|
104142
|
+
const t = setTimeout(() => resolve12(false), 2e3);
|
|
103983
104143
|
this.proc.once("exit", () => {
|
|
103984
104144
|
clearTimeout(t);
|
|
103985
|
-
|
|
104145
|
+
resolve12(true);
|
|
103986
104146
|
});
|
|
103987
104147
|
});
|
|
103988
104148
|
if (!exited) this.proc.kill("SIGKILL");
|
|
@@ -104004,18 +104164,18 @@ var CodexClient = class {
|
|
|
104004
104164
|
|
|
104005
104165
|
// packages/agent-host/src/adapters/codex/rollout.ts
|
|
104006
104166
|
import { createReadStream as createReadStream2 } from "node:fs";
|
|
104007
|
-
import { readdir as
|
|
104167
|
+
import { readdir as readdir5 } from "node:fs/promises";
|
|
104008
104168
|
import { createInterface as createInterface2 } from "node:readline";
|
|
104009
104169
|
import { homedir as homedir6 } from "node:os";
|
|
104010
|
-
import { join as
|
|
104170
|
+
import { join as join23 } from "node:path";
|
|
104011
104171
|
var MIN_SUMMARY_CHARS = 200;
|
|
104012
|
-
async function findRolloutPath(threadId, sessionsDir =
|
|
104172
|
+
async function findRolloutPath(threadId, sessionsDir = join23(homedir6(), ".codex", "sessions")) {
|
|
104013
104173
|
try {
|
|
104014
104174
|
const suffix = `-${threadId}.jsonl`;
|
|
104015
|
-
const names = await
|
|
104175
|
+
const names = await readdir5(sessionsDir, { recursive: true });
|
|
104016
104176
|
const hits = names.filter((n) => String(n).endsWith(suffix)).sort();
|
|
104017
104177
|
const hit = hits[hits.length - 1];
|
|
104018
|
-
return hit ?
|
|
104178
|
+
return hit ? join23(sessionsDir, String(hit)) : null;
|
|
104019
104179
|
} catch {
|
|
104020
104180
|
return null;
|
|
104021
104181
|
}
|
|
@@ -104216,7 +104376,7 @@ function userInputText(content) {
|
|
|
104216
104376
|
}
|
|
104217
104377
|
|
|
104218
104378
|
// packages/agent-host/src/adapters/codex/images.ts
|
|
104219
|
-
import { readFile as
|
|
104379
|
+
import { readFile as readFile3, stat as stat5 } from "node:fs/promises";
|
|
104220
104380
|
var IMAGE_MIMES2 = {
|
|
104221
104381
|
png: "image/png",
|
|
104222
104382
|
jpg: "image/jpeg",
|
|
@@ -104232,9 +104392,9 @@ async function imageEventFromDisk(sessionId, path2, maxBytes = IMAGE_MAX_BYTES)
|
|
|
104232
104392
|
const mime = IMAGE_MIMES2[ext2];
|
|
104233
104393
|
if (!mime) return fail4(`Cannot display this format (.${ext2})`);
|
|
104234
104394
|
try {
|
|
104235
|
-
const s = await
|
|
104395
|
+
const s = await stat5(path2);
|
|
104236
104396
|
if (s.size > maxBytes) return fail4(`Image is too large (${Math.round(s.size / 1048576)}MB)`);
|
|
104237
|
-
const buf = await
|
|
104397
|
+
const buf = await readFile3(path2);
|
|
104238
104398
|
return { type: "message_image", sessionId, mime, data: buf.toString("base64"), path: path2 };
|
|
104239
104399
|
} catch (err) {
|
|
104240
104400
|
return fail4(`Failed to read image: ${err.message}`);
|
|
@@ -105218,16 +105378,16 @@ var CodexChildTracker = class {
|
|
|
105218
105378
|
};
|
|
105219
105379
|
|
|
105220
105380
|
// packages/agent-host/src/adapters/codex/sandbox.ts
|
|
105221
|
-
import { realpath as
|
|
105222
|
-
import { resolve as
|
|
105381
|
+
import { realpath as realpath6 } from "node:fs/promises";
|
|
105382
|
+
import { resolve as resolve8 } from "node:path";
|
|
105223
105383
|
async function sandboxConfig(cwd, preset, readConfig) {
|
|
105224
105384
|
const roots = [];
|
|
105225
105385
|
try {
|
|
105226
105386
|
const stdout = await runGit(cwd, ["rev-parse", "--path-format=absolute", "--git-dir", "--git-common-dir"], { timeout: 5e3 });
|
|
105227
105387
|
const [gitDir, commonDir] = stdout.trim().split(/\r?\n/);
|
|
105228
105388
|
if (gitDir && commonDir) {
|
|
105229
|
-
const gitPath = await
|
|
105230
|
-
const commonPath = await
|
|
105389
|
+
const gitPath = await realpath6(resolve8(cwd, gitDir));
|
|
105390
|
+
const commonPath = await realpath6(resolve8(cwd, commonDir));
|
|
105231
105391
|
if (gitPath !== commonPath) roots.push(commonPath);
|
|
105232
105392
|
}
|
|
105233
105393
|
} catch {
|
|
@@ -105255,7 +105415,7 @@ function permissionOptionsFor2(preset) {
|
|
|
105255
105415
|
function repoFilesConfig(opts) {
|
|
105256
105416
|
if (opts.projectTrusted === true && !opts.noSettingFiles) return {};
|
|
105257
105417
|
const keys = /* @__PURE__ */ new Set();
|
|
105258
|
-
for (const start2 of [
|
|
105418
|
+
for (const start2 of [resolve9(opts.cwd), realPathOr(opts.cwd)]) {
|
|
105259
105419
|
for (let dir = start2; ; dir = dirname9(dir)) {
|
|
105260
105420
|
keys.add(dir);
|
|
105261
105421
|
if (dirname9(dir) === dir) break;
|
|
@@ -105270,7 +105430,7 @@ function realPathOr(path2) {
|
|
|
105270
105430
|
try {
|
|
105271
105431
|
return realpathSync7.native(path2);
|
|
105272
105432
|
} catch {
|
|
105273
|
-
return
|
|
105433
|
+
return resolve9(path2);
|
|
105274
105434
|
}
|
|
105275
105435
|
}
|
|
105276
105436
|
var APP_APPROVAL_MODE = {
|
|
@@ -106389,7 +106549,7 @@ function threadIdOf(res) {
|
|
|
106389
106549
|
}
|
|
106390
106550
|
function codexHome() {
|
|
106391
106551
|
const custom2 = process.env.CODEX_HOME?.trim();
|
|
106392
|
-
return custom2 ? custom2 :
|
|
106552
|
+
return custom2 ? custom2 : join24(homedir10(), ".codex");
|
|
106393
106553
|
}
|
|
106394
106554
|
var CODEX_VERBOSITIES = ["low", "medium", "high"];
|
|
106395
106555
|
var CodexAdapter = class {
|
|
@@ -106445,7 +106605,7 @@ var CodexAdapter = class {
|
|
|
106445
106605
|
const launch2 = launchFor(path2 ?? "codex");
|
|
106446
106606
|
const { stdout } = await exec4(launch2.command, [...launch2.args, "--version"], { timeout: 5e3 });
|
|
106447
106607
|
const version2 = `${stdout.trim()} \xB7 ${path2 ?? "PATH"}`;
|
|
106448
|
-
const loggedIn =
|
|
106608
|
+
const loggedIn = existsSync17(join24(codexHome(), "auth.json"));
|
|
106449
106609
|
return {
|
|
106450
106610
|
tool: "codex",
|
|
106451
106611
|
installed: true,
|
|
@@ -106579,7 +106739,7 @@ function registeredTool(adapters2, preferred) {
|
|
|
106579
106739
|
|
|
106580
106740
|
// packages/agent-host/src/dev-services/store.ts
|
|
106581
106741
|
import Database from "better-sqlite3";
|
|
106582
|
-
import { existsSync as
|
|
106742
|
+
import { existsSync as existsSync18, readFileSync as readFileSync12 } from "node:fs";
|
|
106583
106743
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
106584
106744
|
function resolveSchemaPath() {
|
|
106585
106745
|
const candidates = [
|
|
@@ -106588,7 +106748,7 @@ function resolveSchemaPath() {
|
|
|
106588
106748
|
new URL("../../../protocol/src/schema/schema.sql", import.meta.url)
|
|
106589
106749
|
// source tree
|
|
106590
106750
|
].map((u) => fileURLToPath4(u));
|
|
106591
|
-
const found = candidates.find((p) =>
|
|
106751
|
+
const found = candidates.find((p) => existsSync18(p));
|
|
106592
106752
|
if (!found) throw new Error(`schema.sql not found: ${candidates.join(", ")}`);
|
|
106593
106753
|
return found;
|
|
106594
106754
|
}
|
|
@@ -106866,7 +107026,7 @@ var Store = class _Store {
|
|
|
106866
107026
|
const probe = Object.create(_Store.prototype);
|
|
106867
107027
|
const steps = probe.migrationSteps();
|
|
106868
107028
|
const known = steps[steps.length - 1].to;
|
|
106869
|
-
if (dbPath2 === ":memory:" || !
|
|
107029
|
+
if (dbPath2 === ":memory:" || !existsSync18(dbPath2)) return empty(known);
|
|
106870
107030
|
const db = new Database(dbPath2, { readonly: true, fileMustExist: true });
|
|
106871
107031
|
try {
|
|
106872
107032
|
probe.db = db;
|
|
@@ -108667,7 +108827,7 @@ var Store = class _Store {
|
|
|
108667
108827
|
this.db.prepare(`DELETE FROM sessions WHERE id = ? AND deleted_at IS NOT NULL`).run(sessionId);
|
|
108668
108828
|
return true;
|
|
108669
108829
|
});
|
|
108670
|
-
while (!step()) await new Promise((
|
|
108830
|
+
while (!step()) await new Promise((resolve12) => setImmediate(resolve12));
|
|
108671
108831
|
return true;
|
|
108672
108832
|
}
|
|
108673
108833
|
/** One session's messages in seq order, one transaction per chunk, letting the event loop go between chunks (#179) */
|
|
@@ -108682,7 +108842,7 @@ var Store = class _Store {
|
|
|
108682
108842
|
if (rows.length > 0) after = rows[rows.length - 1].seq;
|
|
108683
108843
|
return rows.length < chunk;
|
|
108684
108844
|
});
|
|
108685
|
-
while (!step()) await new Promise((
|
|
108845
|
+
while (!step()) await new Promise((resolve12) => setImmediate(resolve12));
|
|
108686
108846
|
}
|
|
108687
108847
|
isTrashed(sessionId) {
|
|
108688
108848
|
return !!this.db.prepare(`SELECT 1 FROM sessions WHERE id = ? AND deleted_at IS NOT NULL`).get(sessionId);
|
|
@@ -109349,7 +109509,8 @@ function parseTrashRecord(json2) {
|
|
|
109349
109509
|
projectName: str8(r.projectName),
|
|
109350
109510
|
projectPath: str8(r.projectPath),
|
|
109351
109511
|
removeExternal: r.removeExternal === true,
|
|
109352
|
-
removeWorktree: r.removeWorktree === true
|
|
109512
|
+
removeWorktree: r.removeWorktree === true,
|
|
109513
|
+
...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
109514
|
};
|
|
109354
109515
|
}
|
|
109355
109516
|
var OUT_OF_TRASH = `session_id NOT IN (SELECT id FROM sessions WHERE deleted_at IS NOT NULL)`;
|
|
@@ -109374,7 +109535,7 @@ function indexedText(kind, payload) {
|
|
|
109374
109535
|
|
|
109375
109536
|
// packages/agent-host/src/dev-services/git.ts
|
|
109376
109537
|
import { execFile as execFile6 } from "node:child_process";
|
|
109377
|
-
import { isAbsolute as isAbsolute8, relative as relative7, resolve as
|
|
109538
|
+
import { isAbsolute as isAbsolute8, relative as relative7, resolve as resolve10, sep as sep9 } from "node:path";
|
|
109378
109539
|
import { promisify as promisify6 } from "node:util";
|
|
109379
109540
|
var exec5 = promisify6(execFile6);
|
|
109380
109541
|
var OK2 = { timeout: 1e4, maxBuffer: 32 * 1024 * 1024 };
|
|
@@ -109446,8 +109607,8 @@ function assertLexicalGitPath(cwd, path2) {
|
|
|
109446
109607
|
if (!path2 || path2.includes("\0") || path2.startsWith(":")) {
|
|
109447
109608
|
throw Object.assign(new Error("Invalid git path"), { code: "internal" });
|
|
109448
109609
|
}
|
|
109449
|
-
const root3 =
|
|
109450
|
-
const target = isAbsolute8(path2) ?
|
|
109610
|
+
const root3 = resolve10(cwd);
|
|
109611
|
+
const target = isAbsolute8(path2) ? resolve10(path2) : resolve10(root3, path2);
|
|
109451
109612
|
const rel = relative7(root3, target);
|
|
109452
109613
|
if (rel === "") throw Object.assign(new Error("Path is the project root, not a file in it"), { code: "internal" });
|
|
109453
109614
|
if (rel !== ".." && !rel.startsWith(`..${sep9}`) && !isAbsolute8(rel)) return rel;
|
|
@@ -109566,14 +109727,14 @@ async function gitIgnoredEntries(cwd, limit = 50, trust = {}) {
|
|
|
109566
109727
|
if (paths.length === 0) return [];
|
|
109567
109728
|
const sizes = /* @__PURE__ */ new Map();
|
|
109568
109729
|
try {
|
|
109569
|
-
const out = await new Promise((
|
|
109730
|
+
const out = await new Promise((resolve12, reject) => {
|
|
109570
109731
|
execFile6(
|
|
109571
109732
|
"du",
|
|
109572
109733
|
["-sk", ...paths.slice(0, limit)],
|
|
109573
109734
|
{ cwd, timeout: 5e3, maxBuffer: 1 << 20 },
|
|
109574
109735
|
(err, stdout) => (
|
|
109575
109736
|
// du exits non-zero if even one entry cannot be read — whatever it did print is used anyway
|
|
109576
|
-
stdout ?
|
|
109737
|
+
stdout ? resolve12(stdout) : reject(err)
|
|
109577
109738
|
)
|
|
109578
109739
|
);
|
|
109579
109740
|
});
|
|
@@ -109686,8 +109847,8 @@ async function gitValidBranchName(name) {
|
|
|
109686
109847
|
return false;
|
|
109687
109848
|
}
|
|
109688
109849
|
}
|
|
109689
|
-
async function gitWorktreeRemove(repoCwd, path2, force = false) {
|
|
109690
|
-
await git(repoCwd, ["worktree", "remove", ...force ? ["--force"] : [], path2]);
|
|
109850
|
+
async function gitWorktreeRemove(repoCwd, path2, force = false, withSubmodules = false) {
|
|
109851
|
+
await git(repoCwd, ["worktree", "remove", ...force ? ["--force", ...withSubmodules ? ["--force"] : []] : [], path2]);
|
|
109691
109852
|
}
|
|
109692
109853
|
async function gitBranchDelete(repoCwd, branch, trust = {}) {
|
|
109693
109854
|
await git(repoCwd, ["branch", "-D", branch], trust);
|
|
@@ -109703,13 +109864,13 @@ async function gitWorktreeDirty(path2, trust = {}) {
|
|
|
109703
109864
|
}
|
|
109704
109865
|
|
|
109705
109866
|
// packages/agent-host/src/dev-services/worktree-dependencies.ts
|
|
109706
|
-
import { lstat as
|
|
109707
|
-
import { join as
|
|
109867
|
+
import { lstat as lstat5, readFile as readFile4, readdir as readdir6 } from "node:fs/promises";
|
|
109868
|
+
import { join as join25 } from "node:path";
|
|
109708
109869
|
var LOCKFILES = ["pnpm-lock.yaml", "package-lock.json", "npm-shrinkwrap.json", "yarn.lock", "bun.lock", "bun.lockb"];
|
|
109709
109870
|
async function prepareWorktreeDependencies(projectCwd, worktreeCwd) {
|
|
109710
109871
|
let locked = false;
|
|
109711
109872
|
for (const name of LOCKFILES) {
|
|
109712
|
-
const target = await
|
|
109873
|
+
const target = await lstat5(join25(worktreeCwd, name)).catch((err) => {
|
|
109713
109874
|
if (err.code === "ENOENT") return null;
|
|
109714
109875
|
throw err;
|
|
109715
109876
|
});
|
|
@@ -109718,7 +109879,7 @@ async function prepareWorktreeDependencies(projectCwd, worktreeCwd) {
|
|
|
109718
109879
|
resolveExisting(projectCwd, name),
|
|
109719
109880
|
resolveExisting(worktreeCwd, name)
|
|
109720
109881
|
]);
|
|
109721
|
-
const [before, after] = await Promise.all([
|
|
109882
|
+
const [before, after] = await Promise.all([readFile4(source), readFile4(destination)]);
|
|
109722
109883
|
if (!before.equals(after)) return;
|
|
109723
109884
|
locked = true;
|
|
109724
109885
|
}
|
|
@@ -109727,22 +109888,22 @@ async function prepareWorktreeDependencies(projectCwd, worktreeCwd) {
|
|
|
109727
109888
|
const folders = [""];
|
|
109728
109889
|
while (folders.length) {
|
|
109729
109890
|
const folder = folders.pop();
|
|
109730
|
-
const entries = await
|
|
109891
|
+
const entries = await readdir6(join25(worktreeCwd, folder), { withFileTypes: true });
|
|
109731
109892
|
for (const entry of entries) {
|
|
109732
109893
|
if (entry.isDirectory() && entry.name !== ".git" && entry.name !== "node_modules") {
|
|
109733
|
-
folders.push(
|
|
109894
|
+
folders.push(join25(folder, entry.name));
|
|
109734
109895
|
}
|
|
109735
109896
|
}
|
|
109736
109897
|
if (folder && !entries.some((e) => e.name === "package.json" && e.isFile())) continue;
|
|
109737
109898
|
if (entries.some((e) => e.name === "node_modules")) continue;
|
|
109738
|
-
const rel =
|
|
109899
|
+
const rel = join25(folder, "node_modules");
|
|
109739
109900
|
try {
|
|
109740
109901
|
const src = await resolveExisting(projectCwd, rel);
|
|
109741
|
-
if (!(await
|
|
109742
|
-
const manifest =
|
|
109902
|
+
if (!(await lstat5(src)).isDirectory()) continue;
|
|
109903
|
+
const manifest = join25(folder, "package.json");
|
|
109743
109904
|
const [source, target] = await Promise.all([
|
|
109744
|
-
resolveExisting(projectCwd, manifest).then((p) =>
|
|
109745
|
-
resolveExisting(worktreeCwd, manifest).then((p) =>
|
|
109905
|
+
resolveExisting(projectCwd, manifest).then((p) => readFile4(p)),
|
|
109906
|
+
resolveExisting(worktreeCwd, manifest).then((p) => readFile4(p))
|
|
109746
109907
|
]);
|
|
109747
109908
|
if (!source.equals(target)) continue;
|
|
109748
109909
|
const dst = await prepareCopyTarget(worktreeCwd, rel);
|
|
@@ -109764,8 +109925,8 @@ async function prepareWorktreeDependencies(projectCwd, worktreeCwd) {
|
|
|
109764
109925
|
}
|
|
109765
109926
|
|
|
109766
109927
|
// packages/agent-host/src/dev-services/file-search.ts
|
|
109767
|
-
import { readdir as
|
|
109768
|
-
import { join as
|
|
109928
|
+
import { readdir as readdir7 } from "node:fs/promises";
|
|
109929
|
+
import { join as join26 } from "node:path";
|
|
109769
109930
|
var MAX_FILES2 = 2e4;
|
|
109770
109931
|
var TTL_MS2 = 15e3;
|
|
109771
109932
|
var WALK_DEPTH = 6;
|
|
@@ -109793,7 +109954,7 @@ async function walk(root3) {
|
|
|
109793
109954
|
const { dir, rel, depth } = queue.shift();
|
|
109794
109955
|
let entries;
|
|
109795
109956
|
try {
|
|
109796
|
-
entries = await
|
|
109957
|
+
entries = await readdir7(dir, { withFileTypes: true });
|
|
109797
109958
|
} catch {
|
|
109798
109959
|
continue;
|
|
109799
109960
|
}
|
|
@@ -109801,7 +109962,7 @@ async function walk(root3) {
|
|
|
109801
109962
|
if (e.name.startsWith(".") && e.name !== ".env") continue;
|
|
109802
109963
|
if (e.isDirectory()) {
|
|
109803
109964
|
if (SKIP.has(e.name) || depth >= WALK_DEPTH) continue;
|
|
109804
|
-
queue.push({ dir:
|
|
109965
|
+
queue.push({ dir: join26(dir, e.name), rel: wireJoin(rel, e.name), depth: depth + 1 });
|
|
109805
109966
|
} else {
|
|
109806
109967
|
out.push(wireJoin(rel, e.name).normalize("NFC"));
|
|
109807
109968
|
if (out.length >= MAX_FILES2) break;
|
|
@@ -109866,14 +110027,14 @@ async function searchFiles(root3, query, limit = 20, trust = {}) {
|
|
|
109866
110027
|
}
|
|
109867
110028
|
|
|
109868
110029
|
// packages/agent-host/src/dev-services/attachments.ts
|
|
109869
|
-
import { mkdir as
|
|
109870
|
-
import { extname as extname3, join as
|
|
109871
|
-
var root = () =>
|
|
110030
|
+
import { mkdir as mkdir5, writeFile as writeFile3, rm as rm5 } from "node:fs/promises";
|
|
110031
|
+
import { extname as extname3, join as join27 } from "node:path";
|
|
110032
|
+
var root = () => join27(dataRoot(), "attachments");
|
|
109872
110033
|
function sessionDir(sessionId) {
|
|
109873
110034
|
if (!isSessionId(sessionId)) {
|
|
109874
110035
|
throw Object.assign(new Error(`Not a session id: ${sessionId}`), { code: "internal" });
|
|
109875
110036
|
}
|
|
109876
|
-
return
|
|
110037
|
+
return join27(root(), sessionId);
|
|
109877
110038
|
}
|
|
109878
110039
|
var EXT = {
|
|
109879
110040
|
"image/png": ".png",
|
|
@@ -109883,43 +110044,43 @@ var EXT = {
|
|
|
109883
110044
|
};
|
|
109884
110045
|
async function saveAttachment(sessionId, name, mime, dataBase64) {
|
|
109885
110046
|
const dir = sessionDir(sessionId);
|
|
109886
|
-
await
|
|
110047
|
+
await mkdir5(dir, { recursive: true });
|
|
109887
110048
|
const ext2 = extname3(name) || EXT[mime] || "";
|
|
109888
|
-
const file2 =
|
|
110049
|
+
const file2 = join27(dir, `${Date.now()}-${Math.random().toString(36).slice(2, 8)}${ext2}`);
|
|
109889
110050
|
const buf = Buffer.from(dataBase64, "base64");
|
|
109890
|
-
await
|
|
110051
|
+
await writeFile3(file2, buf);
|
|
109891
110052
|
return { kind: mime.startsWith("image/") ? "image" : "file", path: file2, name, mime, bytes: buf.length };
|
|
109892
110053
|
}
|
|
109893
110054
|
async function clearAttachments(sessionId) {
|
|
109894
|
-
await
|
|
110055
|
+
await rm5(sessionDir(sessionId), { recursive: true, force: true });
|
|
109895
110056
|
}
|
|
109896
110057
|
async function attachmentBytes(sessionId) {
|
|
109897
|
-
const { readdir:
|
|
110058
|
+
const { readdir: readdir10, lstat: lstat7 } = await import("node:fs/promises");
|
|
109898
110059
|
const dir = sessionDir(sessionId);
|
|
109899
|
-
const names = await
|
|
110060
|
+
const names = await readdir10(dir).catch(() => []);
|
|
109900
110061
|
let total = 0;
|
|
109901
110062
|
for (const name of names) {
|
|
109902
|
-
const s = await
|
|
110063
|
+
const s = await lstat7(join27(dir, name)).catch(() => null);
|
|
109903
110064
|
if (s?.isFile()) total += s.size;
|
|
109904
110065
|
}
|
|
109905
110066
|
return total;
|
|
109906
110067
|
}
|
|
109907
110068
|
var ATTACHMENTS_MAX_BYTES = 500 * 1048576;
|
|
109908
110069
|
async function sweepAttachments(maxBytes = ATTACHMENTS_MAX_BYTES) {
|
|
109909
|
-
const { readdir:
|
|
110070
|
+
const { readdir: readdir10, stat: stat7, rm: rmFile } = await import("node:fs/promises");
|
|
109910
110071
|
const files = [];
|
|
109911
110072
|
let dirs;
|
|
109912
110073
|
try {
|
|
109913
|
-
dirs = await
|
|
110074
|
+
dirs = await readdir10(root());
|
|
109914
110075
|
} catch {
|
|
109915
110076
|
return 0;
|
|
109916
110077
|
}
|
|
109917
110078
|
for (const d2 of dirs) {
|
|
109918
|
-
const dir =
|
|
109919
|
-
const names = await
|
|
110079
|
+
const dir = join27(root(), d2);
|
|
110080
|
+
const names = await readdir10(dir).catch(() => []);
|
|
109920
110081
|
for (const name of names) {
|
|
109921
|
-
const p =
|
|
109922
|
-
const s = await
|
|
110082
|
+
const p = join27(dir, name);
|
|
110083
|
+
const s = await stat7(p).catch(() => null);
|
|
109923
110084
|
if (s?.isFile()) files.push({ path: p, size: s.size, mtime: s.mtimeMs });
|
|
109924
110085
|
}
|
|
109925
110086
|
}
|
|
@@ -109939,7 +110100,7 @@ async function sweepAttachments(maxBytes = ATTACHMENTS_MAX_BYTES) {
|
|
|
109939
110100
|
|
|
109940
110101
|
// packages/agent-host/src/dev-services/message-image.ts
|
|
109941
110102
|
import { constants as constants7 } from "node:fs";
|
|
109942
|
-
import { open as open4, realpath as
|
|
110103
|
+
import { open as open4, realpath as realpath7 } from "node:fs/promises";
|
|
109943
110104
|
import { homedir as homedir11 } from "node:os";
|
|
109944
110105
|
import { posix as posix3, sep as sep10, win32 as win326 } from "node:path";
|
|
109945
110106
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
@@ -109996,7 +110157,7 @@ var mb2 = (n) => `${(n / 1e6).toFixed(1)} MB`;
|
|
|
109996
110157
|
async function readMessageImage(abs, max = IMAGE_PREVIEW_MAX_BYTES) {
|
|
109997
110158
|
let file2;
|
|
109998
110159
|
try {
|
|
109999
|
-
file2 = await
|
|
110160
|
+
file2 = await realpath7(abs);
|
|
110000
110161
|
} catch (e) {
|
|
110001
110162
|
const code2 = e.code;
|
|
110002
110163
|
if (code2 === "EACCES" || code2 === "EPERM") return refuse5("unreadable", `Centralu may not read ${abs}`);
|
|
@@ -110035,36 +110196,36 @@ async function readMessageImage(abs, max = IMAGE_PREVIEW_MAX_BYTES) {
|
|
|
110035
110196
|
}
|
|
110036
110197
|
|
|
110037
110198
|
// packages/agent-host/src/dev-services/handoff-notes.ts
|
|
110038
|
-
import { lstat as
|
|
110039
|
-
import { join as
|
|
110040
|
-
var root2 = () =>
|
|
110199
|
+
import { lstat as lstat6, mkdir as mkdir6, readdir as readdir8, rm as rm6, writeFile as writeFile4 } from "node:fs/promises";
|
|
110200
|
+
import { join as join28 } from "node:path";
|
|
110201
|
+
var root2 = () => join28(dataRoot(), "handoff");
|
|
110041
110202
|
function handoffNoteDir(projectId) {
|
|
110042
110203
|
if (!isProjectId(projectId)) {
|
|
110043
110204
|
throw Object.assign(new Error(`Not a project id: ${projectId}`), { code: "internal" });
|
|
110044
110205
|
}
|
|
110045
|
-
return
|
|
110206
|
+
return join28(root2(), projectId);
|
|
110046
110207
|
}
|
|
110047
110208
|
function handoffNotePath(projectId, sessionId) {
|
|
110048
110209
|
if (!isSessionId(sessionId)) {
|
|
110049
110210
|
throw Object.assign(new Error(`Not a session id: ${sessionId}`), { code: "internal" });
|
|
110050
110211
|
}
|
|
110051
|
-
return
|
|
110212
|
+
return join28(handoffNoteDir(projectId), `${sessionId}.md`);
|
|
110052
110213
|
}
|
|
110053
110214
|
async function handoffNoteBytes(projectId, sessionId) {
|
|
110054
|
-
const s = await
|
|
110215
|
+
const s = await lstat6(handoffNotePath(projectId, sessionId)).catch(() => null);
|
|
110055
110216
|
return s?.isFile() ? s.size : 0;
|
|
110056
110217
|
}
|
|
110057
110218
|
async function writeHandoffNote(projectId, sessionId, text2) {
|
|
110058
110219
|
const path2 = handoffNotePath(projectId, sessionId);
|
|
110059
|
-
await
|
|
110060
|
-
await
|
|
110061
|
-
await
|
|
110220
|
+
await mkdir6(handoffNoteDir(projectId), { recursive: true });
|
|
110221
|
+
await rm6(path2, { force: true });
|
|
110222
|
+
await writeFile4(path2, text2, "utf8");
|
|
110062
110223
|
return path2;
|
|
110063
110224
|
}
|
|
110064
110225
|
async function sweepHandoffNotes(owned, projectId) {
|
|
110065
110226
|
let dirs;
|
|
110066
110227
|
try {
|
|
110067
|
-
dirs = projectId ? [projectId] : (await
|
|
110228
|
+
dirs = projectId ? [projectId] : (await readdir8(root2(), { withFileTypes: true })).filter((d2) => d2.isDirectory()).map((d2) => d2.name);
|
|
110068
110229
|
} catch {
|
|
110069
110230
|
return;
|
|
110070
110231
|
}
|
|
@@ -110075,12 +110236,12 @@ async function sweepHandoffNotes(owned, projectId) {
|
|
|
110075
110236
|
} catch {
|
|
110076
110237
|
continue;
|
|
110077
110238
|
}
|
|
110078
|
-
const entries = await
|
|
110239
|
+
const entries = await readdir8(dir, { withFileTypes: true }).catch(() => []);
|
|
110079
110240
|
for (const e of entries) {
|
|
110080
110241
|
if (!e.isFile() || !e.name.endsWith(".md")) continue;
|
|
110081
110242
|
const owner = e.name.slice(0, -".md".length);
|
|
110082
110243
|
if (owned(owner)) continue;
|
|
110083
|
-
await
|
|
110244
|
+
await rm6(join28(dir, e.name), { force: true }).catch(() => {
|
|
110084
110245
|
});
|
|
110085
110246
|
}
|
|
110086
110247
|
}
|
|
@@ -110154,7 +110315,7 @@ var HUB_SENDER = "_hub-orchestrator";
|
|
|
110154
110315
|
var builderKey = (ref) => `${ref.projectId ?? "_user"}/${ref.appId}`;
|
|
110155
110316
|
var APP_BUILDERS_KEY = "apps.builders";
|
|
110156
110317
|
var SessionManager = class {
|
|
110157
|
-
constructor(store2, adapters2, emit, endpoint, worktreeRoot =
|
|
110318
|
+
constructor(store2, adapters2, emit, endpoint, worktreeRoot = join29(homedir12(), DATA_DIR, "worktrees"), keeperOpts = {}) {
|
|
110158
110319
|
this.store = store2;
|
|
110159
110320
|
this.adapters = adapters2;
|
|
110160
110321
|
this.emit = emit;
|
|
@@ -110167,10 +110328,17 @@ var SessionManager = class {
|
|
|
110167
110328
|
rawEmit(withoutToolRecord(full));
|
|
110168
110329
|
};
|
|
110169
110330
|
const LIVE_ONLY = ["working", "waiting_approval"];
|
|
110170
|
-
|
|
110331
|
+
const storedSessions = store2.listSessions();
|
|
110332
|
+
const storedProjects = new Map(storedSessions.map((s) => [s.id, s.projectId]));
|
|
110333
|
+
for (const s of storedSessions) {
|
|
110171
110334
|
const stale = LIVE_ONLY.includes(s.state) && !keeperOpts.keptSessions?.has(s.id);
|
|
110172
110335
|
const fixed = stale ? { ...s, state: "idle", waitingSince: null } : s;
|
|
110336
|
+
const ownerProject = fixed.askedBy ? storedProjects.get(fixed.askedBy) ?? store2.trashedSession(fixed.askedBy)?.record.projectId : void 0;
|
|
110337
|
+
const first = fixed.askedBy && ownerProject === void 0 ? store2.loadMessagesFrom(fixed.id, 0, 1)[0]?.payload : void 0;
|
|
110338
|
+
const oldAsk = fixed.askedBy && (ownerProject !== void 0 ? ownerProject !== fixed.projectId : first?.from?.sessionId === fixed.askedBy && first.text?.includes("through Centralu's ask_project"));
|
|
110339
|
+
if (oldAsk) delete fixed.askedBy;
|
|
110173
110340
|
this.meta.set(s.id, fixed);
|
|
110341
|
+
if (oldAsk) store2.upsertSession(fixed);
|
|
110174
110342
|
if (stale) {
|
|
110175
110343
|
console.error(`[agent-host] stale state reset: ${s.id.slice(0, 8)} ${s.state} -> idle`);
|
|
110176
110344
|
store2.upsertSession(fixed);
|
|
@@ -110311,6 +110479,8 @@ var SessionManager = class {
|
|
|
110311
110479
|
* comment on `runAppAgent`).
|
|
110312
110480
|
*/
|
|
110313
110481
|
agentRuns = /* @__PURE__ */ new Map();
|
|
110482
|
+
/** Temporary readers of a person's turn; they never own work or pin the calling session awake. */
|
|
110483
|
+
turnObservers = /* @__PURE__ */ new Map();
|
|
110314
110484
|
/**
|
|
110315
110485
|
* A card the host raises in a session's approval slot — requestId -> question: an app's capability question (M4
|
|
110316
110486
|
* D-4), or one project reaching another (#371). It uses the same slot as the adapter's card (the session's
|
|
@@ -110319,8 +110489,8 @@ var SessionManager = class {
|
|
|
110319
110489
|
*/
|
|
110320
110490
|
hostAsks = /* @__PURE__ */ new Map();
|
|
110321
110491
|
/**
|
|
110322
|
-
* ask_project's delegations still owed to their caller (#371 part B) — `<caller session>><target project>` -> the
|
|
110323
|
-
* delegation. One per
|
|
110492
|
+
* ask_project's delegations still owed to their caller (#371 part B) — `<caller session>><target project>><target session>` -> the
|
|
110493
|
+
* 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
110494
|
* on this one. Removed once the caller has read the outcome; an outcome nobody comes back for goes with the caller.
|
|
110325
110495
|
*/
|
|
110326
110496
|
delegations = /* @__PURE__ */ new Map();
|
|
@@ -110382,11 +110552,11 @@ var SessionManager = class {
|
|
|
110382
110552
|
);
|
|
110383
110553
|
}
|
|
110384
110554
|
async addProject(path2) {
|
|
110385
|
-
if (!
|
|
110555
|
+
if (!existsSync19(path2) || !statSync12(path2).isDirectory()) {
|
|
110386
110556
|
throw Object.assign(new Error(`Directory not found: ${path2}`), { code: "internal" });
|
|
110387
110557
|
}
|
|
110388
110558
|
const existing = this.store.findProjectByPath(path2);
|
|
110389
|
-
const id2 = existing?.id ??
|
|
110559
|
+
const id2 = existing?.id ?? randomUUID10();
|
|
110390
110560
|
this.store.addProject({ id: id2, path: path2, name: basename5(path2) });
|
|
110391
110561
|
return this.projectInfo(id2, path2);
|
|
110392
110562
|
}
|
|
@@ -110398,8 +110568,8 @@ var SessionManager = class {
|
|
|
110398
110568
|
projectsHome = () => {
|
|
110399
110569
|
throw Object.assign(new Error("This host has no default location for a new project \u2014 give a parent folder"), { code: "internal" });
|
|
110400
110570
|
};
|
|
110401
|
-
useProjectsHome(
|
|
110402
|
-
this.projectsHome =
|
|
110571
|
+
useProjectsHome(resolve12) {
|
|
110572
|
+
this.projectsHome = resolve12;
|
|
110403
110573
|
}
|
|
110404
110574
|
/**
|
|
110405
110575
|
* The folder a new project would get, and whether a clone source is one this host clones (#515) — checked, nothing
|
|
@@ -110900,6 +111070,9 @@ var SessionManager = class {
|
|
|
110900
111070
|
* null — the only place that can create a session with no project is `orchestrator()` below.
|
|
110901
111071
|
*/
|
|
110902
111072
|
async createSession(params) {
|
|
111073
|
+
if (params.askedBy && this.meta.get(params.askedBy)?.projectId !== params.projectId) {
|
|
111074
|
+
throw new Error("A session cannot create a session in another project");
|
|
111075
|
+
}
|
|
110903
111076
|
if (!params.tool) {
|
|
110904
111077
|
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
111078
|
}
|
|
@@ -110914,7 +111087,7 @@ var SessionManager = class {
|
|
|
110914
111087
|
);
|
|
110915
111088
|
}
|
|
110916
111089
|
}
|
|
110917
|
-
const id2 =
|
|
111090
|
+
const id2 = randomUUID10();
|
|
110918
111091
|
let worktree = null;
|
|
110919
111092
|
if (params.worktree && params.projectId) {
|
|
110920
111093
|
const summary = await gitSummary(params.cwd, this.gitTrust(params.projectId));
|
|
@@ -110927,7 +111100,7 @@ var SessionManager = class {
|
|
|
110927
111100
|
);
|
|
110928
111101
|
}
|
|
110929
111102
|
const path2 = this.worktreePathFor(params.projectId, id2);
|
|
110930
|
-
if (!this.projectTrusted(params.projectId)) {
|
|
111103
|
+
if (!this.projectTrusted(params.projectId) && !(params.askedBy && this.autoBoundaryOpen(params.askedBy, "worktree in an untrusted project"))) {
|
|
110931
111104
|
throw Object.assign(
|
|
110932
111105
|
new Error("Worktree sessions need a trusted project: checking out runs the repository's hooks and filters. Trust the project first."),
|
|
110933
111106
|
{ code: "internal" }
|
|
@@ -111318,6 +111491,7 @@ var SessionManager = class {
|
|
|
111318
111491
|
);
|
|
111319
111492
|
}
|
|
111320
111493
|
async doResumeSession(sessionId, adopt) {
|
|
111494
|
+
if (this.worktreeCleanup.has(sessionId)) throw Object.assign(new Error("The session worktree is being removed"), { code: "internal" });
|
|
111321
111495
|
const m = this.meta.get(sessionId);
|
|
111322
111496
|
if (!m) throw Object.assign(new Error(`Session not found: ${sessionId}`), { code: "session_not_found" });
|
|
111323
111497
|
if (this.handles.has(sessionId)) return { session: m, resumed: true };
|
|
@@ -111506,6 +111680,7 @@ var SessionManager = class {
|
|
|
111506
111680
|
* exactly as it was, and only what happens from here on piles up in the copy.
|
|
111507
111681
|
*/
|
|
111508
111682
|
async forkConversation(sessionId) {
|
|
111683
|
+
if (this.worktreeCleanup.has(sessionId)) throw Object.assign(new Error("The session worktree is being removed"), { code: "internal" });
|
|
111509
111684
|
const m = this.meta.get(sessionId);
|
|
111510
111685
|
if (!m) throw Object.assign(new Error(`Session not found: ${sessionId}`), { code: "session_not_found" });
|
|
111511
111686
|
const source = m.externalId ?? m.importedFrom;
|
|
@@ -111556,7 +111731,8 @@ var SessionManager = class {
|
|
|
111556
111731
|
* delete knows that layout (the Claude SDK's `deleteSession`). Left in place it is also a second way back — the
|
|
111557
111732
|
* tool, and **+ → Past conversations**, still have it.
|
|
111558
111733
|
*/
|
|
111559
|
-
async trashSession(sessionId, removeWorktree = false, removeExternal = false) {
|
|
111734
|
+
async trashSession(sessionId, removeWorktree = false, removeExternal = false, recovery) {
|
|
111735
|
+
if (this.worktreeCleanup.has(sessionId) && !recovery) throw Object.assign(new Error("The session worktree is being removed"), { code: "internal" });
|
|
111560
111736
|
const m = this.meta.get(sessionId);
|
|
111561
111737
|
if (!m) {
|
|
111562
111738
|
this.emit({ type: "session_deleted", sessionId });
|
|
@@ -111572,6 +111748,7 @@ var SessionManager = class {
|
|
|
111572
111748
|
agentRun.deleted = true;
|
|
111573
111749
|
agentRun.fail(new Error("the person deleted the agent session before it answered"));
|
|
111574
111750
|
}
|
|
111751
|
+
for (const observer of this.turnObservers.get(sessionId) ?? []) observer.fail(new Error("the person deleted the session before it answered"));
|
|
111575
111752
|
for (const [key, d2] of this.delegations) {
|
|
111576
111753
|
if (d2.callerId !== sessionId) continue;
|
|
111577
111754
|
this.delegations.delete(key);
|
|
@@ -111605,7 +111782,8 @@ var SessionManager = class {
|
|
|
111605
111782
|
projectName: project2?.name ?? null,
|
|
111606
111783
|
projectPath: project2?.path ?? null,
|
|
111607
111784
|
removeExternal: removeExternal && !!externalId,
|
|
111608
|
-
removeWorktree: removeWorktree && !!m.worktree
|
|
111785
|
+
removeWorktree: removeWorktree && !!m.worktree,
|
|
111786
|
+
...recovery ? { recovery } : {}
|
|
111609
111787
|
});
|
|
111610
111788
|
this.appsHub?.sessionGone(sessionId);
|
|
111611
111789
|
this.emit({ type: "session_deleted", sessionId });
|
|
@@ -111613,7 +111791,7 @@ var SessionManager = class {
|
|
|
111613
111791
|
/** Only one trash operation per session at a time — a purge racing a restore would restore half of a deleted session */
|
|
111614
111792
|
trashBusy = /* @__PURE__ */ new Set();
|
|
111615
111793
|
async trashOp(sessionId, run) {
|
|
111616
|
-
if (this.trashBusy.has(sessionId)) {
|
|
111794
|
+
if (this.trashBusy.has(sessionId) || this.worktreeCleanup.has(sessionId)) {
|
|
111617
111795
|
throw Object.assign(new Error("That session is already being restored or deleted"), { code: "internal" });
|
|
111618
111796
|
}
|
|
111619
111797
|
this.trashBusy.add(sessionId);
|
|
@@ -111630,7 +111808,7 @@ var SessionManager = class {
|
|
|
111630
111808
|
this.store.listTrash().map(async (r) => {
|
|
111631
111809
|
const { record: record2 } = r;
|
|
111632
111810
|
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);
|
|
111811
|
+
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
111812
|
return {
|
|
111635
111813
|
id: r.id,
|
|
111636
111814
|
name: r.name,
|
|
@@ -111646,7 +111824,7 @@ var SessionManager = class {
|
|
|
111646
111824
|
bytes: r.bytes + files,
|
|
111647
111825
|
conversationFile: !r.hasConversationFile ? "none" : record2.removeExternal ? "remove" : "keep",
|
|
111648
111826
|
// Only a worktree still on disk is listed — the merged-worktree cleanup removes the folder before the trash is emptied
|
|
111649
|
-
worktree: r.worktree &&
|
|
111827
|
+
worktree: r.worktree && existsSync19(r.worktree.path) ? { ...r.worktree, remove: record2.removeWorktree } : null
|
|
111650
111828
|
};
|
|
111651
111829
|
})
|
|
111652
111830
|
);
|
|
@@ -111684,7 +111862,7 @@ var SessionManager = class {
|
|
|
111684
111862
|
const home = projects.find((p) => p.id === record2.projectId) ?? (record2.projectPath ? projects.find((p) => p.path === record2.projectPath) : void 0);
|
|
111685
111863
|
if (home) {
|
|
111686
111864
|
projectId = home.id;
|
|
111687
|
-
} else if (record2.projectPath &&
|
|
111865
|
+
} else if (record2.projectPath && existsSync19(record2.projectPath) && statSync12(record2.projectPath).isDirectory()) {
|
|
111688
111866
|
this.store.addProject({
|
|
111689
111867
|
id: record2.projectId,
|
|
111690
111868
|
path: record2.projectPath,
|
|
@@ -111703,14 +111881,21 @@ var SessionManager = class {
|
|
|
111703
111881
|
} else if (t.session.kind === "orchestrator" && this.store.orchestratorId()) {
|
|
111704
111882
|
throw Object.assign(new Error("Another orchestrator is in place \u2014 the app runs one at a time"), { code: "internal" });
|
|
111705
111883
|
}
|
|
111884
|
+
let recovered;
|
|
111885
|
+
if (record2.recovery && t.session.worktree && projectId) {
|
|
111886
|
+
const repo = this.store.listProjects().find((p) => p.id === projectId).path;
|
|
111887
|
+
recovered = await restoreWorktreeRecovery(sessionId, repo, t.session.worktree.path, t.session.worktree.branch, record2.recovery, this.gitTrust(projectId));
|
|
111888
|
+
}
|
|
111706
111889
|
const back = await this.store.restoreSession(sessionId, projectId);
|
|
111707
111890
|
if (!back) throw Object.assign(new Error(`Not in the trash: ${sessionId}`), { code: "session_not_found" });
|
|
111708
111891
|
const LIVE_ONLY = ["working", "waiting_approval"];
|
|
111709
111892
|
const fixed = LIVE_ONLY.includes(back.state) ? { ...back, state: "idle", waitingSince: null } : back;
|
|
111710
|
-
if (
|
|
111893
|
+
if (recovered && fixed.worktree) fixed.worktree = { ...fixed.worktree, branch: recovered.branch };
|
|
111894
|
+
if (fixed !== back || recovered) this.store.upsertSession(fixed);
|
|
111711
111895
|
this.meta.set(sessionId, fixed);
|
|
111712
111896
|
const session2 = { ...this.meta.get(sessionId), live: false };
|
|
111713
111897
|
this.emit({ type: "session_created", sessionId, session: session2 });
|
|
111898
|
+
if (recovered) this.onEvent({ type: "notice", sessionId, level: "info", from: "Centralu", label: "worktree recovery", text: recovered.notice });
|
|
111714
111899
|
return { session: session2, project: registered };
|
|
111715
111900
|
});
|
|
111716
111901
|
}
|
|
@@ -111749,6 +111934,7 @@ var SessionManager = class {
|
|
|
111749
111934
|
await gitWorktreeRemove(record2.projectPath, s.worktree.path, true).catch(() => {
|
|
111750
111935
|
});
|
|
111751
111936
|
}
|
|
111937
|
+
await clearWorktreeRecovery(sessionId);
|
|
111752
111938
|
await this.store.purgeSession(sessionId);
|
|
111753
111939
|
await clearAttachments(sessionId).catch(() => {
|
|
111754
111940
|
});
|
|
@@ -111821,6 +112007,7 @@ var SessionManager = class {
|
|
|
111821
112007
|
return live && m && live.model !== m.model ? "model_change" : void 0;
|
|
111822
112008
|
}
|
|
111823
112009
|
async updateSettings(sessionId, s) {
|
|
112010
|
+
if (this.worktreeCleanup.has(sessionId)) throw Object.assign(new Error("The session worktree is being removed"), { code: "internal" });
|
|
111824
112011
|
const m = this.meta.get(sessionId);
|
|
111825
112012
|
if (!m) throw Object.assign(new Error(`Session not found: ${sessionId}`), { code: "session_not_found" });
|
|
111826
112013
|
if (s.model !== void 0 && s.model !== m.model) this.cacheLedger.note(sessionId, "model_change");
|
|
@@ -111937,6 +112124,7 @@ var SessionManager = class {
|
|
|
111937
112124
|
}
|
|
111938
112125
|
const run = e.sessionId ? this.agentRuns.get(e.sessionId) : void 0;
|
|
111939
112126
|
run?.onEvent(e);
|
|
112127
|
+
if (e.sessionId) for (const observer of this.turnObservers.get(e.sessionId) ?? []) observer.onEvent(e);
|
|
111940
112128
|
this.emit(seq != null ? { ...e, seq } : e);
|
|
111941
112129
|
if (endedTurn && e.sessionId && !run?.tracksInputs) void this.reportBackIfAwaited(e.sessionId, e, run?.completedAnswer);
|
|
111942
112130
|
if (e.type === "approval_resolved" && e.sessionId) this.raiseHostAsks(e.sessionId);
|
|
@@ -112318,6 +112506,7 @@ If you need more, you can read that session's recent conversation with read_sess
|
|
|
112318
112506
|
async deliver(sessionId, text2, attachments, from, relayed, fromApp, fromAppVia = "inline", sendInput) {
|
|
112319
112507
|
const restarting = this.deferredRestarts.get(sessionId);
|
|
112320
112508
|
if (restarting) await restarting;
|
|
112509
|
+
if (this.worktreeCleanup.has(sessionId)) throw Object.assign(new Error("The session worktree is being removed"), { code: "internal" });
|
|
112321
112510
|
const m = this.meta.get(sessionId);
|
|
112322
112511
|
if (!m) throw Object.assign(new Error(`Session not found: ${sessionId}`), { code: "session_not_found" });
|
|
112323
112512
|
if (!this.handles.has(sessionId)) {
|
|
@@ -112328,6 +112517,7 @@ If you need more, you can read that session's recent conversation with read_sess
|
|
|
112328
112517
|
});
|
|
112329
112518
|
}
|
|
112330
112519
|
}
|
|
112520
|
+
if (this.worktreeCleanup.has(sessionId)) throw Object.assign(new Error("The session worktree is being removed"), { code: "internal" });
|
|
112331
112521
|
const h = this.requireHandle(sessionId);
|
|
112332
112522
|
this.closeStream(sessionId);
|
|
112333
112523
|
const seq = this.store.nextSeq(sessionId);
|
|
@@ -112382,6 +112572,7 @@ ${attachments.map((a) => `@${a.path}`).join("\n")}` : text2;
|
|
|
112382
112572
|
* untouched, and only the tool underneath it is swapped.
|
|
112383
112573
|
*/
|
|
112384
112574
|
async switchTool(sessionId, tool) {
|
|
112575
|
+
if (this.worktreeCleanup.has(sessionId)) throw Object.assign(new Error("The session worktree is being removed"), { code: "internal" });
|
|
112385
112576
|
const m = this.meta.get(sessionId);
|
|
112386
112577
|
if (!m) throw Object.assign(new Error(`Session not found: ${sessionId}`), { code: "session_not_found" });
|
|
112387
112578
|
if (m.tool === tool) return { ...m, live: this.handles.has(sessionId) };
|
|
@@ -112663,7 +112854,7 @@ ${attachments.map((a) => `@${a.path}`).join("\n")}` : text2;
|
|
|
112663
112854
|
worktreePathFor(projectId, sessionId) {
|
|
112664
112855
|
if (!isProjectId(projectId)) throw Object.assign(new Error(`Not a project id: ${projectId}`), { code: "internal" });
|
|
112665
112856
|
if (!isSessionId(sessionId)) throw Object.assign(new Error(`Not a session id: ${sessionId}`), { code: "internal" });
|
|
112666
|
-
return
|
|
112857
|
+
return join29(this.worktreeRoot, projectId, sessionId);
|
|
112667
112858
|
}
|
|
112668
112859
|
/**
|
|
112669
112860
|
* Sets up a new worktree's workspace (#69): copy configured files, reuse JS dependencies, run setup.
|
|
@@ -112846,7 +113037,7 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
112846
113037
|
const base2 = this.cwdFor(m);
|
|
112847
113038
|
const paths = /* @__PURE__ */ new Set();
|
|
112848
113039
|
for (const p of e.paths) {
|
|
112849
|
-
const rel = relative8(root3,
|
|
113040
|
+
const rel = relative8(root3, resolve11(base2, p));
|
|
112850
113041
|
if (!rel || rel === ".." || rel.startsWith(`..${sep11}`) || isAbsolute9(rel)) continue;
|
|
112851
113042
|
paths.add(rel.replaceAll(sep11, "/"));
|
|
112852
113043
|
}
|
|
@@ -113046,6 +113237,7 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
113046
113237
|
* reports background work.
|
|
113047
113238
|
*/
|
|
113048
113239
|
sessionIdle(sessionId) {
|
|
113240
|
+
if (this.worktreeCleanup.has(sessionId)) return { idle: false, reason: "turn" };
|
|
113049
113241
|
const m = this.meta.get(sessionId);
|
|
113050
113242
|
if (!m) throw Object.assign(new Error(`Session not found: ${sessionId}`), { code: "session_not_found" });
|
|
113051
113243
|
if (this.agentRuns.get(sessionId)?.waitingForInputs) return { idle: false, reason: "turn" };
|
|
@@ -113056,7 +113248,7 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
113056
113248
|
}
|
|
113057
113249
|
/**
|
|
113058
113250
|
* 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
|
|
113251
|
+
* `start_agent`, or a session handling its ask_project task, while the turn it gave them runs, and a session that owes
|
|
113060
113252
|
* it a report (`reportBack`, `awaitingReport`). Their answer is about to come back to it.
|
|
113061
113253
|
*/
|
|
113062
113254
|
waitsOn(sessionId) {
|
|
@@ -113099,9 +113291,10 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
113099
113291
|
const handle = this.requireHandle(sessionId);
|
|
113100
113292
|
const run = this.agentRuns.get(sessionId);
|
|
113101
113293
|
if (run) run.stoppedByPerson = true;
|
|
113294
|
+
for (const observer of this.turnObservers.get(sessionId) ?? []) observer.stoppedByPerson = true;
|
|
113102
113295
|
handle.interrupt();
|
|
113103
113296
|
for (const d2 of this.delegations.values()) {
|
|
113104
|
-
if (d2.callerId === sessionId && (
|
|
113297
|
+
if (d2.callerId === sessionId && (d2.waiters ?? 0) > 0) d2.stop("the person stopped the session that asked");
|
|
113105
113298
|
}
|
|
113106
113299
|
}
|
|
113107
113300
|
/**
|
|
@@ -113110,6 +113303,7 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
113110
113303
|
* process.
|
|
113111
113304
|
*/
|
|
113112
113305
|
async restartSession(sessionId, cause2) {
|
|
113306
|
+
if (this.worktreeCleanup.has(sessionId)) throw Object.assign(new Error("The session worktree is being removed"), { code: "internal" });
|
|
113113
113307
|
if (cause2) {
|
|
113114
113308
|
this.restartReasons.set(sessionId, cause2);
|
|
113115
113309
|
if (cause2 === "launch_settings") console.error(`[sessions] restarting ${sessionId}: launch settings changed or previously unrecorded`);
|
|
@@ -113717,10 +113911,11 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
113717
113911
|
kind: "project_access",
|
|
113718
113912
|
access: access2,
|
|
113719
113913
|
from: { id: from.id, name: from.name },
|
|
113720
|
-
// Said on the card (#382):
|
|
113914
|
+
// Said on the card (#382): the selected session there follows its trust, so the person should know before allowing
|
|
113721
113915
|
to: { id: to.id, name: to.name, trusted: to.trusted },
|
|
113722
113916
|
text: what.text,
|
|
113723
|
-
...what.app ? { app: what.app } : {}
|
|
113917
|
+
...what.app ? { app: what.app } : {},
|
|
113918
|
+
...what.session ? { session: what.session } : {}
|
|
113724
113919
|
},
|
|
113725
113920
|
signal
|
|
113726
113921
|
);
|
|
@@ -113735,20 +113930,26 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
113735
113930
|
}
|
|
113736
113931
|
return { ok: false, error: `The question to the person was withdrawn before they answered, so ${to.name} was not reached.` };
|
|
113737
113932
|
}
|
|
113738
|
-
/**
|
|
113739
|
-
|
|
113740
|
-
|
|
113741
|
-
|
|
113933
|
+
/** The caller's own preset, read at the boundary, never its project's or parent's preset. */
|
|
113934
|
+
autoBoundaryOpen(sessionId, boundary) {
|
|
113935
|
+
if (this.meta.get(sessionId)?.permissionPreset !== "auto") return false;
|
|
113936
|
+
if (boundary) console.error(`[permissions] auto session ${sessionId}: ${boundary} allowed`);
|
|
113937
|
+
return true;
|
|
113938
|
+
}
|
|
113939
|
+
/** Auto allows and logs; other presets raise a host card, withdrawn if the signal fires. */
|
|
113742
113940
|
askHost(sessionId, detail, signal) {
|
|
113743
|
-
return
|
|
113744
|
-
|
|
113745
|
-
|
|
113746
|
-
|
|
113941
|
+
if (signal?.aborted) return Promise.resolve(null);
|
|
113942
|
+
const note = detail.kind === "project_access" && !detail.to.trusted ? " (target not trusted)" : "";
|
|
113943
|
+
if (this.autoBoundaryOpen(sessionId, `${detail.kind}${note}`)) return Promise.resolve("allow");
|
|
113944
|
+
return new Promise((resolve12) => {
|
|
113945
|
+
if (signal?.aborted) return resolve12(null);
|
|
113946
|
+
const requestId = `${detail.kind === "project_access" ? "xp" : detail.kind === "agent_start" ? "ag" : detail.kind === "centralu_update" ? "up" : "mk"}-${randomUUID10()}`;
|
|
113947
|
+
this.hostAsks.set(requestId, { requestId, sessionId, detail, shown: false, resolve: resolve12 });
|
|
113747
113948
|
signal?.addEventListener("abort", () => {
|
|
113748
113949
|
if (!this.hostAsks.delete(requestId)) return;
|
|
113749
113950
|
const m = this.meta.get(sessionId);
|
|
113750
113951
|
if (m?.pendingApproval?.requestId === requestId) this.onEvent({ type: "approval_resolved", sessionId, requestId, decision: "deny" });
|
|
113751
|
-
|
|
113952
|
+
resolve12(null);
|
|
113752
113953
|
}, { once: true });
|
|
113753
113954
|
this.raiseHostAsks(sessionId);
|
|
113754
113955
|
});
|
|
@@ -113780,25 +113981,24 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
113780
113981
|
* and a session that was itself asked by another project cannot ask a third: depth one, so two projects
|
|
113781
113982
|
* that consented to each other cannot bounce a task between them without a person.
|
|
113782
113983
|
* 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`.
|
|
113984
|
+
* 3. An existing ordinary session in the target, named by name or id; no creation or ownership change.
|
|
113985
|
+
* Without a name exactly one is selected, otherwise its sessions are listed for the caller to choose.
|
|
113786
113986
|
* 4. The task goes in a frame (`askFrame`) marked as sent by the caller, and the call waits for the turn, up to
|
|
113787
113987
|
* `ASK_WAIT_MS`. Past it the answer is "still working" and a call with no task waits again.
|
|
113788
113988
|
* 5. The final answer comes back cut to size, and the paths it names inside the target project become readable
|
|
113789
113989
|
* to the caller (`readableGrants`).
|
|
113790
113990
|
*
|
|
113791
113991
|
* Stop on the caller, the call's own signal, or the person deleting either session ends the wait; the first two
|
|
113792
|
-
* stop the
|
|
113992
|
+
* stop an assigned idle target only while waiting; a task that joined the person's turn only ends the wait.
|
|
113793
113993
|
*/
|
|
113794
113994
|
async askProject(callerId, opts, signal) {
|
|
113795
113995
|
const caller = this.meta.get(callerId);
|
|
113796
113996
|
if (!caller?.projectId) return { ok: false, error: "This session belongs to no project, so it cannot ask another one" };
|
|
113797
|
-
if (caller.askedBy) {
|
|
113997
|
+
if ((caller.askedBy || [...this.delegations.values()].some((d8) => d8.sessionId === callerId && !d8.agent && !d8.settled)) && !this.autoBoundaryOpen(callerId, "delegation depth")) {
|
|
113798
113998
|
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
113999
|
}
|
|
113800
114000
|
const projects = this.store.listProjects();
|
|
113801
|
-
if (projects.find((p) => p.id === caller.projectId)?.trusted !== true) {
|
|
114001
|
+
if (projects.find((p) => p.id === caller.projectId)?.trusted !== true && !this.autoBoundaryOpen(callerId, "delegate from an untrusted project")) {
|
|
113802
114002
|
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
114003
|
}
|
|
113804
114004
|
const wanted = opts.project.trim();
|
|
@@ -113811,62 +114011,88 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
113811
114011
|
};
|
|
113812
114012
|
}
|
|
113813
114013
|
if (target.id === caller.projectId) return { ok: false, error: `${target.name} is this session's own project \u2014 do the work here.` };
|
|
113814
|
-
const
|
|
114014
|
+
const candidates = [...this.meta.values()].filter(
|
|
114015
|
+
(s) => s.projectId === target.id && s.kind === "worker" && !s.appId && !this.builderRefOf(s)
|
|
114016
|
+
);
|
|
114017
|
+
const list = () => candidates.map((s) => JSON.stringify({
|
|
114018
|
+
id: s.id,
|
|
114019
|
+
name: s.name,
|
|
114020
|
+
state: s.state,
|
|
114021
|
+
lastActive: lastActiveOf(this.store, s.id) ?? null
|
|
114022
|
+
})).join("\n");
|
|
114023
|
+
const wantedSession = opts.session?.trim();
|
|
114024
|
+
const outstanding = [...this.delegations.entries()].filter(
|
|
114025
|
+
([, d8]) => d8.callerId === callerId && d8.target.id === target.id && !d8.agent
|
|
114026
|
+
);
|
|
114027
|
+
if (!opts.task && !wantedSession && outstanding.length === 1) {
|
|
114028
|
+
const [key2, pending2] = outstanding[0];
|
|
114029
|
+
return this.awaitDelegation(key2, pending2, signal);
|
|
114030
|
+
}
|
|
114031
|
+
const named = wantedSession ? candidates.filter((s) => s.name.toLowerCase() === wantedSession.toLowerCase()) : candidates;
|
|
114032
|
+
const chosen = candidates.find((s) => s.id === wantedSession) ?? (named.length === 1 ? named[0] : void 0);
|
|
114033
|
+
if (!chosen) {
|
|
114034
|
+
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):
|
|
114035
|
+
${list()}` : `${target.name} has no ordinary session. The person has to open one there; this call cannot create one.` };
|
|
114036
|
+
}
|
|
114037
|
+
const key = `${callerId}>${target.id}>${chosen.id}`;
|
|
113815
114038
|
const pending = this.delegations.get(key);
|
|
113816
114039
|
if (pending && !opts.task) return this.awaitDelegation(key, pending, signal);
|
|
113817
114040
|
if (pending && !pending.settled) {
|
|
113818
|
-
return {
|
|
113819
|
-
|
|
113820
|
-
|
|
113821
|
-
|
|
113822
|
-
|
|
113823
|
-
|
|
113824
|
-
|
|
113825
|
-
|
|
114041
|
+
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.` };
|
|
114042
|
+
}
|
|
114043
|
+
if (!opts.task) return { ok: false, error: `Give a task: what ${target.name} / ${chosen.name} should do.` };
|
|
114044
|
+
const available = () => {
|
|
114045
|
+
const current = this.meta.get(chosen.id);
|
|
114046
|
+
if (!current || current.projectId !== target.id || current.kind !== "worker" || current.appId || this.builderRefOf(current)) return "The chosen session is no longer available";
|
|
114047
|
+
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.`;
|
|
114048
|
+
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.`;
|
|
114049
|
+
};
|
|
114050
|
+
const busy = available();
|
|
114051
|
+
if (busy) return { ok: false, error: busy };
|
|
114052
|
+
const consent = await this.ensureProjectAccess(callerId, target.id, "delegate", {
|
|
114053
|
+
text: taskLine(opts.task),
|
|
114054
|
+
session: { id: chosen.id, name: chosen.name }
|
|
114055
|
+
}, signal);
|
|
113826
114056
|
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);
|
|
114057
|
+
const changed = available();
|
|
114058
|
+
if (changed) return { ok: false, error: changed };
|
|
114059
|
+
if (signal?.aborted) return { ok: false, error: "The call was cancelled before delivery" };
|
|
114060
|
+
if (pending) this.delegations.delete(key);
|
|
114061
|
+
const delegatedId = chosen.id;
|
|
114062
|
+
const midTurn = inTurn(this.meta.get(delegatedId).state);
|
|
114063
|
+
let releaseInput;
|
|
114064
|
+
const d2 = this.startDelegation(key, callerId, delegatedId, target, false, midTurn);
|
|
113858
114065
|
const callerProject = projects.find((p) => p.id === caller.projectId)?.name ?? "another project";
|
|
113859
114066
|
try {
|
|
113860
|
-
|
|
114067
|
+
const from = { sessionId: callerId, name: labelOf(this.store, caller) };
|
|
114068
|
+
if (midTurn) {
|
|
114069
|
+
const releaseHold = d2.wait.holdInput();
|
|
114070
|
+
const release = () => {
|
|
114071
|
+
releaseHold();
|
|
114072
|
+
this.applyDeferredRestart(delegatedId);
|
|
114073
|
+
};
|
|
114074
|
+
releaseInput = release;
|
|
114075
|
+
await this.deliver(delegatedId, askFrame(callerProject, opts.task), void 0, from, false, void 0, "inline", (h, input) => {
|
|
114076
|
+
const fail4 = (e) => {
|
|
114077
|
+
d2.wait.fail(e);
|
|
114078
|
+
release();
|
|
114079
|
+
};
|
|
114080
|
+
void h.sendTracked(input, { complete: release, fail: fail4 }).then(release, fail4);
|
|
114081
|
+
});
|
|
114082
|
+
} else {
|
|
114083
|
+
await this.deliver(delegatedId, askFrame(callerProject, opts.task), void 0, from, false);
|
|
114084
|
+
}
|
|
113861
114085
|
} catch (e) {
|
|
113862
|
-
|
|
114086
|
+
d2.wait.fail(e);
|
|
114087
|
+
releaseInput?.();
|
|
114088
|
+
if (this.delegations.get(key) === d2) this.delegations.delete(key);
|
|
113863
114089
|
if (this.agentRuns.get(delegatedId) === d2.wait) this.agentRuns.delete(delegatedId);
|
|
113864
114090
|
return { ok: false, error: `Could not hand the task to ${target.name}: ${e.message}` };
|
|
113865
114091
|
}
|
|
113866
114092
|
return this.awaitDelegation(key, d2, signal);
|
|
113867
114093
|
}
|
|
113868
114094
|
/** 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) {
|
|
114095
|
+
startDelegation(key, callerId, sessionId, target, agent = false, joinedTurn = false) {
|
|
113870
114096
|
let notice;
|
|
113871
114097
|
let answerAfter = this.meta.get(sessionId)?.lastSeq ?? 0;
|
|
113872
114098
|
const wait = new AgentRunWait((message) => notice = message, () => this.meta.get(sessionId)?.name ?? "the session", void 0, () => {
|
|
@@ -113874,7 +114100,13 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
113874
114100
|
answerAfter = this.meta.get(sessionId)?.lastSeq ?? answerAfter;
|
|
113875
114101
|
return answer;
|
|
113876
114102
|
});
|
|
114103
|
+
let cancelWait;
|
|
114104
|
+
const waitCancelled = new Promise((resolve12) => {
|
|
114105
|
+
cancelWait = resolve12;
|
|
114106
|
+
});
|
|
113877
114107
|
const d2 = {
|
|
114108
|
+
joinedTurn,
|
|
114109
|
+
waitCancelled,
|
|
113878
114110
|
callerId,
|
|
113879
114111
|
sessionId,
|
|
113880
114112
|
target,
|
|
@@ -113887,8 +114119,12 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
113887
114119
|
(r) => ({ ok: true, answer: r.answer }),
|
|
113888
114120
|
(e) => ({ ok: false, error: e.message })
|
|
113889
114121
|
),
|
|
113890
|
-
stop: (why) => {
|
|
114122
|
+
stop: (why, targetStop = false) => {
|
|
113891
114123
|
if (d2.settled) return;
|
|
114124
|
+
if (joinedTurn && !targetStop) {
|
|
114125
|
+
cancelWait({ ok: false, error: `${why}; the caller stopped waiting and the target session was left running` });
|
|
114126
|
+
return;
|
|
114127
|
+
}
|
|
113892
114128
|
wait.fail(new Error(`${why}, so the delegated turn was stopped`));
|
|
113893
114129
|
try {
|
|
113894
114130
|
this.handles.get(sessionId)?.interrupt();
|
|
@@ -113899,31 +114135,79 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
113899
114135
|
};
|
|
113900
114136
|
void d2.outcome.then((r) => {
|
|
113901
114137
|
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);
|
|
114138
|
+
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
114139
|
});
|
|
113904
114140
|
this.agentRuns.set(sessionId, wait);
|
|
113905
114141
|
this.delegations.set(key, d2);
|
|
113906
114142
|
return d2;
|
|
113907
114143
|
}
|
|
114144
|
+
/** Read an existing turn without claiming it in delegations, agentRuns or the caller's sleep graph. */
|
|
114145
|
+
observeTurn(child, subscribe) {
|
|
114146
|
+
const active = this.agentRuns.get(child.id);
|
|
114147
|
+
const shared = active && !active.finished ? active : void 0;
|
|
114148
|
+
const wait = shared ?? new AgentRunWait(
|
|
114149
|
+
() => {
|
|
114150
|
+
},
|
|
114151
|
+
() => child.name,
|
|
114152
|
+
void 0,
|
|
114153
|
+
() => finalAnswer(this.store.loadMessages(child.id, 50))
|
|
114154
|
+
);
|
|
114155
|
+
const project2 = this.store.listProjects().find((p) => p.id === child.projectId);
|
|
114156
|
+
const target = { id: child.projectId ?? "", name: project2?.name ?? "", path: this.store.sessionCwd(child.id) ?? project2?.path ?? "" };
|
|
114157
|
+
if (!shared && subscribe) {
|
|
114158
|
+
const readers = this.turnObservers.get(child.id) ?? /* @__PURE__ */ new Set();
|
|
114159
|
+
readers.add(wait);
|
|
114160
|
+
this.turnObservers.set(child.id, readers);
|
|
114161
|
+
}
|
|
114162
|
+
return {
|
|
114163
|
+
run: {
|
|
114164
|
+
// This is the target's own turn, never work owned by its observer.
|
|
114165
|
+
callerId: child.id,
|
|
114166
|
+
sessionId: child.id,
|
|
114167
|
+
target,
|
|
114168
|
+
wait,
|
|
114169
|
+
get settled() {
|
|
114170
|
+
return wait.finished;
|
|
114171
|
+
},
|
|
114172
|
+
notice: () => void 0,
|
|
114173
|
+
outcome: wait.done.then(
|
|
114174
|
+
(r) => ({ ok: true, answer: r.answer }),
|
|
114175
|
+
(e) => ({ ok: false, error: e.message })
|
|
114176
|
+
),
|
|
114177
|
+
stop: (why) => {
|
|
114178
|
+
if (wait.finished) return;
|
|
114179
|
+
wait.fail(new Error(`${why}, so the turn was stopped`));
|
|
114180
|
+
this.handles.get(child.id)?.interrupt();
|
|
114181
|
+
}
|
|
114182
|
+
},
|
|
114183
|
+
release: () => {
|
|
114184
|
+
if (shared) return;
|
|
114185
|
+
const readers = this.turnObservers.get(child.id);
|
|
114186
|
+
readers?.delete(wait);
|
|
114187
|
+
if (readers?.size === 0) this.turnObservers.delete(child.id);
|
|
114188
|
+
}
|
|
114189
|
+
};
|
|
114190
|
+
}
|
|
113908
114191
|
/** Waits for a delegation up to the bound, and turns its outcome into what the caller reads */
|
|
113909
114192
|
async awaitDelegation(key, d2, signal) {
|
|
113910
114193
|
const sessionName = () => this.meta.get(d2.sessionId)?.name ?? d2.sessionId;
|
|
113911
114194
|
let timer;
|
|
113912
114195
|
let onAbort;
|
|
113913
|
-
const bound = new Promise((
|
|
113914
|
-
const aborted2 = new Promise((
|
|
114196
|
+
const bound = new Promise((resolve12) => timer = setTimeout(() => resolve12("bound"), ASK_WAIT_MS));
|
|
114197
|
+
const aborted2 = new Promise((resolve12) => {
|
|
113915
114198
|
if (!signal) return;
|
|
113916
|
-
onAbort = () =>
|
|
114199
|
+
onAbort = () => resolve12("aborted");
|
|
113917
114200
|
if (signal.aborted) onAbort();
|
|
113918
114201
|
else signal.addEventListener("abort", onAbort, { once: true });
|
|
113919
114202
|
});
|
|
114203
|
+
d2.waiters = (d2.waiters ?? 0) + 1;
|
|
113920
114204
|
try {
|
|
113921
|
-
const r = await Promise.race([d2.outcome, bound, aborted2]);
|
|
114205
|
+
const r = await Promise.race([d2.outcome, ...d2.waitCancelled ? [d2.waitCancelled] : [], bound, aborted2]);
|
|
113922
114206
|
if (r === "aborted") {
|
|
113923
114207
|
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.` };
|
|
114208
|
+
if (!d2.joinedTurn) await d2.outcome;
|
|
114209
|
+
if (this.delegations.get(key) === d2) this.delegations.delete(key);
|
|
114210
|
+
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
114211
|
}
|
|
113928
114212
|
if (r === "bound") {
|
|
113929
114213
|
const notice = d2.notice();
|
|
@@ -113933,7 +114217,7 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
113933
114217
|
if (!r.ok) {
|
|
113934
114218
|
return {
|
|
113935
114219
|
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.`
|
|
114220
|
+
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
114221
|
};
|
|
113938
114222
|
}
|
|
113939
114223
|
const answer = r.answer ?? finalAnswer(this.store.loadMessages(d2.sessionId, 50));
|
|
@@ -113954,6 +114238,7 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
113954
114238
|
outside
|
|
113955
114239
|
};
|
|
113956
114240
|
} finally {
|
|
114241
|
+
d2.waiters = (d2.waiters ?? 1) - 1;
|
|
113957
114242
|
clearTimeout(timer);
|
|
113958
114243
|
if (onAbort) signal?.removeEventListener("abort", onAbort);
|
|
113959
114244
|
}
|
|
@@ -113975,19 +114260,19 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
113975
114260
|
async startAgent(callerId, opts, signal) {
|
|
113976
114261
|
const caller = this.meta.get(callerId);
|
|
113977
114262
|
if (!caller?.projectId) return { ok: false, error: "This session belongs to no project, so it cannot start agents." };
|
|
113978
|
-
if (caller.askedBy) {
|
|
114263
|
+
if ((caller.askedBy || [...this.delegations.values()].some((d8) => d8.sessionId === callerId && !d8.agent && !d8.settled)) && !this.autoBoundaryOpen(callerId, "delegation depth")) {
|
|
113979
114264
|
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
114265
|
}
|
|
113981
114266
|
const project2 = this.store.listProjects().find((p) => p.id === caller.projectId);
|
|
113982
114267
|
if (!project2) return { ok: false, error: "This session's project is no longer registered." };
|
|
113983
|
-
if (project2.trusted !== true) {
|
|
114268
|
+
if (project2.trusted !== true && !this.autoBoundaryOpen(callerId, "start in an untrusted project")) {
|
|
113984
114269
|
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
114270
|
}
|
|
113986
114271
|
const tool = opts.tool ?? this.firstTool(project2.defaultTool);
|
|
113987
114272
|
if (!tool) return { ok: false, error: "No agent adapter is available. Install and configure an agent CLI before starting an agent." };
|
|
113988
114273
|
const adapter = this.adapters.get(tool);
|
|
113989
114274
|
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);
|
|
114275
|
+
const full = this.agentCeilingError(callerId, project2.id);
|
|
113991
114276
|
if (full) return { ok: false, error: full };
|
|
113992
114277
|
const remembered = this.store.projectToolDefaults(project2.id)[tool];
|
|
113993
114278
|
const model = opts.model ?? remembered?.model ?? void 0;
|
|
@@ -114012,7 +114297,7 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
114012
114297
|
signal
|
|
114013
114298
|
);
|
|
114014
114299
|
if (!consent.ok) return consent;
|
|
114015
|
-
const fullNow = this.agentCeilingError(project2.id);
|
|
114300
|
+
const fullNow = this.agentCeilingError(callerId, project2.id);
|
|
114016
114301
|
if (fullNow) return { ok: false, error: fullNow };
|
|
114017
114302
|
const cwd = worktree ? project2.path : this.store.sessionCwd(callerId) ?? caller.worktree?.path ?? project2.path;
|
|
114018
114303
|
let child;
|
|
@@ -114050,95 +114335,115 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
114050
114335
|
return result.ok && notice ? { ...result, notice: [result.notice, notice].filter(Boolean).join("; ") } : result;
|
|
114051
114336
|
}
|
|
114052
114337
|
/**
|
|
114053
|
-
* agent_session (#561):
|
|
114054
|
-
* caller, in the caller's project.
|
|
114338
|
+
* agent_session (#561): safe/normal work only with their own children; auto opens ownership within the project.
|
|
114055
114339
|
*
|
|
114056
114340
|
* status where it stands, without waiting (the answer, once the turn the caller gave it has ended)
|
|
114057
114341
|
* wait waits on that turn up to the bound, as start_agent does
|
|
114058
114342
|
* send gives it more, including during the caller's turn on adapters that accept mid-turn input; refused
|
|
114059
114343
|
* while it works on the person's own message or has a higher preset than the caller
|
|
114060
114344
|
* stop stops the turn the caller gave it; a turn the person started there is theirs
|
|
114061
|
-
* delete
|
|
114345
|
+
* delete sets aside local work in the trash, then removes an idle child's worktree and branch (#608)
|
|
114062
114346
|
*/
|
|
114063
114347
|
async agentSession(callerId, opts, signal) {
|
|
114348
|
+
if (opts.sessionId === callerId && opts.action !== "status") {
|
|
114349
|
+
return { ok: false, error: "agent_session cannot wait on, stop, delete or send to its own calling session." };
|
|
114350
|
+
}
|
|
114064
114351
|
const caller = this.meta.get(callerId);
|
|
114065
114352
|
const child = this.meta.get(opts.sessionId);
|
|
114066
|
-
if (!caller?.projectId || !child || child.askedBy !== callerId || child.projectId !== caller.projectId) {
|
|
114353
|
+
if (!caller?.projectId || !child || child.askedBy !== callerId && !this.autoBoundaryOpen(callerId, "session ownership") || child.projectId !== caller.projectId) {
|
|
114067
114354
|
return { ok: false, error: `${opts.sessionId} is not a session this session started. agent_session works only on sessions start_agent started here.` };
|
|
114068
114355
|
}
|
|
114356
|
+
if (opts.sessionId === callerId) return this.agentAtRest(child);
|
|
114069
114357
|
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
|
-
|
|
114358
|
+
let d2 = this.delegations.get(key);
|
|
114359
|
+
let releaseObservation;
|
|
114360
|
+
if (!d2 && this.autoBoundaryOpen(callerId)) {
|
|
114361
|
+
const existing = [...this.delegations.values()].find((run) => run.sessionId === child.id && !run.settled);
|
|
114362
|
+
if (existing) d2 = existing;
|
|
114363
|
+
else if (opts.action !== "delete" && (child.state === "working" || child.state === "waiting_approval")) {
|
|
114364
|
+
const observation = this.observeTurn(child, opts.action !== "status");
|
|
114365
|
+
d2 = observation.run;
|
|
114366
|
+
releaseObservation = observation.release;
|
|
114367
|
+
}
|
|
114368
|
+
}
|
|
114369
|
+
const observing = !!d2 && d2.callerId !== callerId;
|
|
114370
|
+
try {
|
|
114371
|
+
switch (opts.action) {
|
|
114372
|
+
case "status":
|
|
114373
|
+
if (d2 && !d2.settled) return this.agentWorking(d2, false, observing);
|
|
114374
|
+
return d2 ? await this.awaitAgent(key, d2, signal, observing) : this.agentAtRest(child);
|
|
114375
|
+
case "wait":
|
|
114376
|
+
return d2 ? await this.awaitAgent(key, d2, signal, observing) : this.agentAtRest(child);
|
|
114377
|
+
case "stop": {
|
|
114378
|
+
if (!d2 || d2.settled) return { ok: false, error: `Nothing you gave "${child.name}" is running.` };
|
|
114379
|
+
if (!observing && this.awaitingReport.get(child.id) === callerId) this.awaitingReport.delete(child.id);
|
|
114380
|
+
d2.stop("the session that started it stopped it", observing);
|
|
114381
|
+
await d2.outcome;
|
|
114382
|
+
if (!observing) this.delegations.delete(key);
|
|
114383
|
+
return { ok: true, state: "stopped", sessionId: child.id, sessionName: child.name, tool: child.tool };
|
|
114384
|
+
}
|
|
114385
|
+
case "delete": {
|
|
114386
|
+
if (d2 && !d2.settled) return { ok: false, error: `"${child.name}" is still working on what you gave it \u2014 stop it or wait first.` };
|
|
114387
|
+
const r = await this.cleanUpWorktreeChild(child);
|
|
114388
|
+
if (!r.ok) return r;
|
|
114389
|
+
if (d2) this.delegations.delete(key);
|
|
114390
|
+
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
114391
|
}
|
|
114099
|
-
|
|
114100
|
-
|
|
114101
|
-
|
|
114102
|
-
|
|
114103
|
-
|
|
114392
|
+
case "send": {
|
|
114393
|
+
if (!presetWithin(child.permissionPreset, caller.permissionPreset)) {
|
|
114394
|
+
return {
|
|
114395
|
+
ok: false,
|
|
114396
|
+
error: `"${child.name}" now has more permissions (${child.permissionPreset}) than this session (${caller.permissionPreset}), so this session cannot give it work. Tell the person.`
|
|
114397
|
+
};
|
|
114104
114398
|
}
|
|
114105
|
-
const
|
|
114106
|
-
|
|
114107
|
-
|
|
114108
|
-
this.
|
|
114109
|
-
|
|
114399
|
+
const from = { sessionId: callerId, name: labelOf(this.store, caller) };
|
|
114400
|
+
if (d2 && !d2.settled) {
|
|
114401
|
+
const text2 = agentNoteFrame(from.name, opts.text ?? "");
|
|
114402
|
+
if (!this.adapters.get(child.tool)?.capabilities.midTurnInput || !this.handles.get(child.id)?.sendTracked) {
|
|
114403
|
+
return { ok: false, error: `"${child.name}" cannot take messages while working. Wait for it (action "wait") or stop it first.` };
|
|
114404
|
+
}
|
|
114405
|
+
const releaseHold = d2.wait.holdInput();
|
|
114406
|
+
const release = () => {
|
|
114407
|
+
releaseHold();
|
|
114408
|
+
this.applyDeferredRestart(child.id);
|
|
114409
|
+
};
|
|
114410
|
+
try {
|
|
114411
|
+
await this.deliver(child.id, text2, void 0, from, false, void 0, "inline", (h, input) => {
|
|
114412
|
+
const fail4 = (e) => {
|
|
114413
|
+
d2.wait.fail(e);
|
|
114414
|
+
release();
|
|
114415
|
+
};
|
|
114416
|
+
void h.sendTracked(input, { complete: release, fail: fail4 }).then(release, fail4);
|
|
114417
|
+
});
|
|
114418
|
+
} catch (e) {
|
|
114419
|
+
release();
|
|
114420
|
+
return { ok: false, error: `Could not send it: ${e.message}` };
|
|
114421
|
+
}
|
|
114422
|
+
return this.agentWorking(d2, false, observing);
|
|
114423
|
+
}
|
|
114424
|
+
if (child.state === "working" || child.state === "waiting_approval" || this.agentRuns.has(child.id) && !this.agentRuns.get(child.id).finished) {
|
|
114425
|
+
return { ok: false, error: `"${child.name}" is busy with something else (the person may be using it). Try again later.` };
|
|
114426
|
+
}
|
|
114427
|
+
if (d2) this.delegations.delete(key);
|
|
114428
|
+
const full = this.agentCeilingError(callerId, child.projectId ?? "");
|
|
114429
|
+
if (full) return { ok: false, error: full };
|
|
114430
|
+
const project2 = this.store.listProjects().find((p) => p.id === child.projectId);
|
|
114431
|
+
const cwd = this.store.sessionCwd(child.id) ?? child.worktree?.path ?? project2?.path ?? "";
|
|
114432
|
+
const next = this.startDelegation(key, callerId, child.id, { id: child.projectId ?? "", name: project2?.name ?? "", path: cwd }, true);
|
|
114110
114433
|
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
|
-
});
|
|
114434
|
+
await this.deliver(child.id, agentFrame(from.name, opts.text ?? ""), void 0, from, false);
|
|
114118
114435
|
} catch (e) {
|
|
114119
|
-
|
|
114436
|
+
this.delegations.delete(key);
|
|
114437
|
+
if (this.agentRuns.get(child.id) === next.wait) this.agentRuns.delete(child.id);
|
|
114120
114438
|
return { ok: false, error: `Could not send it: ${e.message}` };
|
|
114121
114439
|
}
|
|
114122
|
-
return this.
|
|
114123
|
-
}
|
|
114124
|
-
if (child.state === "working" || child.state === "waiting_approval" || this.agentRuns.has(child.id) && !this.agentRuns.get(child.id).finished) {
|
|
114125
|
-
return { ok: false, error: `"${child.name}" is busy with something else (the person may be using it). Try again later.` };
|
|
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}` };
|
|
114440
|
+
return this.awaitAgent(key, next, signal);
|
|
114139
114441
|
}
|
|
114140
|
-
|
|
114442
|
+
default:
|
|
114443
|
+
return { ok: false, error: "Unknown agent_session action." };
|
|
114141
114444
|
}
|
|
114445
|
+
} finally {
|
|
114446
|
+
releaseObservation?.();
|
|
114142
114447
|
}
|
|
114143
114448
|
}
|
|
114144
114449
|
/**
|
|
@@ -114151,65 +114456,67 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
114151
114456
|
if (!summary.isRepo) return { error: "This project is not a git repository, so it has no worktrees. Start the agent without one." };
|
|
114152
114457
|
const asked = opts.branch?.trim();
|
|
114153
114458
|
if (asked) return await gitValidBranchName(asked) ? { branch: asked } : { error: `Not a valid branch name: ${asked}` };
|
|
114154
|
-
return { branch: agentBranch(opts.task,
|
|
114459
|
+
return { branch: agentBranch(opts.task, randomUUID10()) };
|
|
114155
114460
|
}
|
|
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
|
-
*/
|
|
114461
|
+
/** Prevent a send/resume from racing the snapshot and worktree removal. */
|
|
114462
|
+
worktreeCleanup = /* @__PURE__ */ new Set();
|
|
114463
|
+
/** #608: the parent decides; local work follows the session into the trash in every preset. */
|
|
114165
114464
|
async cleanUpWorktreeChild(target) {
|
|
114166
114465
|
if (!target.worktree?.base || !target.projectId) {
|
|
114167
114466
|
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
114467
|
}
|
|
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
|
|
114171
|
-
}
|
|
114172
|
-
const cwd = this.cwdOf(target.projectId);
|
|
114173
|
-
const { branch, path: path2, base: base2 } = target.worktree;
|
|
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` };
|
|
114468
|
+
if (target.state === "working" || target.state === "waiting_approval" || this.agentRuns.has(target.id) || sessionBusy({ ...target, live: this.handles.has(target.id) })) {
|
|
114469
|
+
return { ok: false, error: `"${target.name}" is still working \u2014 stop it or wait first` };
|
|
114178
114470
|
}
|
|
114179
|
-
|
|
114180
|
-
|
|
114181
|
-
|
|
114182
|
-
|
|
114183
|
-
|
|
114184
|
-
const
|
|
114185
|
-
|
|
114186
|
-
|
|
114187
|
-
|
|
114188
|
-
|
|
114189
|
-
|
|
114190
|
-
|
|
114191
|
-
|
|
114471
|
+
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` };
|
|
114472
|
+
if (this.worktreeCleanup.has(target.id)) return { ok: false, error: "This worktree is already being removed" };
|
|
114473
|
+
this.worktreeCleanup.add(target.id);
|
|
114474
|
+
const { branch, path: path2 } = target.worktree;
|
|
114475
|
+
try {
|
|
114476
|
+
const cwd = this.cwdOf(target.projectId);
|
|
114477
|
+
const handle = this.handles.get(target.id);
|
|
114478
|
+
if (handle) {
|
|
114479
|
+
await handle.dispose();
|
|
114480
|
+
this.handles.delete(target.id);
|
|
114481
|
+
this.running.delete(target.id);
|
|
114482
|
+
}
|
|
114483
|
+
const trunk = await gitRevParse(cwd, this.trunkOf(target.projectId) ?? "HEAD");
|
|
114484
|
+
const setAside = await saveWorktreeRecovery(target.id, path2, trunk ?? "missing-trunk", branch);
|
|
114485
|
+
if (setAside.changedFiles || setAside.commits) {
|
|
114486
|
+
this.onEvent({
|
|
114487
|
+
type: "notice",
|
|
114488
|
+
sessionId: target.id,
|
|
114489
|
+
level: "info",
|
|
114490
|
+
from: "Centralu",
|
|
114491
|
+
label: "worktree recovery",
|
|
114492
|
+
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.`
|
|
114493
|
+
});
|
|
114192
114494
|
}
|
|
114495
|
+
await this.trashSession(target.id, false, false, setAside);
|
|
114496
|
+
try {
|
|
114497
|
+
await gitWorktreeRemove(cwd, path2, true, true);
|
|
114498
|
+
} catch (e) {
|
|
114499
|
+
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)}.` };
|
|
114500
|
+
}
|
|
114501
|
+
try {
|
|
114502
|
+
await gitBranchDelete(cwd, branch, this.gitTrust(target.projectId));
|
|
114503
|
+
} catch (e) {
|
|
114504
|
+
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)}.` };
|
|
114505
|
+
}
|
|
114506
|
+
return { ok: true, branch, setAside };
|
|
114507
|
+
} catch (e) {
|
|
114508
|
+
return { ok: false, error: `Could not finish removing "${branch}": ${e.message}. Any saved recovery files are at ${worktreeRecoveryDir(target.id)}.` };
|
|
114509
|
+
} finally {
|
|
114510
|
+
this.worktreeCleanup.delete(target.id);
|
|
114193
114511
|
}
|
|
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
114512
|
}
|
|
114209
|
-
/**
|
|
114210
|
-
agentCeilingError(projectId) {
|
|
114513
|
+
/** A saved ceiling overrides the caller's preset default: auto unlimited, otherwise AGENT_CEILING. */
|
|
114514
|
+
agentCeilingError(callerId, projectId) {
|
|
114515
|
+
const configured = this.uiPreferences().agentCeiling;
|
|
114516
|
+
const ceiling = configured ?? (this.autoBoundaryOpen(callerId) ? 0 : AGENT_CEILING);
|
|
114517
|
+
if (ceiling === 0) return null;
|
|
114211
114518
|
const running = [...this.delegations.values()].filter((d2) => d2.agent && !d2.settled && this.meta.get(d2.sessionId)?.projectId === projectId).length;
|
|
114212
|
-
if (running <
|
|
114519
|
+
if (running < ceiling) return null;
|
|
114213
114520
|
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
114521
|
}
|
|
114215
114522
|
/**
|
|
@@ -114269,8 +114576,8 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
114269
114576
|
* "Working": the caller is told when the turn ends (#561), through the report the orchestrator's reportBack uses, unless
|
|
114270
114577
|
* a call is waiting on it by then (`awaitAgent` takes the marker down while it waits)
|
|
114271
114578
|
*/
|
|
114272
|
-
agentWorking(d2, arm = true) {
|
|
114273
|
-
if (arm) this.awaitingReport.set(d2.sessionId, d2.callerId);
|
|
114579
|
+
agentWorking(d2, arm = true, observing = false) {
|
|
114580
|
+
if (arm && !observing) this.awaitingReport.set(d2.sessionId, d2.callerId);
|
|
114274
114581
|
const notice = d2.notice();
|
|
114275
114582
|
return {
|
|
114276
114583
|
ok: true,
|
|
@@ -114278,7 +114585,7 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
114278
114585
|
sessionId: d2.sessionId,
|
|
114279
114586
|
sessionName: this.meta.get(d2.sessionId)?.name ?? d2.sessionId,
|
|
114280
114587
|
tool: this.meta.get(d2.sessionId)?.tool ?? "",
|
|
114281
|
-
reported: this.awaitingReport.get(d2.sessionId) === d2.callerId,
|
|
114588
|
+
reported: !observing && this.awaitingReport.get(d2.sessionId) === d2.callerId,
|
|
114282
114589
|
...notice ? { notice } : {}
|
|
114283
114590
|
};
|
|
114284
114591
|
}
|
|
@@ -114295,32 +114602,43 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
114295
114602
|
};
|
|
114296
114603
|
}
|
|
114297
114604
|
/** 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);
|
|
114605
|
+
async awaitAgent(key, d2, signal, observing = false) {
|
|
114606
|
+
if (!observing && this.awaitingReport.get(d2.sessionId) === d2.callerId) this.awaitingReport.delete(d2.sessionId);
|
|
114300
114607
|
const name = () => this.meta.get(d2.sessionId)?.name ?? d2.sessionId;
|
|
114301
114608
|
let timer;
|
|
114302
114609
|
let onAbort;
|
|
114303
|
-
const bound = new Promise((
|
|
114304
|
-
const aborted2 = new Promise((
|
|
114610
|
+
const bound = new Promise((resolve12) => timer = setTimeout(() => resolve12("bound"), ASK_WAIT_MS));
|
|
114611
|
+
const aborted2 = new Promise((resolve12) => {
|
|
114305
114612
|
if (!signal) return;
|
|
114306
|
-
onAbort = () =>
|
|
114613
|
+
onAbort = () => resolve12("aborted");
|
|
114307
114614
|
if (signal.aborted) onAbort();
|
|
114308
114615
|
else signal.addEventListener("abort", onAbort, { once: true });
|
|
114309
114616
|
});
|
|
114310
|
-
d2.waiters = (d2.waiters ?? 0) + 1;
|
|
114617
|
+
if (!observing) d2.waiters = (d2.waiters ?? 0) + 1;
|
|
114311
114618
|
try {
|
|
114312
114619
|
const r = await Promise.race([d2.outcome, bound, aborted2]);
|
|
114313
|
-
d2.waiters
|
|
114620
|
+
if (!observing) d2.waiters = (d2.waiters ?? 1) - 1;
|
|
114314
114621
|
if (r === "aborted") {
|
|
114622
|
+
if (observing) return { ok: false, error: `The observation was cancelled; "${name()}" was left running.` };
|
|
114315
114623
|
d2.stop("the call that started it was cancelled");
|
|
114316
114624
|
await d2.outcome;
|
|
114317
114625
|
this.delegations.delete(key);
|
|
114318
114626
|
return { ok: false, error: `The call was cancelled, so "${name()}" was stopped.` };
|
|
114319
114627
|
}
|
|
114320
|
-
if (r === "bound") return this.agentWorking(d2);
|
|
114321
|
-
if (this.delegations.get(key) === d2) this.delegations.delete(key);
|
|
114628
|
+
if (r === "bound") return this.agentWorking(d2, !observing, observing);
|
|
114629
|
+
if (!observing && this.delegations.get(key) === d2) this.delegations.delete(key);
|
|
114322
114630
|
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
114631
|
const answer = r.answer ?? finalAnswer(this.store.loadMessages(d2.sessionId, 50));
|
|
114632
|
+
if (observing) return {
|
|
114633
|
+
ok: true,
|
|
114634
|
+
state: "done",
|
|
114635
|
+
sessionId: d2.sessionId,
|
|
114636
|
+
sessionName: name(),
|
|
114637
|
+
tool: this.meta.get(d2.sessionId)?.tool ?? "",
|
|
114638
|
+
answer: clipAnswer(answer, name()),
|
|
114639
|
+
readable: [],
|
|
114640
|
+
outside: []
|
|
114641
|
+
};
|
|
114324
114642
|
const { granted, outside } = readableGrants(pathsIn(answer), d2.target.path);
|
|
114325
114643
|
if (granted.length) {
|
|
114326
114644
|
const set3 = this.readGrants.get(d2.callerId) ?? /* @__PURE__ */ new Set();
|
|
@@ -114343,39 +114661,39 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
114343
114661
|
}
|
|
114344
114662
|
}
|
|
114345
114663
|
askCapability(q2, signal) {
|
|
114346
|
-
return new Promise((
|
|
114347
|
-
if (signal.aborted) return
|
|
114664
|
+
return new Promise((resolve12) => {
|
|
114665
|
+
if (signal.aborted) return resolve12(null);
|
|
114348
114666
|
const app2 = { appId: q2.app.appId, projectId: q2.app.projectId, name: q2.appName };
|
|
114349
114667
|
if (q2.origin.kind === "session" && this.meta.has(q2.origin.sessionId)) {
|
|
114350
|
-
const requestId = `cap-${
|
|
114668
|
+
const requestId = `cap-${randomUUID10()}`;
|
|
114351
114669
|
const ask = {
|
|
114352
114670
|
requestId,
|
|
114353
114671
|
sessionId: q2.origin.sessionId,
|
|
114354
114672
|
detail: { kind: "capability", app: app2, capability: q2.capability, text: q2.text },
|
|
114355
114673
|
shown: false,
|
|
114356
|
-
resolve: (d2) =>
|
|
114674
|
+
resolve: (d2) => resolve12(d2 === "always" || d2 === "always_project" ? "allow" : d2)
|
|
114357
114675
|
};
|
|
114358
114676
|
this.hostAsks.set(requestId, ask);
|
|
114359
114677
|
signal.addEventListener("abort", () => {
|
|
114360
114678
|
if (!this.hostAsks.delete(requestId)) return;
|
|
114361
114679
|
const m = this.meta.get(ask.sessionId);
|
|
114362
114680
|
if (m?.pendingApproval?.requestId === requestId) this.onEvent({ type: "approval_resolved", sessionId: ask.sessionId, requestId, decision: "deny" });
|
|
114363
|
-
|
|
114681
|
+
resolve12(null);
|
|
114364
114682
|
}, { once: true });
|
|
114365
114683
|
this.raiseHostAsks(ask.sessionId);
|
|
114366
114684
|
return;
|
|
114367
114685
|
}
|
|
114368
114686
|
const origin = q2.origin.kind === "view" ? q2.origin.app : q2.app;
|
|
114369
|
-
const id2 = `q-${
|
|
114687
|
+
const id2 = `q-${randomUUID10()}`;
|
|
114370
114688
|
this.appQuestions.set(id2, {
|
|
114371
114689
|
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:
|
|
114690
|
+
resolve: resolve12
|
|
114373
114691
|
});
|
|
114374
114692
|
this.emit({ type: "external_app_questions_changed" });
|
|
114375
114693
|
signal.addEventListener("abort", () => {
|
|
114376
114694
|
if (!this.appQuestions.delete(id2)) return;
|
|
114377
114695
|
this.emit({ type: "external_app_questions_changed" });
|
|
114378
|
-
|
|
114696
|
+
resolve12(null);
|
|
114379
114697
|
}, { once: true });
|
|
114380
114698
|
});
|
|
114381
114699
|
}
|
|
@@ -114684,6 +115002,7 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
114684
115002
|
this.appsHub?.dispose();
|
|
114685
115003
|
this.appsHub = new SessionAppsHub(rt2, {
|
|
114686
115004
|
...opts,
|
|
115005
|
+
boundaryOpen: (id2) => this.autoBoundaryOpen(id2),
|
|
114687
115006
|
onDemand: {
|
|
114688
115007
|
attached: (id2) => this.appAccess?.attached(id2) ?? [],
|
|
114689
115008
|
/*
|
|
@@ -114694,7 +115013,9 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
114694
115013
|
allowed: (session2, app2) => this.toolProfileOf(session2.id) === "reader" && (this.appAccess?.allowed(session2, app2) ?? false)
|
|
114695
115014
|
}
|
|
114696
115015
|
});
|
|
115016
|
+
rt2.setSessionBoundaryPolicy((id2) => this.autoBoundaryOpen(id2));
|
|
114697
115017
|
this.appAccess = new AppAccess({
|
|
115018
|
+
boundaryOpen: (id2, boundary) => this.autoBoundaryOpen(id2, boundary),
|
|
114698
115019
|
hub: this.appsHub,
|
|
114699
115020
|
store: this.store,
|
|
114700
115021
|
session: (id2) => {
|
|
@@ -115048,14 +115369,14 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
115048
115369
|
}
|
|
115049
115370
|
async loadMessages(sessionId, limit, beforeSeq) {
|
|
115050
115371
|
const rows = this.store.loadMessages(sessionId, limit, beforeSeq);
|
|
115051
|
-
const { readFile:
|
|
115372
|
+
const { readFile: readFile6 } = await import("node:fs/promises");
|
|
115052
115373
|
return Promise.all(
|
|
115053
115374
|
rows.map(async (r) => {
|
|
115054
115375
|
if (r.kind === "image") {
|
|
115055
115376
|
const p = r.payload;
|
|
115056
115377
|
if (!p.path || p.note) return r;
|
|
115057
115378
|
try {
|
|
115058
|
-
const data = (await
|
|
115379
|
+
const data = (await readFile6(p.path)).toString("base64");
|
|
115059
115380
|
return { ...r, payload: { ...p, data } };
|
|
115060
115381
|
} catch {
|
|
115061
115382
|
return { ...r, payload: { ...p, note: "The image was cleared and no longer exists (total-size cap)" } };
|
|
@@ -115068,7 +115389,7 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
115068
115389
|
p.attachments.map(async (a) => {
|
|
115069
115390
|
if (a.kind !== "image") return a;
|
|
115070
115391
|
try {
|
|
115071
|
-
return { ...a, data: (await
|
|
115392
|
+
return { ...a, data: (await readFile6(a.path)).toString("base64") };
|
|
115072
115393
|
} catch {
|
|
115073
115394
|
return a;
|
|
115074
115395
|
}
|
|
@@ -115082,6 +115403,7 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
115082
115403
|
}
|
|
115083
115404
|
async disposeAll() {
|
|
115084
115405
|
this.watchers.close();
|
|
115406
|
+
for (const readers of this.turnObservers.values()) for (const observer of readers) observer.fail(new Error("Centralu is stopping"));
|
|
115085
115407
|
this.appsHub?.rt.attachBrokerHost(null);
|
|
115086
115408
|
for (const run of [...this.agentRuns.values()]) run.fail(new Error("Centralu is shutting down"));
|
|
115087
115409
|
for (const ask of [...this.hostAsks.values()]) ask.resolve(null);
|
|
@@ -115112,6 +115434,7 @@ ${String(stderr).slice(0, 2e3)}`
|
|
|
115112
115434
|
this.watchers.close();
|
|
115113
115435
|
this.appsHub?.rt.attachBrokerHost(null);
|
|
115114
115436
|
for (const run of [...this.agentRuns.values()]) run.fail(new Error("Centralu is restarting"));
|
|
115437
|
+
for (const readers of this.turnObservers.values()) for (const observer of readers) observer.fail(new Error("Centralu is restarting"));
|
|
115115
115438
|
for (const ask of [...this.hostAsks.values()]) ask.resolve(null);
|
|
115116
115439
|
this.hostAsks.clear();
|
|
115117
115440
|
for (const q2 of [...this.appQuestions.values()]) q2.resolve(null);
|
|
@@ -115143,8 +115466,8 @@ import { relative as relative9 } from "node:path";
|
|
|
115143
115466
|
// packages/agent-host/src/themes.ts
|
|
115144
115467
|
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
115145
115468
|
import { watch as watch2 } from "node:fs";
|
|
115146
|
-
import { mkdir as
|
|
115147
|
-
import { basename as basename6, join as
|
|
115469
|
+
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";
|
|
115470
|
+
import { basename as basename6, join as join30 } from "node:path";
|
|
115148
115471
|
var MAX_FILE_BYTES = 256 * 1024;
|
|
115149
115472
|
var SETTLE_MS = 100;
|
|
115150
115473
|
var ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,59}$/;
|
|
@@ -115159,7 +115482,7 @@ var ThemeFiles = class {
|
|
|
115159
115482
|
timer = null;
|
|
115160
115483
|
/** Makes the folder, writes the schema next to the themes, and starts watching */
|
|
115161
115484
|
async start() {
|
|
115162
|
-
await
|
|
115485
|
+
await mkdir7(this.dir, { recursive: true });
|
|
115163
115486
|
await this.writeSchema();
|
|
115164
115487
|
try {
|
|
115165
115488
|
this.watcher = watch2(this.dir, (_event, name) => {
|
|
@@ -115189,15 +115512,15 @@ var ThemeFiles = class {
|
|
|
115189
115512
|
* a token updates it, and an unchanged one does not touch the folder (or wake the watcher).
|
|
115190
115513
|
*/
|
|
115191
115514
|
async writeSchema() {
|
|
115192
|
-
const path2 =
|
|
115515
|
+
const path2 = join30(this.dir, THEME_SCHEMA_FILE);
|
|
115193
115516
|
const text2 = `${JSON.stringify(themeFileJsonSchema(), null, 2)}
|
|
115194
115517
|
`;
|
|
115195
|
-
const current = await
|
|
115518
|
+
const current = await readFile5(path2, "utf8").catch(() => null);
|
|
115196
115519
|
if (current !== text2) await this.atomicWrite(path2, text2);
|
|
115197
115520
|
}
|
|
115198
115521
|
/** Every `<id>.json` in the folder, read and checked. A file that cannot be read is listed with the reason. */
|
|
115199
115522
|
async list() {
|
|
115200
|
-
const names = await
|
|
115523
|
+
const names = await readdir9(this.dir).catch(() => []);
|
|
115201
115524
|
const entries = [];
|
|
115202
115525
|
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
115526
|
for (const id2 of ids) entries.push(await this.read(id2));
|
|
@@ -115206,11 +115529,11 @@ var ThemeFiles = class {
|
|
|
115206
115529
|
async read(id2) {
|
|
115207
115530
|
const path2 = this.pathOf(id2);
|
|
115208
115531
|
try {
|
|
115209
|
-
const info = await
|
|
115532
|
+
const info = await stat6(path2);
|
|
115210
115533
|
if (info.size > MAX_FILE_BYTES) {
|
|
115211
115534
|
return { id: id2, path: path2, name: id2, base: "dark", tokens: {}, problems: ["The file is too large to be a theme."], broken: true };
|
|
115212
115535
|
}
|
|
115213
|
-
return parseThemeFile(id2, path2, await
|
|
115536
|
+
return parseThemeFile(id2, path2, await readFile5(path2, "utf8"));
|
|
115214
115537
|
} catch (e) {
|
|
115215
115538
|
return { id: id2, path: path2, name: id2, base: "dark", tokens: {}, problems: [`Could not read the file: ${e.message}`], broken: true };
|
|
115216
115539
|
}
|
|
@@ -115218,16 +115541,16 @@ var ThemeFiles = class {
|
|
|
115218
115541
|
/** Writes a theme. `id` null makes a new file named after the theme (`My theme` → `my-theme.json`). */
|
|
115219
115542
|
async save(id2, content) {
|
|
115220
115543
|
const target = id2 ?? await this.freeId(content.name);
|
|
115221
|
-
await
|
|
115544
|
+
await mkdir7(this.dir, { recursive: true });
|
|
115222
115545
|
await this.atomicWrite(this.pathOf(target), formatThemeFile(content));
|
|
115223
115546
|
return this.read(target);
|
|
115224
115547
|
}
|
|
115225
115548
|
/** Copies a file from elsewhere in, under a fresh id if its name is taken. Refuses what does not read as a theme. */
|
|
115226
115549
|
async importFrom(source) {
|
|
115227
|
-
const info = await
|
|
115550
|
+
const info = await stat6(source);
|
|
115228
115551
|
if (!info.isFile()) throw Object.assign(new Error("That is not a file."), { code: "invalid_params" });
|
|
115229
115552
|
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
|
|
115553
|
+
const text2 = await readFile5(source, "utf8");
|
|
115231
115554
|
const parsed = parseThemeFile("import", source, text2);
|
|
115232
115555
|
if (parsed.broken) throw Object.assign(new Error(parsed.problems[0] ?? "Not a theme file."), { code: "invalid_params" });
|
|
115233
115556
|
const fromName = basename6(source).replace(/\.json$/i, "");
|
|
@@ -115238,13 +115561,13 @@ var ThemeFiles = class {
|
|
|
115238
115561
|
/** The absolute path for an id, for the shell to trash or reveal. Refuses anything that is not an id. */
|
|
115239
115562
|
pathOf(id2) {
|
|
115240
115563
|
if (!ID_PATTERN.test(id2)) throw Object.assign(new Error(`Not a theme id: ${id2}`), { code: "invalid_params" });
|
|
115241
|
-
return
|
|
115564
|
+
return join30(this.dir, `${id2}.json`);
|
|
115242
115565
|
}
|
|
115243
115566
|
async freeId(name) {
|
|
115244
115567
|
const base2 = name.toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "theme";
|
|
115245
115568
|
for (let n = 1; n < 1e3; n++) {
|
|
115246
115569
|
const id2 = n === 1 ? base2 : `${base2}-${n}`;
|
|
115247
|
-
const taken = await
|
|
115570
|
+
const taken = await stat6(this.pathOf(id2)).then(
|
|
115248
115571
|
() => true,
|
|
115249
115572
|
() => false
|
|
115250
115573
|
);
|
|
@@ -115256,7 +115579,7 @@ var ThemeFiles = class {
|
|
|
115256
115579
|
async atomicWrite(path2, text2) {
|
|
115257
115580
|
const tmp = `${path2}.${randomBytes4(6).toString("hex")}.tmp`;
|
|
115258
115581
|
try {
|
|
115259
|
-
await
|
|
115582
|
+
await writeFile5(tmp, text2, "utf8");
|
|
115260
115583
|
await rename4(tmp, path2);
|
|
115261
115584
|
} catch (e) {
|
|
115262
115585
|
await unlink2(tmp).catch(() => {
|
|
@@ -116277,7 +116600,7 @@ var sameCause = (a, b2) => a !== null && b2 !== null && JSON.stringify(a) === JS
|
|
|
116277
116600
|
|
|
116278
116601
|
// packages/agent-host/src/dev-services/terminal.ts
|
|
116279
116602
|
import { createRequire } from "node:module";
|
|
116280
|
-
import { existsSync as
|
|
116603
|
+
import { existsSync as existsSync20 } from "node:fs";
|
|
116281
116604
|
import { win32 as win327 } from "node:path";
|
|
116282
116605
|
var require2 = createRequire(import.meta.url);
|
|
116283
116606
|
var SCROLLBACK_BYTES = 256 * 1024;
|
|
@@ -116509,9 +116832,9 @@ function commandShell(command, platform = process.platform, env = process.env) {
|
|
|
116509
116832
|
}
|
|
116510
116833
|
function shellPath() {
|
|
116511
116834
|
const fromEnv = process.env.SHELL;
|
|
116512
|
-
if (fromEnv &&
|
|
116835
|
+
if (fromEnv && existsSync20(fromEnv)) return fromEnv;
|
|
116513
116836
|
for (const candidate of ["/bin/zsh", "/bin/bash", "/bin/sh"]) {
|
|
116514
|
-
if (
|
|
116837
|
+
if (existsSync20(candidate)) return candidate;
|
|
116515
116838
|
}
|
|
116516
116839
|
return process.platform === "win32" ? "powershell.exe" : "/bin/sh";
|
|
116517
116840
|
}
|
|
@@ -116698,7 +117021,7 @@ var CommandRunner = class {
|
|
|
116698
117021
|
|
|
116699
117022
|
// packages/agent-host/src/updates.ts
|
|
116700
117023
|
import { execFile as execFile8 } from "node:child_process";
|
|
116701
|
-
import { existsSync as
|
|
117024
|
+
import { existsSync as existsSync21 } from "node:fs";
|
|
116702
117025
|
var REGISTRY_URL = `https://registry.npmjs.org/${APP_SLUG}/latest`;
|
|
116703
117026
|
var CHECK_INTERVAL_MS = 6 * 60 * 60 * 1e3;
|
|
116704
117027
|
var SCHEDULED_TIMEOUT_MS = 3e3;
|
|
@@ -116708,7 +117031,7 @@ var NPM_RETRY_DELAY_MS = 3e4;
|
|
|
116708
117031
|
var NPM_RETRY_WINDOW_MS = 10 * 60 * 1e3;
|
|
116709
117032
|
function existingInstalledCopy() {
|
|
116710
117033
|
const path2 = installedCopyPath();
|
|
116711
|
-
return
|
|
117034
|
+
return existsSync21(path2) ? path2 : null;
|
|
116712
117035
|
}
|
|
116713
117036
|
async function fetchLatestFromRegistry(timeoutMs) {
|
|
116714
117037
|
try {
|
|
@@ -116722,16 +117045,16 @@ async function fetchLatestFromRegistry(timeoutMs) {
|
|
|
116722
117045
|
return { ok: false, reason: "Could not reach the registry \u2014 check the network" };
|
|
116723
117046
|
}
|
|
116724
117047
|
}
|
|
116725
|
-
function commandFor(file2, args,
|
|
116726
|
-
const launch2 =
|
|
117048
|
+
function commandFor(file2, args, resolve12 = (f) => resolveCommand(f)) {
|
|
117049
|
+
const launch2 = resolve12(file2);
|
|
116727
117050
|
return { file: launch2.command, args: [...launch2.args, ...args] };
|
|
116728
117051
|
}
|
|
116729
117052
|
function failureDetail(file2, stderr) {
|
|
116730
117053
|
const lines = stderr.split(/\r?\n/).map((l) => l.trim()).filter((l) => l && !/complete log of this run/i.test(l));
|
|
116731
117054
|
return (file2 === "npm" ? lines.at(-1) : lines[0]) ?? null;
|
|
116732
117055
|
}
|
|
116733
|
-
function runCommand(file2, args,
|
|
116734
|
-
const cmd = commandFor(file2, args,
|
|
117056
|
+
function runCommand(file2, args, resolve12) {
|
|
117057
|
+
const cmd = commandFor(file2, args, resolve12);
|
|
116735
117058
|
return new Promise((done, reject) => {
|
|
116736
117059
|
execFile8(cmd.file, cmd.args, { timeout: INSTALL_TIMEOUT_MS, windowsHide: true }, (err, _stdout, stderr) => {
|
|
116737
117060
|
if (!err) return done();
|
|
@@ -116759,7 +117082,7 @@ var UpdateService = class {
|
|
|
116759
117082
|
readAutoApply: deps.readAutoApply ?? (() => false),
|
|
116760
117083
|
writeAutoApply: deps.writeAutoApply ?? (() => {
|
|
116761
117084
|
}),
|
|
116762
|
-
wait: deps.wait ?? ((ms3) => new Promise((
|
|
117085
|
+
wait: deps.wait ?? ((ms3) => new Promise((resolve12) => setTimeout(resolve12, ms3)))
|
|
116763
117086
|
};
|
|
116764
117087
|
this.status = {
|
|
116765
117088
|
/*
|
|
@@ -117305,7 +117628,7 @@ var SessionSleeper = class {
|
|
|
117305
117628
|
// packages/agent-host/src/dev-services/instance-lock.ts
|
|
117306
117629
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
117307
117630
|
import { readFileSync as readFileSync13, unlinkSync as unlinkSync3, writeFileSync as writeFileSync7 } from "node:fs";
|
|
117308
|
-
import { dirname as dirname10, join as
|
|
117631
|
+
import { dirname as dirname10, join as join31 } from "node:path";
|
|
117309
117632
|
import Database2 from "better-sqlite3";
|
|
117310
117633
|
var OWNERSHIP_FILE = "host-ownership.sqlite";
|
|
117311
117634
|
var held = /* @__PURE__ */ new Set();
|
|
@@ -117339,7 +117662,7 @@ function parseHolder(raw) {
|
|
|
117339
117662
|
function acquireInstanceLock(dbPath2, startOf = processStartTime) {
|
|
117340
117663
|
if (dbPath2 === ":memory:") return { ok: true, release: () => {
|
|
117341
117664
|
} };
|
|
117342
|
-
const lockPath =
|
|
117665
|
+
const lockPath = join31(dirname10(dbPath2), "host.lock");
|
|
117343
117666
|
const readHolder = () => {
|
|
117344
117667
|
try {
|
|
117345
117668
|
return parseHolder(readFileSync13(lockPath, "utf8"));
|
|
@@ -117347,7 +117670,7 @@ function acquireInstanceLock(dbPath2, startOf = processStartTime) {
|
|
|
117347
117670
|
return null;
|
|
117348
117671
|
}
|
|
117349
117672
|
};
|
|
117350
|
-
const owner = takeOwnership(
|
|
117673
|
+
const owner = takeOwnership(join31(dirname10(dbPath2), OWNERSHIP_FILE));
|
|
117351
117674
|
if (!owner) {
|
|
117352
117675
|
const holder = readHolder();
|
|
117353
117676
|
return { ok: false, heldByPid: holder && Number.isInteger(holder.pid) && holder.pid > 0 ? holder.pid : null, lockPath };
|
|
@@ -117668,9 +117991,9 @@ var ControlChannel = class {
|
|
|
117668
117991
|
}
|
|
117669
117992
|
next(op) {
|
|
117670
117993
|
if (this.ended) return Promise.reject(new Error("the keeper closed the control channel"));
|
|
117671
|
-
return new Promise((
|
|
117994
|
+
return new Promise((resolve12, reject) => {
|
|
117672
117995
|
const list = this.waiters.get(op) ?? [];
|
|
117673
|
-
list.push({ resolve:
|
|
117996
|
+
list.push({ resolve: resolve12, reject });
|
|
117674
117997
|
this.waiters.set(op, list);
|
|
117675
117998
|
});
|
|
117676
117999
|
}
|
|
@@ -117775,7 +118098,7 @@ async function runDrain(bound, deps) {
|
|
|
117775
118098
|
}
|
|
117776
118099
|
|
|
117777
118100
|
// packages/agent-host/src/links/install.ts
|
|
117778
|
-
import { existsSync as
|
|
118101
|
+
import { existsSync as existsSync22, readFileSync as readFileSync14 } from "node:fs";
|
|
117779
118102
|
import { hostname as hostname3 } from "node:os";
|
|
117780
118103
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
117781
118104
|
import { gzipSync } from "node:zlib";
|
|
@@ -118099,7 +118422,7 @@ var SshTunnel = class {
|
|
|
118099
118422
|
if (this.closing) throw new Error(`The link to ${this.opts.target} was closed`);
|
|
118100
118423
|
}
|
|
118101
118424
|
run(args, stdin) {
|
|
118102
|
-
return new Promise((
|
|
118425
|
+
return new Promise((resolve12, reject) => {
|
|
118103
118426
|
const child = spawn5(this.opts.ssh ?? "ssh", args, { env: this.opts.env ?? process.env, stdio: [stdin === void 0 ? "ignore" : "pipe", "pipe", "pipe"] });
|
|
118104
118427
|
if (stdin !== void 0) {
|
|
118105
118428
|
child.stdin.on("error", () => {
|
|
@@ -118116,7 +118439,7 @@ var SshTunnel = class {
|
|
|
118116
118439
|
this.asking.delete(child);
|
|
118117
118440
|
reject(e);
|
|
118118
118441
|
});
|
|
118119
|
-
child.once("close", (code2) =>
|
|
118442
|
+
child.once("close", (code2) => resolve12({ code: code2, stdout, stderr }));
|
|
118120
118443
|
});
|
|
118121
118444
|
}
|
|
118122
118445
|
config() {
|
|
@@ -118163,7 +118486,7 @@ var SshTunnel = class {
|
|
|
118163
118486
|
this.child = child;
|
|
118164
118487
|
let stderr = "";
|
|
118165
118488
|
child.stderr.on("data", (d2) => stderr += String(d2));
|
|
118166
|
-
return new Promise((
|
|
118489
|
+
return new Promise((resolve12, reject) => {
|
|
118167
118490
|
let settled = false;
|
|
118168
118491
|
const deadline = Date.now() + (this.opts.forwardTimeoutMs ?? 2e4);
|
|
118169
118492
|
const fail4 = (message) => {
|
|
@@ -118182,7 +118505,7 @@ var SshTunnel = class {
|
|
|
118182
118505
|
if (await accepts(localPort)) {
|
|
118183
118506
|
settled = true;
|
|
118184
118507
|
this.opts.log?.(`[links] forward up: 127.0.0.1:${localPort} -> ${this.opts.target}:127.0.0.1:${remotePort}`);
|
|
118185
|
-
return
|
|
118508
|
+
return resolve12();
|
|
118186
118509
|
}
|
|
118187
118510
|
if (Date.now() > deadline) {
|
|
118188
118511
|
this.ended.add(child);
|
|
@@ -118200,12 +118523,12 @@ var SshTunnel = class {
|
|
|
118200
118523
|
this.child = null;
|
|
118201
118524
|
if (!child || child.exitCode !== null || child.signalCode !== null) return;
|
|
118202
118525
|
this.ended.add(child);
|
|
118203
|
-
await new Promise((
|
|
118204
|
-
child.once("exit", () =>
|
|
118526
|
+
await new Promise((resolve12) => {
|
|
118527
|
+
child.once("exit", () => resolve12());
|
|
118205
118528
|
child.kill("SIGTERM");
|
|
118206
118529
|
setTimeout(() => {
|
|
118207
118530
|
child.kill("SIGKILL");
|
|
118208
|
-
|
|
118531
|
+
resolve12();
|
|
118209
118532
|
}, 2e3).unref();
|
|
118210
118533
|
});
|
|
118211
118534
|
}
|
|
@@ -118215,29 +118538,29 @@ function lastLine(s) {
|
|
|
118215
118538
|
return [...lines].reverse().find((l) => /error|denied|refused|timed out|not found|cannot|could not/i.test(l)) ?? lines.at(-1) ?? "";
|
|
118216
118539
|
}
|
|
118217
118540
|
function accepts(port2) {
|
|
118218
|
-
return new Promise((
|
|
118541
|
+
return new Promise((resolve12) => {
|
|
118219
118542
|
const s = createConnection2({ host: "127.0.0.1", port: port2 });
|
|
118220
118543
|
s.once("connect", () => {
|
|
118221
118544
|
s.destroy();
|
|
118222
|
-
|
|
118545
|
+
resolve12(true);
|
|
118223
118546
|
});
|
|
118224
|
-
s.once("error", () =>
|
|
118547
|
+
s.once("error", () => resolve12(false));
|
|
118225
118548
|
});
|
|
118226
118549
|
}
|
|
118227
118550
|
function portFree(port2) {
|
|
118228
|
-
return new Promise((
|
|
118551
|
+
return new Promise((resolve12) => {
|
|
118229
118552
|
const probe = createServer3();
|
|
118230
|
-
probe.once("error", () =>
|
|
118231
|
-
probe.listen(port2, "127.0.0.1", () => probe.close(() =>
|
|
118553
|
+
probe.once("error", () => resolve12(false));
|
|
118554
|
+
probe.listen(port2, "127.0.0.1", () => probe.close(() => resolve12(true)));
|
|
118232
118555
|
});
|
|
118233
118556
|
}
|
|
118234
118557
|
function freePort() {
|
|
118235
|
-
return new Promise((
|
|
118558
|
+
return new Promise((resolve12, reject) => {
|
|
118236
118559
|
const probe = createServer3();
|
|
118237
118560
|
probe.once("error", reject);
|
|
118238
118561
|
probe.listen(0, "127.0.0.1", () => {
|
|
118239
118562
|
const { port: port2 } = probe.address();
|
|
118240
|
-
probe.close(() =>
|
|
118563
|
+
probe.close(() => resolve12(port2));
|
|
118241
118564
|
});
|
|
118242
118565
|
});
|
|
118243
118566
|
}
|
|
@@ -118453,7 +118776,7 @@ async function installRemote(o) {
|
|
|
118453
118776
|
function bundled(name, source) {
|
|
118454
118777
|
for (const url2 of [new URL(`./${name}`, import.meta.url), new URL(source, import.meta.url)]) {
|
|
118455
118778
|
const file2 = fileURLToPath6(url2);
|
|
118456
|
-
if (
|
|
118779
|
+
if (existsSync22(file2)) return readFileSync14(file2, "utf8");
|
|
118457
118780
|
}
|
|
118458
118781
|
throw new Error(`${name} is missing from this host`);
|
|
118459
118782
|
}
|
|
@@ -118556,7 +118879,7 @@ function probeTarget(target, opts = {}) {
|
|
|
118556
118879
|
return Promise.reject(Object.assign(new Error("Not an ssh target: use a host alias from ~/.ssh/config, or user@host"), { code: "internal" }));
|
|
118557
118880
|
}
|
|
118558
118881
|
const args = [...opts.configFile ? ["-F", opts.configFile] : [], "-T", "-o", "BatchMode=yes", "-o", "ConnectTimeout=10", "--", t, probeCommand()];
|
|
118559
|
-
return new Promise((
|
|
118882
|
+
return new Promise((resolve12) => {
|
|
118560
118883
|
let stdout = "";
|
|
118561
118884
|
let stderr = "";
|
|
118562
118885
|
let done = false;
|
|
@@ -118564,7 +118887,7 @@ function probeTarget(target, opts = {}) {
|
|
|
118564
118887
|
if (done) return;
|
|
118565
118888
|
done = true;
|
|
118566
118889
|
clearTimeout(timer);
|
|
118567
|
-
|
|
118890
|
+
resolve12(parseProbe(t, run));
|
|
118568
118891
|
};
|
|
118569
118892
|
const child = spawn6(opts.ssh ?? "ssh", args, { env: opts.env ?? process.env, stdio: ["ignore", "pipe", "pipe"] });
|
|
118570
118893
|
const timer = setTimeout(() => {
|
|
@@ -118582,7 +118905,7 @@ function probeTarget(target, opts = {}) {
|
|
|
118582
118905
|
import { spawn as spawn7 } from "node:child_process";
|
|
118583
118906
|
import { readdirSync as readdirSync8, readFileSync as readFileSync15, statSync as statSync13 } from "node:fs";
|
|
118584
118907
|
import { homedir as homedir13 } from "node:os";
|
|
118585
|
-
import { isAbsolute as isAbsolute10, join as
|
|
118908
|
+
import { isAbsolute as isAbsolute10, join as join32, normalize, parse as parse6, sep as sep12 } from "node:path";
|
|
118586
118909
|
var MAX_DEPTH2 = 16;
|
|
118587
118910
|
function splitConfigLine(raw) {
|
|
118588
118911
|
const m = /^\s*([^\s=#]+)\s*(?:=\s*)?([\s\S]*)$/.exec(raw);
|
|
@@ -118648,7 +118971,7 @@ function expandGlob(path2) {
|
|
|
118648
118971
|
const next = [];
|
|
118649
118972
|
for (const base2 of found) {
|
|
118650
118973
|
if (!MAGIC.test(seg)) {
|
|
118651
|
-
next.push(
|
|
118974
|
+
next.push(join32(base2, seg));
|
|
118652
118975
|
continue;
|
|
118653
118976
|
}
|
|
118654
118977
|
let names;
|
|
@@ -118660,7 +118983,7 @@ function expandGlob(path2) {
|
|
|
118660
118983
|
const re2 = segmentRe(seg);
|
|
118661
118984
|
for (const name of names.sort()) {
|
|
118662
118985
|
if (name.startsWith(".") && !seg.startsWith(".")) continue;
|
|
118663
|
-
if (re2.test(name)) next.push(
|
|
118986
|
+
if (re2.test(name)) next.push(join32(base2, name));
|
|
118664
118987
|
}
|
|
118665
118988
|
}
|
|
118666
118989
|
found = next;
|
|
@@ -118715,11 +119038,11 @@ function readSshConfig(opts) {
|
|
|
118715
119038
|
}
|
|
118716
119039
|
for (const arg of split2.args) {
|
|
118717
119040
|
let path2 = arg;
|
|
118718
|
-
if (path2.startsWith("~/")) path2 =
|
|
119041
|
+
if (path2.startsWith("~/")) path2 = join32(opts.home, path2.slice(2));
|
|
118719
119042
|
else if (path2.startsWith("~")) {
|
|
118720
119043
|
notes.push(`An Include of another user's folder (${where(file2, n + 1)}) was not read`);
|
|
118721
119044
|
continue;
|
|
118722
|
-
} else if (!isAbsolute10(path2)) path2 =
|
|
119045
|
+
} else if (!isAbsolute10(path2)) path2 = join32(opts.home, ".ssh", path2);
|
|
118723
119046
|
for (const f of expandGlob(path2)) read(f, depth + 1);
|
|
118724
119047
|
}
|
|
118725
119048
|
}
|
|
@@ -118742,7 +119065,7 @@ function parseSshG(stdout) {
|
|
|
118742
119065
|
return { hostName: first.get("hostname") || null, user: first.get("user") || null, port: Number.isInteger(port2) && port2 > 0 ? port2 : null };
|
|
118743
119066
|
}
|
|
118744
119067
|
function resolveAlias(alias, opts = {}) {
|
|
118745
|
-
return new Promise((
|
|
119068
|
+
return new Promise((resolve12) => {
|
|
118746
119069
|
const args = [...opts.configFile ? ["-F", opts.configFile] : [], "-G", "--", alias];
|
|
118747
119070
|
let stdout = "";
|
|
118748
119071
|
let stderr = "";
|
|
@@ -118751,7 +119074,7 @@ function resolveAlias(alias, opts = {}) {
|
|
|
118751
119074
|
if (done) return;
|
|
118752
119075
|
done = true;
|
|
118753
119076
|
clearTimeout(timer);
|
|
118754
|
-
|
|
119077
|
+
resolve12(r);
|
|
118755
119078
|
};
|
|
118756
119079
|
const none = (error51) => ({ hostName: null, user: null, port: null, error: error51 });
|
|
118757
119080
|
const child = spawn7(opts.ssh ?? "ssh", args, { env: opts.env ?? process.env, stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -118769,18 +119092,18 @@ var MAX_HOSTS = 100;
|
|
|
118769
119092
|
var RESOLVE_AT_ONCE = 8;
|
|
118770
119093
|
async function listSshHosts(opts = {}) {
|
|
118771
119094
|
const home = opts.home ?? homedir13();
|
|
118772
|
-
const file2 = opts.configFile ??
|
|
119095
|
+
const file2 = opts.configFile ?? join32(home, ".ssh", "config");
|
|
118773
119096
|
const read = readSshConfig({ file: file2, home });
|
|
118774
119097
|
const notes = [...read.notes];
|
|
118775
119098
|
const listed = read.hosts.slice(0, MAX_HOSTS);
|
|
118776
119099
|
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
|
|
119100
|
+
const resolve12 = opts.resolve ?? ((alias) => resolveAlias(alias, opts));
|
|
118778
119101
|
const resolved = new Array(listed.length);
|
|
118779
119102
|
let next = 0;
|
|
118780
119103
|
const worker = async () => {
|
|
118781
119104
|
while (next < listed.length) {
|
|
118782
119105
|
const i = next++;
|
|
118783
|
-
resolved[i] = await
|
|
119106
|
+
resolved[i] = await resolve12(listed[i].alias);
|
|
118784
119107
|
}
|
|
118785
119108
|
};
|
|
118786
119109
|
await Promise.all(Array.from({ length: Math.min(RESOLVE_AT_ONCE, listed.length) }, worker));
|
|
@@ -119269,11 +119592,11 @@ var RemoteClient = class {
|
|
|
119269
119592
|
if (!this.ready || !this.ws) return Promise.reject(linkError("The machine is not reachable right now", true));
|
|
119270
119593
|
const ws2 = this.ws;
|
|
119271
119594
|
const id2 = String(this.nextId++);
|
|
119272
|
-
return new Promise((
|
|
119595
|
+
return new Promise((resolve12, reject) => {
|
|
119273
119596
|
const timer = setTimeout(() => {
|
|
119274
119597
|
if (this.pending.delete(id2)) reject(Object.assign(new Error(`The machine did not answer ${method} in time`), { code: "internal", retryable: true }));
|
|
119275
119598
|
}, this.opts.callTimeoutMs ?? DEFAULT_CALL_TIMEOUT_MS);
|
|
119276
|
-
this.pending.set(id2, { resolve:
|
|
119599
|
+
this.pending.set(id2, { resolve: resolve12, reject, timer });
|
|
119277
119600
|
ws2.send(JSON.stringify({ kind: "rpc", id: id2, method, params }));
|
|
119278
119601
|
});
|
|
119279
119602
|
}
|
|
@@ -119636,14 +119959,14 @@ var LinkedMachine = class {
|
|
|
119636
119959
|
if (this.client?.connected) return this.client.call("host.activity", {}).then(read, () => null);
|
|
119637
119960
|
const e = this.endpoint;
|
|
119638
119961
|
if (!e?.url || this.versions?.compatible === false || this.held) return Promise.resolve(null);
|
|
119639
|
-
return new Promise((
|
|
119962
|
+
return new Promise((resolve12) => {
|
|
119640
119963
|
let settled = false;
|
|
119641
119964
|
const done = (v) => {
|
|
119642
119965
|
if (settled) return;
|
|
119643
119966
|
settled = true;
|
|
119644
119967
|
clearTimeout(timer);
|
|
119645
119968
|
client.close();
|
|
119646
|
-
|
|
119969
|
+
resolve12(v);
|
|
119647
119970
|
};
|
|
119648
119971
|
const client = new RemoteClient(
|
|
119649
119972
|
{ ...this.deps.client, url: e.url, token: e.token, handshakeTimeoutMs: 5e3, callTimeoutMs: 5e3 },
|
|
@@ -119717,16 +120040,16 @@ var LinkedMachine = class {
|
|
|
119717
120040
|
const started = await this.tunnel.startHost();
|
|
119718
120041
|
this.hostStarted = { ...started, at: Date.now() };
|
|
119719
120042
|
},
|
|
119720
|
-
answers: (version2, ms3) => new Promise((
|
|
120043
|
+
answers: (version2, ms3) => new Promise((resolve12) => {
|
|
119721
120044
|
const waiter = (v) => {
|
|
119722
120045
|
if (v !== version2) return;
|
|
119723
120046
|
clearTimeout(timer);
|
|
119724
120047
|
this.helloWaiters.delete(waiter);
|
|
119725
|
-
|
|
120048
|
+
resolve12(true);
|
|
119726
120049
|
};
|
|
119727
120050
|
const timer = setTimeout(() => {
|
|
119728
120051
|
this.helloWaiters.delete(waiter);
|
|
119729
|
-
void hold().then(() =>
|
|
120052
|
+
void hold().then(() => resolve12(false));
|
|
119730
120053
|
}, this.deps.checkMs ?? ms3);
|
|
119731
120054
|
this.helloWaiters.add(waiter);
|
|
119732
120055
|
this.held = false;
|
|
@@ -120875,7 +121198,7 @@ function storeMirror(store2) {
|
|
|
120875
121198
|
var start = new StartSequence();
|
|
120876
121199
|
start.at("launch variables");
|
|
120877
121200
|
var launch = takeLaunchEnv();
|
|
120878
|
-
var BUILD = true ? "
|
|
121201
|
+
var BUILD = true ? "5dca7b8" : "dev";
|
|
120879
121202
|
var { values } = parseArgs({
|
|
120880
121203
|
options: {
|
|
120881
121204
|
port: { type: "string", default: "5175" },
|
|
@@ -120896,12 +121219,12 @@ start.at("data folder");
|
|
|
120896
121219
|
var movedNote = null;
|
|
120897
121220
|
function defaultDbPath() {
|
|
120898
121221
|
const isDev = process.env.CC_DEV === "1";
|
|
120899
|
-
const dir =
|
|
120900
|
-
const legacy =
|
|
121222
|
+
const dir = join33(homedir14(), isDev ? DATA_DIR_DEV : DATA_DIR);
|
|
121223
|
+
const legacy = join33(homedir14(), isDev ? DATA_DIR_LEGACY.dev : DATA_DIR_LEGACY.prod);
|
|
120901
121224
|
movedNote = migrateLegacyDataDir(legacy, dir) ? `[agent-host] data folder moved: ${legacy} -> ${dir}` : null;
|
|
120902
121225
|
mkdirSync11(dir, { recursive: true });
|
|
120903
121226
|
process.env.CC_DATA_DIR = dir;
|
|
120904
|
-
return
|
|
121227
|
+
return join33(dir, "store.db");
|
|
120905
121228
|
}
|
|
120906
121229
|
var dbPath = values.memory ? ":memory:" : values.db ?? defaultDbPath();
|
|
120907
121230
|
start.at("log");
|
|
@@ -120979,7 +121302,7 @@ var mgr = new SessionManager(
|
|
|
120979
121302
|
},
|
|
120980
121303
|
(sessionId) => bridgeAddress(launch.frontDoor, port, token, sessionId),
|
|
120981
121304
|
// Worktrees are created next to the data folder — dev and the packaged app never touch each other's worktrees
|
|
120982
|
-
|
|
121305
|
+
join33(dirname11(dbPath), "worktrees"),
|
|
120983
121306
|
held2 ? { processes: held2.processes, keptSessions: held2.kept.sessionIds } : {}
|
|
120984
121307
|
);
|
|
120985
121308
|
mgr.useProjectsHome(() => projectsHome());
|
|
@@ -121013,7 +121336,7 @@ if (held2) {
|
|
|
121013
121336
|
}
|
|
121014
121337
|
var AUTO_UPDATE_CHECK_KEY = "updates.auto";
|
|
121015
121338
|
var AUTO_APPLY_UPDATES_KEY = "updates.autoApply";
|
|
121016
|
-
var themes = new ThemeFiles(
|
|
121339
|
+
var themes = new ThemeFiles(join33(dataRoot(), "themes"), () => events.push({ type: "themes_changed" }));
|
|
121017
121340
|
await themes.start().catch((e) => console.error(`[themes] the themes folder is unavailable: ${e.message}`));
|
|
121018
121341
|
var updates = new UpdateService((status) => events.push({ type: "update_status", status }), {
|
|
121019
121342
|
// On by default when nothing has been saved yet. Since the check is read-only and every failure
|
|
@@ -121232,7 +121555,7 @@ var endings = wireEndings({
|
|
|
121232
121555
|
heldChildren: held2 !== null,
|
|
121233
121556
|
leave: (mode, handOver) => void shutdown.leave(mode, handOver),
|
|
121234
121557
|
force: (why) => shutdown.force(why),
|
|
121235
|
-
record: crashRecorder(
|
|
121558
|
+
record: crashRecorder(join33(dirname11(dbPath), "host-errors.log"), (line) => console.error(line)),
|
|
121236
121559
|
stdin: process.stdin,
|
|
121237
121560
|
watchParent: values["watch-parent"] === true,
|
|
121238
121561
|
log: (line) => console.error(line)
|