@centralu/win32-x64 0.1.0-beta.37 → 0.1.0-beta.38

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.
Binary file
@@ -1,9 +1,9 @@
1
1
  {
2
- "commit": "49153ff",
2
+ "commit": "bab6469",
3
3
  "protocolVersion": 1,
4
4
  "hostProof": 1,
5
5
  "tokenOnStdin": 1,
6
- "builtAt": "2026-10-25T08:20:16.645Z",
6
+ "builtAt": "2026-10-25T19:49:20.429Z",
7
7
  "runtime": "system-node",
8
8
  "platform": "win32",
9
9
  "arch": "x64"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "centralu",
3
- "version": "0.1.0-beta.37",
3
+ "version": "0.1.0-beta.38",
4
4
  "description": "Run, watch and steer several Claude Code and Codex CLI sessions from one window",
5
5
  "license": "MIT",
6
6
  "author": "ijun17",
@@ -38,9 +38,9 @@
38
38
  "win32"
39
39
  ],
40
40
  "optionalDependencies": {
41
- "@centralu/darwin-arm64": "0.1.0-beta.37",
42
- "@centralu/linux-arm64": "0.1.0-beta.37",
43
- "@centralu/linux-x64": "0.1.0-beta.37",
44
- "@centralu/win32-x64": "0.1.0-beta.37"
41
+ "@centralu/darwin-arm64": "0.1.0-beta.38",
42
+ "@centralu/linux-arm64": "0.1.0-beta.38",
43
+ "@centralu/linux-x64": "0.1.0-beta.38",
44
+ "@centralu/win32-x64": "0.1.0-beta.38"
45
45
  }
46
46
  }
@@ -43053,9 +43053,11 @@ import { hostname as hostname5 } from "node:os";
43053
43053
  import { dirname as dirname23, join as join52 } from "node:path";
43054
43054
  import { mkdirSync as mkdirSync19, writeSync as writeSync3 } from "node:fs";
43055
43055
 
43056
+ // packages/protocol/src/version.generated.ts
43057
+ var APP_VERSION = "0.1.0-beta.38";
43058
+
43056
43059
  // packages/protocol/src/brand.ts
43057
43060
  var APP_NAME = "Centralu";
43058
- var APP_VERSION = "0.1.0-beta.37";
43059
43061
  var APP_SLUG = "centralu";
43060
43062
  var CLIENT_INFO = { name: APP_SLUG, title: APP_NAME, version: APP_VERSION };
43061
43063
  var DATA_DIR = ".centralu";
@@ -104106,7 +104108,7 @@ var NOT_CONNECTED = "not connected until the person connects it";
104106
104108
  var NOT_CONNECTED_PHRASE = "is not connected, and nothing here connects it by itself; ask the person to connect it (Settings, Machines, Connect)";
104107
104109
  function awayPhrase(why) {
104108
104110
  if (why === NOT_CONNECTED) return NOT_CONNECTED_PHRASE;
104109
- return why === NOT_REACHABLE ? `is ${NOT_REACHABLE}` : `could not be asked just now (${why})`;
104111
+ return why === NOT_REACHABLE || why.startsWith(`${NOT_REACHABLE};`) ? `is ${why}` : `could not be asked just now (${why})`;
104110
104112
  }
104111
104113
 
104112
104114
  // packages/agent-host/src/adapters/contract/surface.ts
@@ -113241,11 +113243,12 @@ var RemoteAgents = class {
113241
113243
  if (!origin || !key) {
113242
113244
  return { error: `This project has ${origin ? "a local folder as its origin remote" : "no origin remote"}, so no checkout of it on ${machine.name} can be told apart. Start the agent here, without machine.` };
113243
113245
  }
113244
- const all = await reader.projects();
113246
+ const all = await reader.projects({ machineId: machine.id });
113245
113247
  const away = all.away.find((a) => a.machineId === machine.id);
113246
113248
  if (away) {
113247
113249
  if (away.why === NOT_CONNECTED) return { error: `${machine.name} ${awayPhrase(NOT_CONNECTED)}, so nothing was started there. Try again once it is connected.` };
113248
- return { error: `${machine.name} cannot be reached (${away.why}), so nothing was started there. Try again once its link is back.` };
113250
+ const cannot = away.why.startsWith(`${NOT_REACHABLE};`) ? `is ${away.why}` : `cannot be reached (${away.why})`;
113251
+ return { error: `${machine.name} ${cannot}, so nothing was started there. Try again once its link is back.` };
113249
113252
  }
113250
113253
  const theirs = all.projects.filter((p) => p.machineId === machine.id);
113251
113254
  const read = await Promise.all(theirs.map(async (p) => ({ p, r: await reader.origin(p) })));
@@ -135715,7 +135718,7 @@ var AskProjectFlow = class {
135715
135718
  const exactHere = projects.some((p) => p.id === wanted);
135716
135719
  const hubs2 = this.hubs;
135717
135720
  const none = { projects: [], away: [] };
135718
- const [there, fromHubs] = (reader || hubs2) && !exactHere ? await Promise.all([reader ? reader.projects() : none, hubs2 ? hubs2.projects() : none]) : [none, none];
135721
+ const [there, fromHubs] = (reader || hubs2) && !exactHere ? await Promise.all([reader ? reader.projects({ ref: wanted }) : none, hubs2 ? hubs2.projects() : none]) : [none, none];
135719
135722
  const askable = [
135720
135723
  ...projects.map((p) => ({ id: p.id, name: p.name, machine: null, machineId: null })),
135721
135724
  ...there.projects.map((p) => ({ id: p.id, name: p.name, machine: p.machine, machineId: p.machineId })),
@@ -144805,6 +144808,13 @@ var AUTO_RETRY_DELAYS_MS = [10 * 60 * 1e3, 60 * 60 * 1e3, 6 * 60 * 60 * 1e3];
144805
144808
  var INSTALL_TIMEOUT_MS = 5 * 60 * 1e3;
144806
144809
  var NPM_RETRY_DELAY_MS = 3e4;
144807
144810
  var NPM_RETRY_WINDOW_MS = 10 * 60 * 1e3;
144811
+ var UNSTAMPED_VERSION = "0.0.0-dev";
144812
+ function installsByItself(build3, current) {
144813
+ return build3 !== "dev" && current !== UNSTAMPED_VERSION;
144814
+ }
144815
+ function hubUpdatesMachines(f) {
144816
+ return installsByItself(f.build, f.current) && f.autoApply && !f.draining;
144817
+ }
144808
144818
  function existingInstalledCopy() {
144809
144819
  const path2 = installedCopyPath();
144810
144820
  return existsSync22(path2) ? path2 : null;
@@ -144855,6 +144865,8 @@ var UpdateService = class {
144855
144865
  constructor(publish, deps = {}) {
144856
144866
  this.publish = publish;
144857
144867
  this.deps = {
144868
+ current: deps.current ?? APP_VERSION,
144869
+ build: deps.build ?? "",
144858
144870
  fetchLatest: deps.fetchLatest ?? fetchLatestFromRegistry,
144859
144871
  run: deps.run ?? runCommand,
144860
144872
  installedCopy: deps.installedCopy ?? existingInstalledCopy,
@@ -144880,13 +144892,13 @@ var UpdateService = class {
144880
144892
  /*
144881
144893
  * The honest source for "which build is this".
144882
144894
  *
144883
- * `APP_VERSION` is what the app was compiled from, and `tooling/brand.test.ts`
144884
- * fails the build if it disagrees with the npm packages that carry it. The
144885
- * workspace root's package.json is not it — that one is private, has no version
144886
- * anyone installs, and would have read `0.0.0` forever without ever being wrong
144887
- * enough to notice.
144895
+ * `APP_VERSION` is what the app was compiled from: the version a release stamped
144896
+ * into the build with the npm packages that carry it (#1501, `tooling/brand.test.ts`),
144897
+ * or the placeholder `0.0.0-dev` of a source tree. The workspace root's package.json
144898
+ * is not it — that one is private, has no version anyone installs, and would have
144899
+ * read `0.0.0` forever without ever being wrong enough to notice.
144888
144900
  */
144889
- current: APP_VERSION,
144901
+ current: this.deps.current,
144890
144902
  latest: null,
144891
144903
  newer: false,
144892
144904
  auto: this.deps.readAuto(),
@@ -144983,6 +144995,7 @@ var UpdateService = class {
144983
144995
  */
144984
144996
  installIfAutomatic() {
144985
144997
  if (!this.status.autoApply || !this.status.newer || !this.status.latest) return;
144998
+ if (!installsByItself(this.deps.build, this.deps.current)) return;
144986
144999
  if (this.status.phase === "idle") {
144987
145000
  void this.apply();
144988
145001
  return;
@@ -145006,6 +145019,7 @@ var UpdateService = class {
145006
145019
  this.retryTimer = this.deps.schedule(() => {
145007
145020
  this.retryTimer = null;
145008
145021
  if (this.stopped || !this.status.autoApply || this.status.phase !== "failed" || !this.status.newer || !this.status.latest) return;
145022
+ if (!installsByItself(this.deps.build, this.deps.current)) return;
145009
145023
  void this.apply();
145010
145024
  }, delay);
145011
145025
  }
@@ -147380,8 +147394,8 @@ function failure2(target, code2, detail) {
147380
147394
  case "dev_dirty":
147381
147395
  return `The working copy on ${target} has ${detail} uncommitted change${detail === "1" ? "" : "s"}; commit or stash them, or pass --allow-dirty to build them too. Nothing was built`;
147382
147396
  case "dev_base":
147383
- if (/^0\.0\.0-dev(?:\.|$)/.test(detail))
147384
- return `The working copy on ${target} carries no release version (${detail}): its version is stamped from a release tag at build time (#1501), which this dev build cannot do. Nothing was built`;
147397
+ if (!detail || /^0\.0\.0-dev(?:\.|$)/.test(detail))
147398
+ return `The working copy on ${target} has no release tag (v*) reachable from its HEAD, which a dev build takes its version from (#1501); fetch the tags there (git fetch --tags) and try again. Nothing was built`;
147385
147399
  return `The working copy on ${target} is version ${detail || "(none)"}; a dev build is cut from a prerelease (x.y.z-beta.N)`;
147386
147400
  case "dev_protocol": {
147387
147401
  const [have = "?", want = "?"] = detail.split(" ");
@@ -148220,7 +148234,11 @@ var KEEPS_TRYING = "reconnecting";
148220
148234
  var WAITS_FOR_PERSON = "press Connect to try again";
148221
148235
  var NEXT_STEP = new RegExp(`; (${KEEPS_TRYING}|asking the machine again|${WAITS_FOR_PERSON})$`);
148222
148236
  var ATTEMPT_WAIT_MS = 6e4;
148237
+ var CALL_WAIT_MS = 1e4;
148238
+ var CALL_FAILURE_KEPT_MS = 6e4;
148239
+ var CALL_WINDOW_MS = 15e3;
148223
148240
  var AWAY = /* @__PURE__ */ new Set(["unreachable", "away", "not_running", "refused", "stopped"]);
148241
+ var CALL_RETRIES = /* @__PURE__ */ new Set(["unreachable", "away", "not_running", "refused"]);
148224
148242
  var LinkedMachineConnection = class {
148225
148243
  constructor(record2, tunnel, deps) {
148226
148244
  this.record = record2;
@@ -148335,6 +148353,10 @@ var LinkedMachineConnection = class {
148335
148353
  nextAttemptAt = null;
148336
148354
  /** Calls waiting for an attempt to connect to end (`settled`) */
148337
148355
  statusWaiters = /* @__PURE__ */ new Set();
148356
+ /** When a call last started an attempt (`reachForCall`), and how the attempt a call waited on ended, with when */
148357
+ lastCallAttemptAt = 0;
148358
+ callFailure = null;
148359
+ callFailureAt = 0;
148338
148360
  get id() {
148339
148361
  return this.record.id;
148340
148362
  }
@@ -148376,7 +148398,11 @@ var LinkedMachineConnection = class {
148376
148398
  }
148377
148399
  /** Resolves once no attempt is under way, or after `waitMs` (a minute): connecting took 2 to 4 s on a LAN, a host started there adds its start */
148378
148400
  settled(waitMs = this.deps.attemptWaitMs ?? ATTEMPT_WAIT_MS) {
148379
- if (!this.attemptUnderWay) return Promise.resolve();
148401
+ return this.waitUntil(() => !this.attemptUnderWay, waitMs);
148402
+ }
148403
+ /** Resolves once `ready()` holds (checked at every change of status and at the end of every opening), or after `waitMs` */
148404
+ waitUntil(ready, waitMs) {
148405
+ if (ready()) return Promise.resolve();
148380
148406
  return new Promise((resolve21) => {
148381
148407
  const done2 = () => {
148382
148408
  clearTimeout(timer);
@@ -148384,13 +148410,63 @@ var LinkedMachineConnection = class {
148384
148410
  resolve21();
148385
148411
  };
148386
148412
  const waiter = () => {
148387
- if (!this.attemptUnderWay) done2();
148413
+ if (ready()) done2();
148388
148414
  };
148389
148415
  const timer = setTimeout(done2, waitMs);
148390
148416
  timer.unref?.();
148391
148417
  this.statusWaiters.add(waiter);
148392
148418
  });
148393
148419
  }
148420
+ /**
148421
+ * An agent's call needs this machine (#1532): with Auto reconnect on and the link down, it does what the person's Connect
148422
+ * does (`connect`, the one action that opens a link now) instead of waiting out the backoff, and waits for it a short
148423
+ * bound (`CALL_WAIT_MS`). The caller reads `reachable` afterwards: connected in time, the call goes through; not, it is
148424
+ * answered as not reachable with `whyDown`, and the backoff goes on from where it was (`keepBackoff`).
148425
+ *
148426
+ * - one attempt at a time: a call that finds one under way (the backoff's, the person's press, another call's) waits on
148427
+ * it and starts none; so do ten calls at once, and a press at the same moment as a call;
148428
+ * - calls cannot defeat the backoff: at most one attempt started by a call per machine within `CALL_WINDOW_MS`; a call
148429
+ * inside the window waits for nothing and is answered at once;
148430
+ * - with Auto reconnect off nothing is started (#1341): the call is answered as not connected, and the person is named.
148431
+ * Neither does it when the person disconnected the machine, the host was stopped there, an operation holds the link
148432
+ * or the versions differ: a Connect does not help those, or is the person's to press
148433
+ */
148434
+ async reachForCall() {
148435
+ if (this.reachable) return;
148436
+ const until = Date.now() + (this.deps.callWaitMs ?? CALL_WAIT_MS);
148437
+ const left = () => Math.max(0, until - Date.now());
148438
+ await this.waitUntil(() => this.reachable || !this.redialing, left());
148439
+ if (this.reachable) return;
148440
+ if (this.attemptUnderWay) {
148441
+ await this.settled(Math.min(left(), this.deps.attemptWaitMs ?? ATTEMPT_WAIT_MS));
148442
+ return this.noteCallFailure();
148443
+ }
148444
+ if (this.stopped || this.parked || this.held || this.inOperation() || !this.autoReconnect || this.redialing) return;
148445
+ if (!CALL_RETRIES.has(this.status)) return;
148446
+ const now = Date.now();
148447
+ if (now - this.lastCallAttemptAt < (this.deps.callWindowMs ?? CALL_WINDOW_MS)) return;
148448
+ this.lastCallAttemptAt = now;
148449
+ this.connect({ start: false, keepBackoff: true });
148450
+ await this.settled(Math.min(left(), this.deps.attemptWaitMs ?? ATTEMPT_WAIT_MS));
148451
+ this.noteCallFailure();
148452
+ }
148453
+ /** How the attempt a call waited on ended when it did not connect: the machine's own words. One still going is told as it is (`whyDown`) */
148454
+ noteCallFailure() {
148455
+ if (this.reachable || this.attemptUnderWay) return;
148456
+ this.callFailure = this.error?.replace(NEXT_STEP, "") ?? null;
148457
+ this.callFailureAt = Date.now();
148458
+ }
148459
+ /**
148460
+ * What the answer to a call that could not take it says of the attempt to connect the machine (#1532), as a clause: that
148461
+ * one is still under way, or that the one a call made last failed, with the machine's own words. Null once the link is
148462
+ * up, and when no attempt was made or told of
148463
+ */
148464
+ get whyDown() {
148465
+ if (this.reachable) return null;
148466
+ if (this.attemptUnderWay) return "an attempt to connect it is still under way";
148467
+ if (!this.callFailure || Date.now() - this.callFailureAt > CALL_FAILURE_KEPT_MS) return null;
148468
+ return `the attempt to connect it just now failed: ${this.callFailure}`;
148469
+ }
148394
148470
  /** An install, update or other operation the person started is running: its retries are part of it (`retryLater`) */
148395
148471
  inOperation() {
148396
148472
  return this.held;
@@ -148526,7 +148602,9 @@ var LinkedMachineConnection = class {
148526
148602
  * The person's Connect (`machines.reconnect`, #1134): opens now, forgetting the backoff, also a disconnected link and
148527
148603
  * also with Auto reconnect off (#1341), where it is the only way to try again. It is also the person's Start
148528
148604
  * (#1143): a host is started there even where they stopped it or the link stopped starting them. `start: false` is
148529
- * the orchestrator's reconnect without its card, which opens the link and starts nothing the link would not
148605
+ * the orchestrator's reconnect without its card, which opens the link and starts nothing the link would not.
148606
+ * `keepBackoff` is an agent's call that needs the machine (`reachForCall`, #1532): it opens now too, but the backoff
148607
+ * goes on from the step it was at instead of starting over, so calls cannot hold it at its short steps
148530
148608
  */
148531
148609
  connect(opts = {}) {
148532
148610
  if (opts.start !== false) {
@@ -148535,7 +148613,7 @@ var LinkedMachineConnection = class {
148535
148613
  this.restartsSpent = false;
148536
148614
  }
148537
148615
  if (this.parked) return this.start();
148538
- this.reconnect();
148616
+ this.reconnect(opts.keepBackoff === true);
148539
148617
  }
148540
148618
  /**
148541
148619
  * Closes the link and tries none until the person connects it again (#1134). The machine stays linked; its sessions
@@ -148580,9 +148658,9 @@ var LinkedMachineConnection = class {
148580
148658
  this.start();
148581
148659
  }
148582
148660
  /** Opens again now, forgetting the backoff (`machines.reconnect`, or the person accepted the versions) */
148583
- reconnect() {
148661
+ reconnect(keepBackoff = false) {
148584
148662
  this.clearRetry();
148585
- this.attempt = 0;
148663
+ if (!keepBackoff) this.attempt = 0;
148586
148664
  const under = this.opening;
148587
148665
  if (under) {
148588
148666
  if (this.status === "away") this.setStatus("connecting", null);
@@ -148995,6 +149073,7 @@ var LinkedMachineConnection = class {
148995
149073
  setStatus(status, error51) {
148996
149074
  if (this.status === "connected" && status !== "connected") this.seen();
148997
149075
  const changed = status !== this.status || error51 !== this.error;
149076
+ if (status === "connected") this.callFailure = null;
148998
149077
  this.status = status;
148999
149078
  this.error = error51;
149000
149079
  if (changed) {
@@ -150301,9 +150380,12 @@ function linkedSessions(machines, route = notRouted, hubName2, options = {}) {
150301
150380
  for (const m of machines()) if (m.id === id3) return m;
150302
150381
  return null;
150303
150382
  };
150304
- const away = (m) => m.waitsForPerson ? `${m.name} ${awayPhrase(NOT_CONNECTED)}, then try again` : `${m.name} is ${NOT_REACHABLE}; try again once its link is back`;
150383
+ const away = (m) => m.waitsForPerson ? `${m.name} ${awayPhrase(NOT_CONNECTED)}, then try again` : `${m.name} is ${notReachable(m)}; try again once its link is back`;
150384
+ const notReachable = (m) => m.whyDown ? `${NOT_REACHABLE}; ${oneLine3(m.whyDown, 300)}` : NOT_REACHABLE;
150305
150385
  const settle = async (m) => {
150306
- if (m.attemptUnderWay) await m.settled?.();
150386
+ if (m.reachable) return;
150387
+ if (m.reachForCall) await m.reachForCall();
150388
+ else if (m.attemptUnderWay) await m.settled?.();
150307
150389
  };
150308
150390
  const attempt = async (m, run4) => {
150309
150391
  await settle(m);
@@ -150373,26 +150455,36 @@ function linkedSessions(machines, route = notRouted, hubName2, options = {}) {
150373
150455
  }
150374
150456
  return out;
150375
150457
  };
150376
- const everyProject = async () => {
150458
+ const projectsOf = async (m, tried = false) => {
150377
150459
  const away2 = [];
150378
- const perMachine = await Promise.all(
150379
- [...machines()].map(async (m) => {
150380
- const mirrored = (why) => {
150381
- away2.push({ machine: m.name, machineId: m.id, why });
150382
- return lastProjects(m).map((p) => ({ ...p, away: why }));
150383
- };
150384
- if (!m.reachable && m.attemptUnderWay) return lastProjects(m);
150385
- if (!m.reachable) return mirrored(m.waitsForPerson ? NOT_CONNECTED : NOT_REACHABLE);
150386
- try {
150387
- const list = await raceTimeout(m.call("projects.list", {}), lookupMs, TIMED_OUT);
150388
- if (list === TIMED_OUT) throw new Error(notWithin(lookupMs));
150389
- return (Array.isArray(list) ? list : []).map((raw) => projectOf(m, raw)).filter((p) => p !== null);
150390
- } catch (err) {
150391
- return mirrored(cause(m, err));
150392
- }
150393
- })
150394
- );
150395
- return { projects: perMachine.flat(), away: away2 };
150460
+ const mirrored = (why) => {
150461
+ away2.push({ machine: m.name, machineId: m.id, why });
150462
+ return lastProjects(m).map((p) => ({ ...p, away: why }));
150463
+ };
150464
+ if (!m.reachable && m.attemptUnderWay && !tried) return { projects: lastProjects(m), away: away2 };
150465
+ if (!m.reachable) return { projects: mirrored(m.waitsForPerson ? NOT_CONNECTED : notReachable(m)), away: away2 };
150466
+ try {
150467
+ const list = await raceTimeout(m.call("projects.list", {}), lookupMs, TIMED_OUT);
150468
+ if (list === TIMED_OUT) throw new Error(notWithin(lookupMs));
150469
+ return { projects: (Array.isArray(list) ? list : []).map((raw) => projectOf(m, raw)).filter((p) => p !== null), away: away2 };
150470
+ } catch (err) {
150471
+ return { projects: mirrored(cause(m, err)), away: away2 };
150472
+ }
150473
+ };
150474
+ const needs = (m, need3) => {
150475
+ if (need3.machineId === m.id) return true;
150476
+ const ref = need3.ref?.trim();
150477
+ if (!ref) return false;
150478
+ const slash = ref.indexOf("/");
150479
+ const prefix = slash > 0 ? ref.slice(0, slash).trim().toLowerCase() : null;
150480
+ if (prefix === m.id || prefix === m.name.toLowerCase() || ref.startsWith(`${m.id}.`)) return true;
150481
+ return lastProjects(m).some((p) => p.id === ref || p.name.toLowerCase() === ref.toLowerCase());
150482
+ };
150483
+ const everyProject = async (need3) => {
150484
+ const all = [...machines()];
150485
+ const wanted = need3 ? all.filter((m) => !m.reachable && needs(m, need3)) : [];
150486
+ const parts = await Promise.all(all.map(async (m) => wanted.includes(m) ? (await settle(m), projectsOf(m, true)) : projectsOf(m)));
150487
+ return { projects: parts.flatMap((p) => p.projects), away: parts.flatMap((p) => p.away) };
150396
150488
  };
150397
150489
  return {
150398
150490
  async send(sessionId, text2, fromName, ask) {
@@ -150410,7 +150502,7 @@ function linkedSessions(machines, route = notRouted, hubName2, options = {}) {
150410
150502
  },
150411
150503
  async findProjects(ref) {
150412
150504
  const wanted = ref.trim();
150413
- return (await everyProject()).projects.filter((p) => p.id === wanted || p.name === wanted);
150505
+ return (await everyProject({ ref: wanted })).projects.filter((p) => p.id === wanted || p.name === wanted);
150414
150506
  },
150415
150507
  projects: everyProject,
150416
150508
  async projectSessions(project2) {
@@ -150676,7 +150768,7 @@ function linkedSessions(machines, route = notRouted, hubName2, options = {}) {
150676
150768
  const m = owner(sessionId);
150677
150769
  if (!m) return { ok: false, error: `Not a session of a linked machine: ${sessionId}` };
150678
150770
  await settle(m);
150679
- if (!m.reachable) return { ok: false, error: m.waitsForPerson ? away(m) : `${m.name} is ${NOT_REACHABLE}` };
150771
+ if (!m.reachable) return { ok: false, error: m.waitsForPerson ? away(m) : `${m.name} is ${notReachable(m)}` };
150680
150772
  try {
150681
150773
  const answer = await m.call("host.lastAnswer", { sessionId: m.q.strip(sessionId, "The session"), maxChars: Math.min(maxChars, ANSWER_MAX_CHARS2) });
150682
150774
  const r5 = HostLastAnswerResult.safeParse(answer);
@@ -150716,7 +150808,7 @@ function linkedSessions(machines, route = notRouted, hubName2, options = {}) {
150716
150808
  const m = owner(sessionId);
150717
150809
  if (!m) return { ok: false, error: `Not a session of a linked machine: ${sessionId}` };
150718
150810
  await settle(m);
150719
- if (!m.reachable) return { ok: false, error: m.waitsForPerson ? away(m) : `${m.name} is not reachable right now; read it again once its link is back` };
150811
+ if (!m.reachable) return { ok: false, error: m.waitsForPerson ? away(m) : `${m.name} is ${notReachable(m)}; read it again once its link is back` };
150720
150812
  const toolMode = toolLinesOf(opts?.tools);
150721
150813
  try {
150722
150814
  const answer = await m.call("host.readSession", {
@@ -151126,7 +151218,7 @@ var MachineUnreachableError = class extends Error {
151126
151218
  retryable = true;
151127
151219
  data;
151128
151220
  constructor(machine) {
151129
- super(`${machine.name} is not reachable right now; this works again once its link is back`);
151221
+ super(`${machine.name} is not reachable right now${machine.whyDown ? `; ${machine.whyDown}` : ""}; this works again once its link is back`);
151130
151222
  this.data = { machine: machine.id, reason: "unreachable" };
151131
151223
  }
151132
151224
  };
@@ -151191,7 +151283,8 @@ var Router = class {
151191
151283
  for (const m of this.opts.machines()) if (m.id === id3) return m;
151192
151284
  return null;
151193
151285
  }
151194
- async forward(m, method, params) {
151286
+ async forward(m, method, params, ctx) {
151287
+ if (!m.reachable && ctx === void 0) await m.reachForCall?.();
151195
151288
  if (!m.reachable) throw new MachineUnreachableError(m);
151196
151289
  return m.call(method, params);
151197
151290
  }
@@ -151201,7 +151294,7 @@ var Router = class {
151201
151294
  const m2 = this.machineById(params.machine);
151202
151295
  if (!m2) throw rpcError(`No linked machine ${params.machine}`);
151203
151296
  const { machine: _machine2, ...rest } = params;
151204
- const r5 = await this.forward(m2, method, route.params ? route.params(rest, m2.q) : rest);
151297
+ const r5 = await this.forward(m2, method, route.params ? route.params(rest, m2.q) : rest, ctx);
151205
151298
  return route.result ? route.result(r5, m2.q) : r5;
151206
151299
  }
151207
151300
  const m = this.owner(params[key]);
@@ -151212,7 +151305,7 @@ var Router = class {
151212
151305
  const done2 = this.opts.forwarding?.(method, params);
151213
151306
  let r;
151214
151307
  try {
151215
- r = await this.forward(m, method, p);
151308
+ r = await this.forward(m, method, p, ctx);
151216
151309
  } catch (e) {
151217
151310
  done2?.(false);
151218
151311
  throw e;
@@ -151247,7 +151340,7 @@ var Router = class {
151247
151340
  const { machine: _machine, ...own } = params;
151248
151341
  const p = route.orMachine ? own : { ...params };
151249
151342
  for (const key of route.keys) if (typeof p[key] === "string") p[key] = target.q.strip(p[key], "The id");
151250
- const r = await this.forward(target, method, p);
151343
+ const r = await this.forward(target, method, p, ctx);
151251
151344
  return route.result ? route.result(r, target.q) : r;
151252
151345
  }
151253
151346
  async byMachine(method, params, route, ctx) {
@@ -151259,7 +151352,7 @@ var Router = class {
151259
151352
  const shared = await this.sameAccountAnswer(m, method, rest, ctx);
151260
151353
  if (shared) return route.result ? route.result(shared, m.q) : shared;
151261
151354
  }
151262
- const r = await this.forward(m, method, rest);
151355
+ const r = await this.forward(m, method, rest, ctx);
151263
151356
  return route.result ? route.result(r, m.q) : r;
151264
151357
  }
151265
151358
  /**
@@ -151915,7 +152008,7 @@ var handedToken = launch.tokenOnStdin ? await readHandedToken(process.stdin).cat
151915
152008
  console.error(`[agent-host] the launcher did not hand over the token: ${e.message}`);
151916
152009
  process.exit(1);
151917
152010
  }) : launch.token;
151918
- var BUILD = true ? "49153ff" : "dev";
152011
+ var BUILD = true ? "bab6469" : "dev";
151919
152012
  var { values } = parseArgs({
151920
152013
  options: {
151921
152014
  port: { type: "string", default: "5175" },
@@ -152178,6 +152271,8 @@ var { auto: autoUpdateCheck, autoApply: autoApplyUpdates } = updateSwitches(stor
152178
152271
  var themes = new ThemeFiles(join52(dataRoot(), "themes"), () => events.push({ type: "themes_changed" }));
152179
152272
  await themes.start().catch((e) => console.error(`[themes] the themes folder is unavailable: ${e.message}`));
152180
152273
  var updates = new UpdateService((status) => events.push({ type: "update_status", status }), {
152274
+ // A host from source never installs by itself, whatever is stored (#1501 review, `installsByItself`)
152275
+ build: BUILD,
152181
152276
  // On by default when nothing has been saved yet. Since the check is read-only and every failure
152182
152277
  // is swallowed, leaving it on costs nothing, while leaving it off traps **someone who never once
152183
152278
  // opens settings** on an old version forever — that person is exactly the one most likely to stay
@@ -152279,7 +152374,7 @@ var links = new Links({
152279
152374
  // When agents compact: this hub's setting governs the machines it links (#1376)
152280
152375
  autoCompact: () => autoCompactOf(mgr.uiPreferences()),
152281
152376
  // Automatic updates cover the linked machines too (#1508): one on an older release follows this hub's version; none starts mid-swap
152282
- autoUpdate: () => autoApplyUpdates.read() && !hostDrain.draining,
152377
+ autoUpdate: () => hubUpdatesMachines({ build: BUILD, current: APP_VERSION, autoApply: autoApplyUpdates.read(), draining: hostDrain.draining }),
152283
152378
  reverse: reverseCalls({
152284
152379
  allowed: (machineId) => store.machines.linkedMachineAsks(machineId),
152285
152380
  projects: () => mgr.projectsForMachines(),
@@ -462,12 +462,24 @@ function atLeast(a, b) {
462
462
  /** A release a dev build can be cut from: a prerelease, since `<base>.local.N` is SemVer only on one (`isLocalBuildVersion`) */
463
463
  const DEV_BASE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*$/
464
464
  /**
465
- * The placeholder a working copy's version files carry once its release version is stamped from the tag at build time
466
- * (#1501). A dev build of it would say it is `0.0.0-dev.local.N`, older than every release, so it is refused, by hubs
467
- * carrying this from before the switch lands.
465
+ * The placeholder a working copy's version files carry since its release version is stamped from the tag at build time
466
+ * (#1501). Never a dev build's base: `0.0.0-dev.local.N` would be older than every release.
468
467
  */
469
468
  const PLACEHOLDER = /^0\.0\.0-dev(?:\.|$)/
470
469
 
470
+ /**
471
+ * The release a dev build of the working copy is cut from, as `scripts/app-version.mjs` resolves it for a local build
472
+ * (#1501): the newest release tag reachable from HEAD there. A commit from before the switch, in a clone without tags,
473
+ * still carries its version in the shim's manifest, which is taken then. An empty string when neither says.
474
+ */
475
+ function devBase(repo, shim) {
476
+ const r = spawnSync('git', ['-C', repo, 'describe', '--tags', '--abbrev=0', '--match', 'v[0-9]*'], { cwd: repo, encoding: 'utf8', windowsHide: true })
477
+ const tag = r.status === 0 ? String(r.stdout).trim().replace(/^v/, '') : ''
478
+ if (tag) return tag
479
+ const own = String(shim.version ?? '')
480
+ return PLACEHOLDER.test(own) ? '' : own
481
+ }
482
+
471
483
  /** git in the working copy, by name on PATH as the person runs it there; what it printed, or a refusal */
472
484
  function git(repo, args) {
473
485
  const r = spawnSync('git', ['-C', repo, ...args], { cwd: repo, encoding: 'utf8', windowsHide: true, maxBuffer: 16 * 1024 * 1024 })
@@ -514,7 +526,7 @@ export function devBuild(p, { dataDir = process.env.CC_DATA_DIR || join(homedir(
514
526
  const changes = git(repo, ['status', '--porcelain']).split('\n').filter((line) => line.trim()).length
515
527
  if (changes > 0 && !p.allowDirty) throw new Refusal('dev_dirty', String(changes))
516
528
  const shim = JSON.parse(readFileSync(join(repo, 'packaging', 'npm', 'centralu', 'package.json'), 'utf8'))
517
- const base = String(shim.version ?? '')
529
+ const base = devBase(repo, shim)
518
530
  if (!DEV_BASE.test(base) || base.includes('.local.') || PLACEHOLDER.test(base)) throw new Refusal('dev_base', base)
519
531
  const protocol = Number(/export const PROTOCOL_VERSION = (\d+)/.exec(readFileSync(join(repo, 'packages', 'protocol', 'src', 'envelope.ts'), 'utf8'))?.[1])
520
532
  if (protocol !== p.protocol) throw new Refusal('dev_protocol', `${protocol} ${p.protocol}`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@centralu/win32-x64",
3
- "version": "0.1.0-beta.37",
3
+ "version": "0.1.0-beta.38",
4
4
  "description": "Centralu app folder for Windows on x86-64",
5
5
  "license": "MIT",
6
6
  "author": "ijun17",