@centralu/host-linux-arm64 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.
package/host/main.mjs CHANGED
@@ -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 rm3(e) {
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 rm3(t) ? q(e, sm) : void 0;
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" && rm3(t));
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 (rm3(t)) return;
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 join32 } from "node:path";
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.20";
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' starts a session in `to` and gives it a task
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: a session ask_project opens
58050
- * there follows that trust. Absent from a host that predates it, read as not saying.
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 asked for this one through ask_project (#371 part B) — a session in another project. Null for
59631
- * every session a person or an app started.
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((resolve11) => {
61764
- if (out.readableEnded || out.destroyed) return resolve11();
61765
- const t = setTimeout(resolve11, DETACH_WAIT_MS);
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
- resolve11();
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((resolve11, reject) => {
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 resolve11(v);
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((resolve11, reject) => {
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
- resolve11({ sock, answer });
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((resolve11, reject) => {
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: resolve11, reject, timer });
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((resolve11) => {
62109
- if (sock.readableEnded || sock.destroyed) return resolve11();
62110
- const t = setTimeout(resolve11, 1e3);
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
- resolve11();
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((resolve11, reject) => {
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() && resolve11(v),
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((resolve11, reject) => {
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
- resolve11(typeof addr === "object" && addr ? addr.port : this.opts.port);
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 join33 = (...parts) => parts.filter(Boolean).join(" ");
63030
+ const join34 = (...parts) => parts.filter(Boolean).join(" ");
63032
63031
  const policy = [
63033
63032
  "default-src 'none'",
63034
- `script-src ${join33("'unsafe-inline'", r)}`,
63035
- `style-src ${join33("'unsafe-inline'", r)}`,
63036
- `img-src ${join33("data: blob:", r)}`,
63037
- `font-src ${join33("data:", r)}`,
63038
- `media-src ${join33("data: blob:", r)}`,
63039
- `worker-src ${join33("blob:", r)}`,
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((resolve11, reject) => {
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
- resolve11();
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((resolve11, reject) => {
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 resolve11(manualInputRequiredValue(decoded));
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 resolve11(this._resolveNonCompleteResult(decoded, flow));
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) resolve11(parseResult.data);
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 = resolve11.call(this, root3, ref);
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 resolve11(root3, ref) {
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 resolve11(baseURI, relativeURI, options) {
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: resolve11,
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((resolve11) => {
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
- resolve11(reply);
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((resolve11, reject) => {
79074
- resolveOpening = resolve11;
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((resolve11) => {
79079
- resolveClosed = resolve11;
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((resolve11, reject) => {
79852
- this.writable.write(serializeMessage(message), (err) => err ? reject(err) : resolve11());
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((resolve11) => {
80065
- const t = setTimeout(() => resolve11(false), ms3);
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
- resolve11(true);
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((resolve11, reject) => {
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
- resolve11();
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((resolve11, reject) => {
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 resolve11(manualInputRequiredValue2(decoded));
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 resolve11(this._resolveNonCompleteResult(decoded, flow));
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) resolve11(parseResult.data);
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 = resolve11.call(this, root3, ref);
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 resolve11(root3, ref) {
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 resolve11(baseURI, relativeURI, options) {
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: resolve11,
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((resolve11, reject) => {
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
- resolve11();
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
- resolve11();
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((resolve11) => {
93612
+ return await new Promise((resolve12) => {
93614
93613
  const waiter = () => {
93615
93614
  clearTimeout(timer);
93616
- resolve11(true);
93615
+ resolve12(true);
93617
93616
  };
93618
93617
  const timer = setTimeout(() => {
93619
93618
  this._drainWaiters = this._drainWaiters.filter((pending) => pending !== waiter);
93620
- resolve11(false);
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((resolve11) => call2.signal.aborted ? resolve11(null) : call2.signal.addEventListener("abort", () => resolve11(null), { once: true }))
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
- if (!e.scope.trusted) return done("rejected", null, "This app's project is not trusted, so Centralu does not call its apps");
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((resolve11) => {
97274
+ return new Promise((resolve12) => {
97270
97275
  const waiter = () => {
97271
97276
  clearTimeout(t);
97272
- resolve11(true);
97277
+ resolve12(true);
97273
97278
  };
97274
97279
  const t = setTimeout(() => {
97275
97280
  e.life.idleWaiters = e.life.idleWaiters.filter((w) => w !== waiter);
97276
- resolve11(false);
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((resolve11, reject) => {
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
- resolve11(server2);
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 randomUUID9 } from "node:crypto";
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: "Has another Centralu project do a job in its own folder; returns its session's answer. For another project's apps, use find_apps.",
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: `Cleaned up ${who}: "${r.branch}" had landed on the trunk (${r.proof}), so its worktree and branch were removed and the session went to the trash. Tell the person in one line.`
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
- return askProjectText(await tools.askProject({ project: project2, ...task ? { task } : {} }, signal));
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 join16 } from "node:path";
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 = join16(dataRoot(), "orchestrator");
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((resolve11) => timer = setTimeout(() => resolve11(null), opts.waitMs))
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((resolve11) => {
100058
+ return new Promise((resolve12) => {
99906
100059
  const waiter = {
99907
100060
  server: server2,
99908
100061
  tool,
99909
100062
  args: key,
99910
- resolve: resolve11,
100063
+ resolve: resolve12,
99911
100064
  timer: setTimeout(() => {
99912
100065
  this.waiters = this.waiters.filter((x) => x !== waiter);
99913
- resolve11(null);
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((resolve11, reject) => this.settle = { resolve: resolve11, reject });
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 resolve2, sep as sep4 } from "node:path";
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 = resolve2(cwd, path2);
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 join28, relative as relative8, resolve as resolve10, sep as sep11 } from "node:path";
100839
- import { existsSync as existsSync18, rmSync as rmSync7, statSync as statSync12 } from "node:fs";
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 randomUUID8 } from "node:crypto";
100998
+ import { randomUUID as randomUUID9 } from "node:crypto";
100846
100999
 
100847
101000
  // packages/agent-host/src/adapters/input-receipts.ts
100848
- import { randomUUID as randomUUID7 } from "node:crypto";
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 = randomUUID7();
100854
- let resolve11;
101006
+ const id2 = randomUUID8();
101007
+ let resolve12;
100855
101008
  let reject;
100856
101009
  const done = new Promise((yes, no2) => {
100857
- resolve11 = yes;
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: resolve11, reject, observer });
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 join21 } from "node:path";
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" ? join21(pkg, "install.cjs") : null;
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, stat6, fs2 = realExeFs) {
101178
+ function claudeKey(exe, stat7, fs2 = realExeFs) {
101026
101179
  let version2 = null;
101027
101180
  try {
101028
- const pkg = JSON.parse(fs2.read(join21(dirname8(dirname8(exe)), "package.json")));
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}-${stat6.size}` : `${stat6.size}-${Math.floor(stat6.mtimeMs)}`;
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 join21(this.opts.root(), "tools", "claude");
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 stat6;
101218
+ let stat7;
101066
101219
  try {
101067
- stat6 = this.fs.stat(exe);
101220
+ stat7 = this.fs.stat(exe);
101068
101221
  } catch {
101069
101222
  return direct;
101070
101223
  }
101071
- const problem2 = placeholderProblem(exe, stat6.size, this.fs);
101224
+ const problem2 = placeholderProblem(exe, stat7.size, this.fs);
101072
101225
  if (problem2) throw new ClaudePlaceholderError(problem2);
101073
- const key = claudeKey(exe, stat6, this.fs);
101074
- const dir = join21(this.base(), key);
101075
- const target = join21(dir, "claude.exe");
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 === stat6.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 = join21(dir, `claude-${randomBytes2(4).toString("hex")}.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(join21(this.base(), name));
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((resolve11) => this.markEnded = resolve11);
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((resolve11) => {
102950
- self2.questions.set(requestId2, { resolve: resolve11, input: toolInput });
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((resolve11) => {
102960
- self2.pending.set(requestId, { resolve: resolve11, input: toolInput });
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((resolve11) => this.settleGoalKnown = resolve11);
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 = randomUUID8(), note) {
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((resolve11) => timer = setTimeout(resolve11, this.launch.exitGraceMs))
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 existsSync14 } from "node:fs";
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) => existsSync14(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 existsSync16, realpathSync as realpathSync7 } from "node:fs";
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 join23, resolve as resolve8 } from "node:path";
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 existsSync15, readFileSync as readFileSync11, realpathSync as realpathSync6 } from "node:fs";
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: existsSync15,
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((resolve11, reject) => {
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
- resolve11(v);
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((resolve11) => {
103981
- if (this.proc.exitCode !== null || this.proc.signalCode != null || this.finished) return resolve11(true);
103982
- const t = setTimeout(() => resolve11(false), 2e3);
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
- resolve11(true);
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 readdir4 } from "node:fs/promises";
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 join22 } from "node:path";
104170
+ import { join as join23 } from "node:path";
104011
104171
  var MIN_SUMMARY_CHARS = 200;
104012
- async function findRolloutPath(threadId, sessionsDir = join22(homedir6(), ".codex", "sessions")) {
104172
+ async function findRolloutPath(threadId, sessionsDir = join23(homedir6(), ".codex", "sessions")) {
104013
104173
  try {
104014
104174
  const suffix = `-${threadId}.jsonl`;
104015
- const names = await readdir4(sessionsDir, { recursive: true });
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 ? join22(sessionsDir, String(hit)) : null;
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 readFile2, stat as stat4 } from "node:fs/promises";
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 stat4(path2);
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 readFile2(path2);
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 realpath5 } from "node:fs/promises";
105222
- import { resolve as resolve7 } from "node:path";
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 realpath5(resolve7(cwd, gitDir));
105230
- const commonPath = await realpath5(resolve7(cwd, commonDir));
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 [resolve8(opts.cwd), realPathOr(opts.cwd)]) {
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 resolve8(path2);
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 : join23(homedir10(), ".codex");
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 = existsSync16(join23(codexHome(), "auth.json"));
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 existsSync17, readFileSync as readFileSync12 } from "node:fs";
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) => existsSync17(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:" || !existsSync17(dbPath2)) return empty(known);
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((resolve11) => setImmediate(resolve11));
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((resolve11) => setImmediate(resolve11));
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 resolve9, sep as sep9 } from "node:path";
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 = resolve9(cwd);
109450
- const target = isAbsolute8(path2) ? resolve9(path2) : resolve9(root3, 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((resolve11, reject) => {
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 ? resolve11(stdout) : reject(err)
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 lstat4, readFile as readFile3, readdir as readdir5 } from "node:fs/promises";
109707
- import { join as join24 } from "node:path";
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 lstat4(join24(worktreeCwd, name)).catch((err) => {
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([readFile3(source), readFile3(destination)]);
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 readdir5(join24(worktreeCwd, folder), { withFileTypes: true });
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(join24(folder, entry.name));
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 = join24(folder, "node_modules");
109899
+ const rel = join25(folder, "node_modules");
109739
109900
  try {
109740
109901
  const src = await resolveExisting(projectCwd, rel);
109741
- if (!(await lstat4(src)).isDirectory()) continue;
109742
- const manifest = join24(folder, "package.json");
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) => readFile3(p)),
109745
- resolveExisting(worktreeCwd, manifest).then((p) => readFile3(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 readdir6 } from "node:fs/promises";
109768
- import { join as join25 } from "node:path";
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 readdir6(dir, { withFileTypes: true });
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: join25(dir, e.name), rel: wireJoin(rel, e.name), depth: depth + 1 });
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 mkdir4, writeFile as writeFile2, rm as rm4 } from "node:fs/promises";
109870
- import { extname as extname3, join as join26 } from "node:path";
109871
- var root = () => join26(dataRoot(), "attachments");
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 join26(root(), sessionId);
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 mkdir4(dir, { recursive: true });
110047
+ await mkdir5(dir, { recursive: true });
109887
110048
  const ext2 = extname3(name) || EXT[mime] || "";
109888
- const file2 = join26(dir, `${Date.now()}-${Math.random().toString(36).slice(2, 8)}${ext2}`);
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 writeFile2(file2, buf);
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 rm4(sessionDir(sessionId), { recursive: true, force: true });
110055
+ await rm5(sessionDir(sessionId), { recursive: true, force: true });
109895
110056
  }
109896
110057
  async function attachmentBytes(sessionId) {
109897
- const { readdir: readdir9, lstat: lstat6 } = await import("node:fs/promises");
110058
+ const { readdir: readdir10, lstat: lstat7 } = await import("node:fs/promises");
109898
110059
  const dir = sessionDir(sessionId);
109899
- const names = await readdir9(dir).catch(() => []);
110060
+ const names = await readdir10(dir).catch(() => []);
109900
110061
  let total = 0;
109901
110062
  for (const name of names) {
109902
- const s = await lstat6(join26(dir, name)).catch(() => null);
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: readdir9, stat: stat6, rm: rmFile } = await import("node:fs/promises");
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 readdir9(root());
110074
+ dirs = await readdir10(root());
109914
110075
  } catch {
109915
110076
  return 0;
109916
110077
  }
109917
110078
  for (const d2 of dirs) {
109918
- const dir = join26(root(), d2);
109919
- const names = await readdir9(dir).catch(() => []);
110079
+ const dir = join27(root(), d2);
110080
+ const names = await readdir10(dir).catch(() => []);
109920
110081
  for (const name of names) {
109921
- const p = join26(dir, name);
109922
- const s = await stat6(p).catch(() => null);
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 realpath6 } from "node:fs/promises";
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 realpath6(abs);
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 lstat5, mkdir as mkdir5, readdir as readdir7, rm as rm5, writeFile as writeFile3 } from "node:fs/promises";
110039
- import { join as join27 } from "node:path";
110040
- var root2 = () => join27(dataRoot(), "handoff");
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 join27(root2(), projectId);
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 join27(handoffNoteDir(projectId), `${sessionId}.md`);
110212
+ return join28(handoffNoteDir(projectId), `${sessionId}.md`);
110052
110213
  }
110053
110214
  async function handoffNoteBytes(projectId, sessionId) {
110054
- const s = await lstat5(handoffNotePath(projectId, sessionId)).catch(() => null);
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 mkdir5(handoffNoteDir(projectId), { recursive: true });
110060
- await rm5(path2, { force: true });
110061
- await writeFile3(path2, text2, "utf8");
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 readdir7(root2(), { withFileTypes: true })).filter((d2) => d2.isDirectory()).map((d2) => d2.name);
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 readdir7(dir, { withFileTypes: true }).catch(() => []);
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 rm5(join27(dir, e.name), { force: true }).catch(() => {
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 = join28(homedir12(), DATA_DIR, "worktrees"), keeperOpts = {}) {
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
- for (const s of store2.listSessions()) {
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 pair at a time: a second task while the first runs is refused, and a call with no task waits
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 (!existsSync18(path2) || !statSync12(path2).isDirectory()) {
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 ?? randomUUID9();
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(resolve11) {
110402
- this.projectsHome = resolve11;
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 = randomUUID9();
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 && existsSync18(r.worktree.path) ? { ...r.worktree, remove: record2.removeWorktree } : null
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 && existsSync18(record2.projectPath) && statSync12(record2.projectPath).isDirectory()) {
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 (fixed !== back) this.store.upsertSession(fixed);
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 join28(this.worktreeRoot, projectId, sessionId);
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, resolve10(base2, p));
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 ask_project opened for it, while the turn it gave them runs, and a session that owes
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 && (!d2.agent || (d2.waiters ?? 0) > 0)) d2.stop("the person stopped the session that asked");
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): a session opened there follows its trust, so the person should know before allowing
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
- * Raises a card of the host's own in a session's approval slot and waits for the person's answer (#371, #515):
113740
- * null when `signal` fires first, the card then taken down so none is left unanswerable.
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 new Promise((resolve11) => {
113744
- if (signal?.aborted) return resolve11(null);
113745
- const requestId = `${detail.kind === "project_access" ? "xp" : detail.kind === "agent_start" ? "ag" : detail.kind === "centralu_update" ? "up" : "mk"}-${randomUUID9()}`;
113746
- this.hostAsks.set(requestId, { requestId, sessionId, detail, shown: false, resolve: resolve11 });
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
- resolve11(null);
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. A session in the target: the idle one this caller asked there before, else a new one with that project's
113784
- * folder, instructions and default tool, under the `normal` preset — what a session the person opens there
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 delegated turn as well.
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 key = `${callerId}>${target.id}`;
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
- ok: false,
113820
- error: `${target.name} is still working on your last task in the session "${this.meta.get(pending.sessionId)?.name ?? pending.sessionId}". Call ask_project with project "${target.name}" and no task to wait for it.`
113821
- };
113822
- }
113823
- if (pending) this.delegations.delete(key);
113824
- if (!opts.task) return { ok: false, error: `Give a task: what ${target.name} should do.` };
113825
- const consent = await this.ensureProjectAccess(callerId, target.id, "delegate", { text: taskLine(opts.task) }, signal);
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 reuse = [...this.meta.values()].filter((s) => s.askedBy === callerId && s.projectId === target.id && s.kind === "worker" && (s.state === "idle" || s.state === "waiting_input") && !this.agentRuns.has(s.id)).sort((a, b2) => b2.createdAt - a.createdAt)[0];
113828
- let delegatedId;
113829
- if (reuse) {
113830
- delegatedId = reuse.id;
113831
- } else {
113832
- const tool = this.firstTool(target.defaultTool);
113833
- if (!tool) return { ok: false, error: `Could not open a session in ${target.name}: no agent adapter is available. Install and configure an agent CLI first.` };
113834
- const adapter = this.adapters.get(tool);
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
- await this.deliver(delegatedId, askFrame(callerProject, opts.task), void 0, { sessionId: callerId, name: labelOf(this.store, caller) }, false);
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
- this.delegations.delete(key);
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((resolve11) => timer = setTimeout(() => resolve11("bound"), ASK_WAIT_MS));
113914
- const aborted2 = new Promise((resolve11) => {
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 = () => resolve11("aborted");
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): the caller works with a session it started — and only those: one whose `askedBy` is the
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 cleans up a child's merged, clean worktree (`cleanUpWorktreeChild`, #76's hard gate, part 3)
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
- const d2 = this.delegations.get(key);
114071
- switch (opts.action) {
114072
- case "status":
114073
- if (d2 && !d2.settled) return this.agentWorking(d2, false);
114074
- return d2 ? this.awaitAgent(key, d2, signal) : this.agentAtRest(child);
114075
- case "wait":
114076
- return d2 ? this.awaitAgent(key, d2, signal) : this.agentAtRest(child);
114077
- case "stop": {
114078
- if (!d2 || d2.settled) return { ok: false, error: `Nothing you gave "${child.name}" is running.` };
114079
- if (this.awaitingReport.get(child.id) === callerId) this.awaitingReport.delete(child.id);
114080
- d2.stop("the session that started it stopped it");
114081
- await d2.outcome;
114082
- this.delegations.delete(key);
114083
- return { ok: true, state: "stopped", sessionId: child.id, sessionName: child.name, tool: child.tool };
114084
- }
114085
- case "delete": {
114086
- if (d2 && !d2.settled) return { ok: false, error: `"${child.name}" is still working on what you gave it \u2014 stop it or wait first.` };
114087
- const r = await this.cleanUpWorktreeChild(child);
114088
- if (!r.ok) return r;
114089
- if (d2) this.delegations.delete(key);
114090
- return { ok: true, state: "deleted", sessionId: child.id, sessionName: child.name, tool: child.tool, branch: r.branch, proof: r.proof };
114091
- }
114092
- case "send": {
114093
- if (!presetWithin(child.permissionPreset, caller.permissionPreset)) {
114094
- return {
114095
- ok: false,
114096
- error: `"${child.name}" now has more permissions (${child.permissionPreset}) than this session (${caller.permissionPreset}), so this session cannot give it work. Tell the person.`
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
- const from = { sessionId: callerId, name: labelOf(this.store, caller) };
114100
- if (d2 && !d2.settled) {
114101
- const text2 = agentNoteFrame(from.name, opts.text ?? "");
114102
- if (!this.adapters.get(child.tool)?.capabilities.midTurnInput || !this.handles.get(child.id)?.sendTracked) {
114103
- return { ok: false, error: `"${child.name}" cannot take messages while working. Wait for it (action "wait") or stop it first.` };
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 releaseHold = d2.wait.holdInput();
114106
- const release = () => {
114107
- releaseHold();
114108
- this.applyDeferredRestart(child.id);
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, text2, void 0, from, false, void 0, "inline", (h, input) => {
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
- release();
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.agentWorking(d2, false);
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
- return this.awaitAgent(key, next, signal);
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, randomUUID9()) };
114459
+ return { branch: agentBranch(opts.task, randomUUID10()) };
114155
114460
  }
114156
- /**
114157
- * Cleans up a child's merged worktree (#76's hard gate, moved from the retired worktree manager's tool to
114158
- * agent_session delete, #561 part 3). It runs **only when losslessness is proven at this moment**: no uncommitted
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 clean it up once its turn ends` };
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
- const trunk = this.trunkOf(target.projectId) ?? "HEAD";
114180
- let proof = null;
114181
- if (await gitBranchMerged(cwd, branch, base2, trunk).catch(() => false)) {
114182
- proof = "trunk ancestry";
114183
- } else if (this.ghAvailable) {
114184
- const pr = await this.prLookup(cwd, branch).catch(() => null);
114185
- if (pr === "unavailable") this.ghAvailable = false;
114186
- else if (pr && pr.state === "merged") {
114187
- const tip2 = await gitRevParse(cwd, `refs/heads/${branch}`);
114188
- if (pr.headOid && tip2 && tip2 === pr.headOid) proof = `PR #${pr.number}`;
114189
- else if (pr.headOid && tip2) {
114190
- return { ok: false, error: `PR #${pr.number} was merged, but "${branch}" has a new commit since \u2014 it is deleted only once that commit has landed on the trunk too` };
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
- /** Refuses a start when the project's children already running reach `AGENT_CEILING` (#561) */
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 < AGENT_CEILING) return null;
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((resolve11) => timer = setTimeout(() => resolve11("bound"), ASK_WAIT_MS));
114304
- const aborted2 = new Promise((resolve11) => {
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 = () => resolve11("aborted");
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((resolve11) => {
114347
- if (signal.aborted) return resolve11(null);
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-${randomUUID9()}`;
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) => resolve11(d2 === "always" || d2 === "always_project" ? "allow" : 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
- resolve11(null);
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-${randomUUID9()}`;
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: resolve11
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
- resolve11(null);
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: readFile5 } = await import("node:fs/promises");
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 readFile5(p.path)).toString("base64");
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 readFile5(a.path)).toString("base64") };
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 mkdir6, readdir as readdir8, readFile as readFile4, rename as rename4, stat as stat5, unlink as unlink2, writeFile as writeFile4 } from "node:fs/promises";
115147
- import { basename as basename6, join as join29 } from "node:path";
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 mkdir6(this.dir, { recursive: true });
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 = join29(this.dir, THEME_SCHEMA_FILE);
115515
+ const path2 = join30(this.dir, THEME_SCHEMA_FILE);
115193
115516
  const text2 = `${JSON.stringify(themeFileJsonSchema(), null, 2)}
115194
115517
  `;
115195
- const current = await readFile4(path2, "utf8").catch(() => null);
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 readdir8(this.dir).catch(() => []);
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 stat5(path2);
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 readFile4(path2, "utf8"));
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 mkdir6(this.dir, { recursive: true });
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 stat5(source);
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 readFile4(source, "utf8");
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 join29(this.dir, `${id2}.json`);
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 stat5(this.pathOf(id2)).then(
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 writeFile4(tmp, text2, "utf8");
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 existsSync19 } from "node:fs";
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 && existsSync19(fromEnv)) return fromEnv;
116835
+ if (fromEnv && existsSync20(fromEnv)) return fromEnv;
116513
116836
  for (const candidate of ["/bin/zsh", "/bin/bash", "/bin/sh"]) {
116514
- if (existsSync19(candidate)) return candidate;
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 existsSync20 } from "node:fs";
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 existsSync20(path2) ? path2 : null;
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, resolve11 = (f) => resolveCommand(f)) {
116726
- const launch2 = resolve11(file2);
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, resolve11) {
116734
- const cmd = commandFor(file2, args, resolve11);
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((resolve11) => setTimeout(resolve11, ms3)))
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 join30 } from "node:path";
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 = join30(dirname10(dbPath2), "host.lock");
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(join30(dirname10(dbPath2), OWNERSHIP_FILE));
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((resolve11, reject) => {
117994
+ return new Promise((resolve12, reject) => {
117672
117995
  const list = this.waiters.get(op) ?? [];
117673
- list.push({ resolve: resolve11, reject });
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 existsSync21, readFileSync as readFileSync14 } from "node:fs";
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((resolve11, reject) => {
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) => resolve11({ code: code2, stdout, stderr }));
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((resolve11, reject) => {
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 resolve11();
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((resolve11) => {
118204
- child.once("exit", () => resolve11());
118526
+ await new Promise((resolve12) => {
118527
+ child.once("exit", () => resolve12());
118205
118528
  child.kill("SIGTERM");
118206
118529
  setTimeout(() => {
118207
118530
  child.kill("SIGKILL");
118208
- resolve11();
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((resolve11) => {
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
- resolve11(true);
118545
+ resolve12(true);
118223
118546
  });
118224
- s.once("error", () => resolve11(false));
118547
+ s.once("error", () => resolve12(false));
118225
118548
  });
118226
118549
  }
118227
118550
  function portFree(port2) {
118228
- return new Promise((resolve11) => {
118551
+ return new Promise((resolve12) => {
118229
118552
  const probe = createServer3();
118230
- probe.once("error", () => resolve11(false));
118231
- probe.listen(port2, "127.0.0.1", () => probe.close(() => resolve11(true)));
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((resolve11, reject) => {
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(() => resolve11(port2));
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 (existsSync21(file2)) return readFileSync14(file2, "utf8");
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((resolve11) => {
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
- resolve11(parseProbe(t, run));
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 join31, normalize, parse as parse6, sep as sep12 } from "node:path";
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(join31(base2, seg));
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(join31(base2, name));
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 = join31(opts.home, path2.slice(2));
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 = join31(opts.home, ".ssh", 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((resolve11) => {
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
- resolve11(r);
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 ?? join31(home, ".ssh", "config");
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 resolve11 = opts.resolve ?? ((alias) => resolveAlias(alias, opts));
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 resolve11(listed[i].alias);
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((resolve11, reject) => {
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: resolve11, reject, timer });
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((resolve11) => {
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
- resolve11(v);
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((resolve11) => {
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
- resolve11(true);
120048
+ resolve12(true);
119726
120049
  };
119727
120050
  const timer = setTimeout(() => {
119728
120051
  this.helloWaiters.delete(waiter);
119729
- void hold().then(() => resolve11(false));
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 ? "c6afa2e" : "dev";
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 = join32(homedir14(), isDev ? DATA_DIR_DEV : DATA_DIR);
120900
- const legacy = join32(homedir14(), isDev ? DATA_DIR_LEGACY.dev : DATA_DIR_LEGACY.prod);
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 join32(dir, "store.db");
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
- join32(dirname11(dbPath), "worktrees"),
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(join32(dataRoot(), "themes"), () => events.push({ type: "themes_changed" }));
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(join32(dirname11(dbPath), "host-errors.log"), (line) => console.error(line)),
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)