@centralu/host-linux-arm64 0.1.0-beta.50 → 0.1.0-beta.52

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/centralu-keeper CHANGED
Binary file
@@ -1,11 +1,11 @@
1
1
  {
2
- "commit": "3bbc42b",
2
+ "commit": "62e7369",
3
3
  "protocolVersion": 1,
4
4
  "hostProof": 1,
5
5
  "tokenOnStdin": 1,
6
6
  "verifiers": 1,
7
7
  "keyPairing": 1,
8
- "builtAt": "2026-11-06T23:11:51.476Z",
8
+ "builtAt": "2026-11-07T06:59:13.773Z",
9
9
  "runtime": "system-node",
10
10
  "platform": "linux",
11
11
  "arch": "arm64"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "centralu",
3
- "version": "0.1.0-beta.50",
3
+ "version": "0.1.0-beta.52",
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.50",
42
- "@centralu/linux-arm64": "0.1.0-beta.50",
43
- "@centralu/linux-x64": "0.1.0-beta.50",
44
- "@centralu/win32-x64": "0.1.0-beta.50"
41
+ "@centralu/darwin-arm64": "0.1.0-beta.52",
42
+ "@centralu/linux-arm64": "0.1.0-beta.52",
43
+ "@centralu/linux-x64": "0.1.0-beta.52",
44
+ "@centralu/win32-x64": "0.1.0-beta.52"
45
45
  }
46
46
  }
package/host/main.mjs CHANGED
@@ -43050,7 +43050,7 @@ import { dirname as dirname27, join as join59 } from "node:path";
43050
43050
  import { mkdirSync as mkdirSync21, readFileSync as readFileSync27, writeSync as writeSync4 } from "node:fs";
43051
43051
 
43052
43052
  // packages/protocol/src/version.generated.ts
43053
- var APP_VERSION = "0.1.0-beta.50";
43053
+ var APP_VERSION = "0.1.0-beta.52";
43054
43054
 
43055
43055
  // packages/protocol/src/brand.ts
43056
43056
  var APP_NAME = "Centralu";
@@ -108241,6 +108241,9 @@ function lowestPreset(...presets) {
108241
108241
  function presetWithin(child, parent) {
108242
108242
  return PRESET_RANK[child] <= PRESET_RANK[parent];
108243
108243
  }
108244
+ function aboveCallerError(name, child, caller) {
108245
+ return presetWithin(child, caller) ? null : `"${name}" now has more permissions (${child}) than this session (${caller}), so this session cannot give it work. Tell the person.`;
108246
+ }
108244
108247
  var PARENT_MAIL = 'To write to it before you finish, call mail with `to: "parent"`. ';
108245
108248
  function canMailParent(child, senderId) {
108246
108249
  return child.askedBy === senderId && !!child.projectId;
@@ -113921,7 +113924,7 @@ var HANDLERS = {
113921
113924
  async recall({ query, limit }, { tools }) {
113922
113925
  const r = await tools.recall(query, limit);
113923
113926
  const unsearched = r.notSearched?.length ? `
113924
- Not searched: ${r.notSearched.join("; ")}.` : "";
113927
+ Not searched: ${r.notSearched.join(". ")}.` : "";
113925
113928
  if (r.hits.length === 0) return { text: `Nothing was found for "${query}". Try a different word.${unsearched}` };
113926
113929
  return {
113927
113930
  text: r.hits.map(
@@ -114345,6 +114348,7 @@ var START_AGENT_MODEL_CHECK_MS = 3e3;
114345
114348
  var START_AGENT_SETUP_MS = 2e5;
114346
114349
  var START_AGENT_CALL_MS = 27e4;
114347
114350
  var ASK_THE_PERSON = "Do not do this with your own subagents instead: tell the person what stopped it and let them decide.";
114351
+ var UNTRUSTED_START = `This session's project is not trusted, so it cannot start agents \u2014 the person can trust it from the project menu. ${ASK_THE_PERSON}`;
114348
114352
  function agentStartPending(req, lowMemory) {
114349
114353
  return { ok: true, state: "pending", sessionId: req.requestId, sessionName: "", tool: req.detail.tool, ...lowMemory ? { lowMemory } : {} };
114350
114354
  }
@@ -114385,21 +114389,6 @@ var AgentStartGates = class {
114385
114389
  * many. Counted by `agentCeilingError` with the running children, so starts at once cannot pass the ceiling together.
114386
114390
  */
114387
114391
  agentStartReserved = /* @__PURE__ */ new Map();
114388
- /**
114389
- * What a child's start rests on in its caller, read once the person allowed it and again once its session exists,
114390
- * right before its delegation is registered: the caller is still here, its project still registered and trusted
114391
- * (or the caller is `auto`), and it is still above the agent depth (#867), which `auto` does not open.
114392
- */
114393
- agentStartGateError(callerId, projectId) {
114394
- const caller = this.host.meta.get(callerId);
114395
- if (!caller) return "The session that asked is no longer here.";
114396
- if (projectId === ORCHESTRATOR_PLACE && caller.kind === "orchestrator") return agentDepthRefusal(this.host, callerId, "start agents");
114397
- const now = this.host.store.projects.listProjects().find((p) => p.id === projectId);
114398
- if (!now) return "This session's project is no longer registered.";
114399
- const auto = caller.permissionPreset === "auto";
114400
- if (now.trusted !== true && !auto) return `This session's project is no longer trusted, so it cannot start agents. ${ASK_THE_PERSON}`;
114401
- return agentDepthRefusal(this.host, callerId, "start agents");
114402
- }
114403
114392
  /**
114404
114393
  * A place under the per-project ceiling, checked and taken in one synchronous step (`agentStartReserved`), for a start
114405
114394
  * (`createAgentChild`) or a fork (`forkFor`) that waits on something before its delegation counts the child.
@@ -114439,6 +114428,16 @@ var AgentStartGates = class {
114439
114428
  return `${running2} agents ${whose} are already working, the most at once. Wait for one (${wait}) or stop one, then try again.`;
114440
114429
  }
114441
114430
  };
114431
+ function agentStartGateError(host, callerId, projectId) {
114432
+ const caller = host.meta.get(callerId);
114433
+ if (!caller) return "The session that asked is no longer here.";
114434
+ if (projectId === ORCHESTRATOR_PLACE && caller.kind === "orchestrator") return agentDepthRefusal(host, callerId, "start agents");
114435
+ const now = host.store.projects.listProjects().find((p) => p.id === projectId);
114436
+ if (!now) return "This session's project is no longer registered.";
114437
+ const auto = caller.permissionPreset === "auto";
114438
+ if (now.trusted !== true && !auto) return `This session's project is no longer trusted, so it cannot start agents. ${ASK_THE_PERSON}`;
114439
+ return agentDepthRefusal(host, callerId, "start agents");
114440
+ }
114442
114441
 
114443
114442
  // packages/agent-host/src/sessions/agent-consents.ts
114444
114443
  import { randomUUID as randomUUID14 } from "node:crypto";
@@ -114866,7 +114865,7 @@ var AgentStartFlow = class {
114866
114865
  return done2({ ok: false, error: "A session the orchestrator starts works outside any project, so it has no worktree. Start it without one, or hand the work to a project's session with send_to_session." });
114867
114866
  }
114868
114867
  if (project2.trusted !== true && !this.host.autoBoundaryOpen(callerId, "start in an untrusted project")) {
114869
- return done2({ 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. ${ASK_THE_PERSON}` });
114868
+ return done2({ ok: false, error: UNTRUSTED_START });
114870
114869
  }
114871
114870
  const waiting = [...this.host.children.agentStarts.values()].find(
114872
114871
  (r5) => !r5.ended && r5.req.callerId === callerId && r5.req.task === opts.task && (!opts.tool || r5.req.tool === opts.tool)
@@ -114978,7 +114977,7 @@ var AgentStartFlow = class {
114978
114977
  */
114979
114978
  async createAgentChild(callerId, project2, choice, progress, approvedPreset, requestId) {
114980
114979
  const done2 = (r) => ({ done: r });
114981
- const refused2 = this.agentStartGateError(callerId, project2.id);
114980
+ const refused2 = agentStartGateError(this.host, callerId, project2.id);
114982
114981
  if (refused2) return done2({ ok: false, error: refused2 });
114983
114982
  const caller = this.host.meta.get(callerId);
114984
114983
  if (approvedPreset && !presetWithin(caller.permissionPreset, approvedPreset)) {
@@ -115007,10 +115006,6 @@ var AgentStartFlow = class {
115007
115006
  release();
115008
115007
  }
115009
115008
  }
115010
- /** `AgentStartGates.agentStartGateError` (agent-start-gates.ts), kept on the flow for its callers */
115011
- agentStartGateError(...a) {
115012
- return this.gates.agentStartGateError(...a);
115013
- }
115014
115009
  /** `AgentStartGates.reservePlace` (agent-start-gates.ts), kept on the flow for its callers */
115015
115010
  reservePlace(...a) {
115016
115011
  return this.gates.reservePlace(...a);
@@ -115082,7 +115077,7 @@ var AgentStartFlow = class {
115082
115077
  recordAgentDepth(this.host, child.id, callerId);
115083
115078
  const callerNow = this.host.meta.get(callerId);
115084
115079
  const withdrawn = requestId ? this.host.children.agentStarts.get(requestId)?.ended : void 0;
115085
- const late = (withdrawn && "error" in withdrawn ? `The request was withdrawn while its session was being made. ${withdrawn.error}` : null) ?? this.agentStartGateError(callerId, project2.id) ?? (callerNow && !presetWithin(child.permissionPreset, callerNow.permissionPreset) ? `This session's permissions were lowered to ${callerNow.permissionPreset} while the session was being made.` : null);
115080
+ const late = (withdrawn && "error" in withdrawn ? `The request was withdrawn while its session was being made. ${withdrawn.error}` : null) ?? agentStartGateError(this.host, callerId, project2.id) ?? (callerNow && !presetWithin(child.permissionPreset, callerNow.permissionPreset) ? `This session's permissions were lowered to ${callerNow.permissionPreset} while the session was being made.` : null);
115086
115081
  const name = choice.name ?? (worktree ? void 0 : agentName(choice.task));
115087
115082
  if (late) return done2(await this.abandonChild(child, late, name));
115088
115083
  progress.stage = "created";
@@ -115757,7 +115752,7 @@ var AgentSessionActions = class {
115757
115752
  const seat = caller?.projectId ? "project" : caller?.kind === "orchestrator" ? "orchestrator" : null;
115758
115753
  const owns = seat === "orchestrator" ? child?.askedBy === callerId : startedHere || !deletingWithoutWorktree && this.host.autoBoundaryOpen(callerId, "session ownership");
115759
115754
  if (!caller || !seat || !child || !owns || child.projectId !== caller.projectId) {
115760
- return { ok: false, error: `${opts.sessionId} is not a session this session started. agent_session works only on sessions start_agent started here.${child ? "" : staleSessionHint(this.host, opts.sessionId, (s) => s.projectId === caller?.projectId && s.id !== callerId && (seat !== "orchestrator" || s.askedBy === callerId))}` };
115755
+ return { ok: false, error: `${opts.sessionId} is not a session this session started. agent_session works only on sessions start_agent started from here.${child ? "" : staleSessionHint(this.host, opts.sessionId, (s) => s.projectId === caller?.projectId && s.id !== callerId && (seat !== "orchestrator" || s.askedBy === callerId))}` };
115761
115756
  }
115762
115757
  if (opts.sessionId === callerId) return this.flow.agentAtRest(child);
115763
115758
  if (opts.action === "answer") return this.host.answerRoutedCard(callerId, child.id, opts.card, opts.text ?? "");
@@ -115829,12 +115824,8 @@ var AgentSessionActions = class {
115829
115824
  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 } : {}, ...note2 ? { note: note2 } : {} };
115830
115825
  }
115831
115826
  case "send": {
115832
- if (!presetWithin(child.permissionPreset, caller.permissionPreset)) {
115833
- return {
115834
- ok: false,
115835
- error: `"${child.name}" now has more permissions (${child.permissionPreset}) than this session (${caller.permissionPreset}), so this session cannot give it work. Tell the person.`
115836
- };
115837
- }
115827
+ const above = aboveCallerError(child.name, child.permissionPreset, caller.permissionPreset);
115828
+ if (above) return { ok: false, error: above };
115838
115829
  const refused2 = this.host.refusesDelegation(callerId, { sessionId: child.id, projectId: child.projectId }, { tool: "agent_session", target: child.name || child.id, text: opts.text ?? "" });
115839
115830
  if (refused2) return { ok: false, error: refused2 };
115840
115831
  const from = { sessionId: callerId, name: labelOf(this.host.store.messages, caller) };
@@ -116182,9 +116173,11 @@ var RemoteAgents = class {
116182
116173
  if (!caller?.projectId) {
116183
116174
  return { ok: false, error: caller?.kind === "orchestrator" ? "The orchestrator starts a session on a linked machine with create_session; start_agent with a machine is for a project's session." : "This session belongs to no project, so it cannot start agents." };
116184
116175
  }
116176
+ const project2 = this.host.store.projects.listProjects().find((p) => p.id === caller.projectId);
116177
+ if (!project2) return { ok: false, error: "This session's project is no longer registered." };
116178
+ if (project2.trusted !== true && !this.host.autoBoundaryOpen(callerId, "start in an untrusted project")) return { ok: false, error: UNTRUSTED_START };
116185
116179
  const refused2 = this.gateError(callerId);
116186
116180
  if (refused2) return { ok: false, error: refused2 };
116187
- const project2 = this.host.store.projects.listProjects().find((p) => p.id === caller.projectId);
116188
116181
  const full = this.places.agentCeilingError(callerId, project2.id);
116189
116182
  if (full) return { ok: false, error: full };
116190
116183
  const reader = this.host.linkedReader;
@@ -116377,11 +116370,8 @@ var RemoteAgents = class {
116377
116370
  case "send": {
116378
116371
  const text3 = opts.text?.trim();
116379
116372
  if (!text3) return { ok: false, error: "Give the text to send." };
116380
- const childPreset = rec.preset ?? "normal";
116381
- const own = this.callerPreset(callerId);
116382
- if (!presetWithin(childPreset, own)) {
116383
- return { ok: false, error: `"${rec.name}" now has more permissions (${childPreset}) than this session (${own}), so this session cannot give it work. Tell the person.` };
116384
- }
116373
+ const above = aboveCallerError(rec.name, rec.preset ?? "normal", this.callerPreset(callerId));
116374
+ if (above) return { ok: false, error: above };
116385
116375
  const now = await stateOf();
116386
116376
  if (!now.ok) return now;
116387
116377
  if (now.running) {
@@ -116413,16 +116403,8 @@ var RemoteAgents = class {
116413
116403
  return this.sendAtRest(rec, callerId, text3, label, reader);
116414
116404
  }
116415
116405
  case "stop": {
116416
- const owed = this.host.children.linkedReports.get(rec.sessionId);
116417
- if (owed?.orchestratorId === callerId) this.host.children.dropLinkedReport(rec.sessionId);
116418
- const held3 = rec.queued;
116419
- this.setQueued(rec.sessionId, []);
116420
- const r = await reader.interrupt(rec.sessionId);
116421
- if (!r.ok) {
116422
- if (held3) this.setQueued(rec.sessionId, held3);
116423
- if (owed?.orchestratorId === callerId && !this.host.children.linkedReports.has(rec.sessionId)) this.host.children.setLinkedReport(rec.sessionId, owed);
116424
- return { ok: false, error: `Could not stop "${rec.name}" on ${rec.machine}: ${bare(r.error)}.` };
116425
- }
116406
+ const r = await this.interruptFor(rec, callerId, reader);
116407
+ if (!r.ok) return { ok: false, error: `Could not stop "${rec.name}" on ${rec.machine}: ${bare(r.error)}.` };
116426
116408
  return { ok: true, state: "stopped", sessionId: rec.sessionId, sessionName: rec.name, tool: rec.tool };
116427
116409
  }
116428
116410
  case "delete": {
@@ -116441,14 +116423,9 @@ var RemoteAgents = class {
116441
116423
  const now = await stateOf();
116442
116424
  if (!now.ok) return now;
116443
116425
  if (now.running) {
116444
- const owed = this.host.children.linkedReports.get(rec.sessionId);
116445
- if (owed?.orchestratorId === callerId) this.host.children.dropLinkedReport(rec.sessionId);
116446
- const stopped = await reader.interrupt(rec.sessionId);
116447
- if (!stopped.ok) {
116448
- if (owed?.orchestratorId === callerId && !this.host.children.linkedReports.has(rec.sessionId)) this.host.children.setLinkedReport(rec.sessionId, owed);
116449
- return { ok: false, error: `Could not stop "${rec.name}" on ${rec.machine} before deleting it: ${bare(stopped.error)}.` };
116450
- }
116451
- }
116426
+ const stopped = await this.interruptFor(rec, callerId, reader);
116427
+ if (!stopped.ok) return { ok: false, error: `Could not stop "${rec.name}" on ${rec.machine} before deleting it: ${bare(stopped.error)}.` };
116428
+ } else this.setQueued(rec.sessionId, []);
116452
116429
  const canClean = typeof reader.deleteAgentChild === "function";
116453
116430
  const withWorktree = rec.worktree && canClean ? await reader.deleteAgentChild(rec.sessionId, rec.worktree).catch((e) => ({ ok: false, error: e instanceof Error ? e.message : String(e) })) : void 0;
116454
116431
  if (withWorktree && !withWorktree.ok) return { ok: false, error: `Could not delete "${rec.name}" on ${rec.machine}: ${bare(withWorktree.error)}.` };
@@ -116713,6 +116690,22 @@ var RemoteAgents = class {
116713
116690
  this.scheduleDrain(rec.sessionId);
116714
116691
  return { ok: true, tool: rec.tool, state: "queued", sessionId: rec.sessionId, sessionName: rec.name, queueId: entry.id, position: (same ? held3.indexOf(same) : held3.length) + 1 };
116715
116692
  }
116693
+ /**
116694
+ * Stops the child's turn there for a stop or a deletion by the one it would report to: nothing more is owed, and its
116695
+ * end must not send what was held for it. Both are taken down first, as the stopped turn's end can reach the hub before
116696
+ * the machine's answer does, and put back if the stop does not go
116697
+ */
116698
+ async interruptFor(rec, callerId, reader) {
116699
+ const owed = this.host.children.linkedReports.get(rec.sessionId);
116700
+ if (owed?.orchestratorId === callerId) this.host.children.dropLinkedReport(rec.sessionId);
116701
+ const held3 = this.of(rec.sessionId)?.queued;
116702
+ this.setQueued(rec.sessionId, []);
116703
+ const r = await reader.interrupt(rec.sessionId);
116704
+ if (r.ok) return r;
116705
+ if (held3) this.setQueued(rec.sessionId, held3);
116706
+ if (owed?.orchestratorId === callerId && !this.host.children.linkedReports.has(rec.sessionId)) this.host.children.setLinkedReport(rec.sessionId, owed);
116707
+ return r;
116708
+ }
116716
116709
  setQueued(sessionId, queued) {
116717
116710
  if (!queued.length && !this.of(sessionId)?.queued) return;
116718
116711
  this.save(this.records().map((r) => r.sessionId === sessionId ? { ...r, queued: queued.length ? queued : void 0 } : r));
@@ -117100,16 +117093,9 @@ Reason, by kind: ${errorKind(why2)} (its words are an error's, kept in this noti
117100
117093
  for (const key of [...this.toldCards]) if (key.startsWith(`${sessionId}\0`)) this.toldCards.delete(key);
117101
117094
  if (this.host.children.linkedReports.has(sessionId)) this.host.children.dropLinkedReport(sessionId);
117102
117095
  }
117103
- /** What start_agent and a send rest on in the caller, read again after a card: here, in a trusted project, above the depth */
117096
+ /** What start_agent and a send rest on in the caller, read again after a card and before each send: a local start's (`agentStartGateError`) */
117104
117097
  gateError(callerId) {
117105
- const caller = this.host.meta.get(callerId);
117106
- if (!caller) return "The session that asked is no longer here.";
117107
- const project2 = this.host.store.projects.listProjects().find((p) => p.id === caller.projectId);
117108
- if (!project2) return "This session's project is no longer registered.";
117109
- if (project2.trusted !== true && !this.host.autoBoundaryOpen(callerId, "start in an untrusted project")) {
117110
- return `This session's project is not trusted, so it cannot start agents \u2014 the person can trust it from the project menu. ${ASK_THE_PERSON}`;
117111
- }
117112
- return agentDepthRefusal(this.host, callerId, "start agents");
117098
+ return agentStartGateError(this.host, callerId, this.host.meta.get(callerId)?.projectId || null);
117113
117099
  }
117114
117100
  /** The caller's preset as it is now; `safe` for one gone, so nothing is decided on a preset no one has */
117115
117101
  callerPreset(callerId) {
@@ -117124,7 +117110,7 @@ Reason, by kind: ${errorKind(why2)} (its words are an error's, kept in this noti
117124
117110
  async autoThere(machine, reader) {
117125
117111
  const r = await reader.hubAuto(machine.id);
117126
117112
  if (r.ok) return r.allowed ? { auto: true } : { auto: false, withheld: "machine_off" };
117127
- return { error: `Could not ask ${machine.name} whether it takes auto from this computer: ${bare(r.error)}. Nothing was started there.` };
117113
+ return { error: `Could not tell whether ${machine.name} takes auto from this computer: ${bare(r.error)}. Nothing was started there.` };
117128
117114
  }
117129
117115
  /** The machine's project that is a checkout of this project's repository: the same `origin`, read on both sides */
117130
117116
  async checkoutOn(machine, project2, reader) {
@@ -122730,10 +122716,6 @@ var MailboxRecords = class {
122730
122716
  markDelivered(recipient, ids, at2, turnSeq) {
122731
122717
  this.update("delivered_at = ?, last_error = NULL, turn_seq = ?", [at2, turnSeq], recipient, ids);
122732
122718
  }
122733
- /** These delivered rows were delivered in the turn `turnSeq` (the delivery began it after it delivered them) */
122734
- markTurn(recipient, ids, turnSeq) {
122735
- this.update("turn_seq = ?", [turnSeq], recipient, ids, "delivered_at IS NOT NULL");
122736
- }
122737
122719
  /** Sets why mail waits for the person (`policy`, `loop`, `budget`, `failed`, `uncertain`), or clears it with null */
122738
122720
  hold(recipient, ids, why2) {
122739
122721
  this.update("held = ?", [why2], recipient, ids);
@@ -125182,11 +125164,6 @@ var MessageService = class {
125182
125164
  });
125183
125165
  return { needReply, unread: unread2, waiting, held: held3 };
125184
125166
  }
125185
- /** The messages to or from `address`, newest first, as the address reads them (the person's Inbox feed, M6's `mail.inbox`, §12.2). Marks nothing */
125186
- feed(address, opts) {
125187
- const found = this.records.feedOf(address, { ...opts.before === void 0 ? {} : { before: opts.before }, limit: opts.limit + 1 });
125188
- return { items: found.slice(0, opts.limit).map((m) => this.item(m, address, null)), older: found.length > opts.limit };
125189
- }
125190
125167
  /**
125191
125168
  * Whether mail that wakes `recipient` waits for it, due or not yet (the debounce makes a message wait a moment before it is due): a turn of its own is
125192
125169
  * coming, which a note of the host's should follow. Reads only; mail that is held out or settled is not waiting
@@ -125270,10 +125247,6 @@ var MessageService = class {
125270
125247
  asker: turnStarts && toOrchestrator ? picked : null
125271
125248
  };
125272
125249
  }
125273
- /** The delivery began a turn for `recipient` (it called `MailTurns.begin`): the rows it delivered were delivered in that turn */
125274
- turnStarted(recipient, messageIds, turnSeq) {
125275
- this.run(() => this.records.markTurn(recipient, messageIds, turnSeq));
125276
- }
125277
125250
  /**
125278
125251
  * What a host that died left half done (§7.6.5 of the old plan): a delivery whose attempt was written and whose acceptance was not. It is not sent
125279
125252
  * again on its own, since an inline task the tool took just before the host died would start the same work twice: it is held `uncertain`, for the
@@ -125435,29 +125408,6 @@ var MessageService = class {
125435
125408
  return rows3.length;
125436
125409
  });
125437
125410
  }
125438
- /** One line for the person's guidance per project with mail held for its trust or delegations refused (§7.4) */
125439
- heldByProject() {
125440
- const byProject = /* @__PURE__ */ new Map();
125441
- const entry = (projectId) => {
125442
- const kept = byProject.get(projectId) ?? { count: 0, refused: 0, recipients: /* @__PURE__ */ new Set() };
125443
- byProject.set(projectId, kept);
125444
- return kept;
125445
- };
125446
- for (const { row, senderProject } of this.records.heldPolicy()) {
125447
- if (senderProject === null) continue;
125448
- const e = entry(senderProject);
125449
- e.count++;
125450
- e.recipients.add(row.recipient);
125451
- }
125452
- for (const { message } of this.records.refusedRecords()) {
125453
- const e = message.facts.refused ? entry(message.facts.refused.projectId) : void 0;
125454
- if (e) {
125455
- e.refused++;
125456
- e.recipients.add(message.facts.refused?.target ?? "");
125457
- }
125458
- }
125459
- return [...byProject].map(([projectId, e]) => ({ projectId, count: e.count, refused: e.refused, recipients: [...e.recipients] })).sort((a, b2) => a.projectId.localeCompare(b2.projectId));
125460
- }
125461
125411
  // ── Parties that go, parties that take over (§7.7) ──
125462
125412
  /** A session went to the trash or was deleted: its watched rows are `gone`, its own watched messages `withdrawn`, and each sender whose needsReply message it leaves is told once (D25) */
125463
125413
  partyGone(address) {
@@ -125477,6 +125427,8 @@ var MessageService = class {
125477
125427
  * `needsReply` message, the person's too) is released with the cause, and each sender is told once, in one notice. The rows stay open, so a reply that
125478
125428
  * comes after the person continues the session still answers them. A question delivered in an earlier turn is not this stop's: it was read, and the
125479
125429
  * session may have answered it in conversation. A task is not released here: the old report path tells its owner until requests decide reports.
125430
+ * A request to the orchestrator is (owner: its sender always learns it will not be answered, #1186): the turn's report tells the asker it was owed to,
125431
+ * which the caller passes as `held`, and every other asker, or one that report misses (an asker the same quit cut), gets the stop notice.
125480
125432
  * `turnSeq` is the cut turn (none: nothing is released, as for a turn the mailbox never recorded)
125481
125433
  *
125482
125434
  * Who is woken (Q1): a sender that is at rest and waits on the stopped session, by a task it gave it that is still open or by a wait (`waitsOn`).
@@ -125499,10 +125451,7 @@ var MessageService = class {
125499
125451
  if (turnSeq === null) continue;
125500
125452
  const rows3 = this.records.watchedTo(address);
125501
125453
  const gave = new Set(rows3.filter((r) => this.records.message(r.messageId)?.kind === "task").flatMap((r) => this.records.message(r.messageId)?.sender ?? []));
125502
- for (const row of rows3) {
125503
- if (row.releasedAt !== null || row.deliveredAt === null || row.answerId !== null || row.turnSeq !== turnSeq) continue;
125504
- const m = this.records.message(row.messageId);
125505
- if (!m || m.kind === "task" || !m.needsReply || m.sender === address || this.isRequest(m, address)) continue;
125454
+ for (const { row, message: m } of this.cutQuestions(address, turnSeq)) {
125506
125455
  if (!this.records.markReleased(row.messageId, address, now, why2)) continue;
125507
125456
  released++;
125508
125457
  if (gave.has(m.sender) || this.opts.waitsOn?.(m.sender, address)) waiting.add(m.sender);
@@ -125580,14 +125529,22 @@ var MessageService = class {
125580
125529
  turnsLost(close) {
125581
125530
  this.releaseAndTell((now, senders) => {
125582
125531
  for (const { address, turnSeq } of close()) {
125583
- for (const row of this.records.unansweredTo(address)) {
125584
- if (row.turnSeq !== turnSeq || row.settled !== null) continue;
125585
- const m = this.records.message(row.messageId);
125586
- if (m && m.kind !== "task" && this.records.markReleased(row.messageId, address, now, "lost")) senders.add(m.sender);
125587
- }
125532
+ for (const { row, message } of this.cutQuestions(address, turnSeq)) if (this.records.markReleased(row.messageId, address, now, "lost")) senders.add(message.sender);
125588
125533
  }
125589
125534
  });
125590
125535
  }
125536
+ /**
125537
+ * The questions `address` left unanswered in its turn `turnSeq`, the rows a stop (`sessionsStopped`) or a lost turn (`turnsLost`) releases: each
125538
+ * `needsReply` message delivered into that turn and not answered, settled or released, a request to the orchestrator included. Not a task (the old
125539
+ * report path tells its owner, #698), nor a message the session sent itself (a handoff to the very session that asked leaves such a row)
125540
+ */
125541
+ cutQuestions(address, turnSeq) {
125542
+ return this.records.unansweredTo(address).flatMap((row) => {
125543
+ if (row.turnSeq !== turnSeq || row.settled !== null || row.releasedAt !== null) return [];
125544
+ const message = this.records.message(row.messageId);
125545
+ return message && message.kind !== "task" && message.sender !== address ? [{ row, message }] : [];
125546
+ });
125547
+ }
125591
125548
  /** One write: `release` releases rows and adds each released question's sender to `senders`; then each sender is told once, in one notice (D25) */
125592
125549
  releaseAndTell(release, quiet = () => false) {
125593
125550
  this.run(() => {
@@ -156054,7 +156011,6 @@ function updateNodeSentence(o) {
156054
156011
  import { execFileSync as execFileSync5, spawn as spawn6 } from "node:child_process";
156055
156012
  import { createHash as createHash14 } from "node:crypto";
156056
156013
  import { createConnection as createConnection2, createServer as createServer3 } from "node:net";
156057
- import { win32 as win329 } from "node:path";
156058
156014
  import { hostname as hostname3 } from "node:os";
156059
156015
 
156060
156016
  // packages/agent-host/src/links/ssh-run.ts
@@ -156340,6 +156296,12 @@ function notInstalled(where2, spec) {
156340
156296
  return new Error(`Centralu is not installed ${where2} (run \`${npmInstall(spec)}\` there, then \`${shimNames(spec).centralu} serve\` once)`);
156341
156297
  }
156342
156298
  var NOT_FOUND2 = "CENTRALU-NOT-FOUND";
156299
+ var STALE_LAUNCHER = "; if Node was upgraded there, run `centralu serve` once from an interactive shell on that machine";
156300
+ function noAnswer(err, r, remote) {
156301
+ const why2 = lastLine(r.stderr);
156302
+ if (r.code === 127 && !remote.command?.trim()) return `The Centralu there did not run: ${why2 || "exit 127"}${STALE_LAUNCHER}`;
156303
+ return `${err.message}${why2 ? `: ${why2}` : r.code ? ` (exit ${r.code})` : ""}`;
156304
+ }
156343
156305
  function connectionCommand(spec, pair = null) {
156344
156306
  return remoteCommand(spec, pair ? `serve --connection --pair ${pair}` : "serve --connection");
156345
156307
  }
@@ -156468,13 +156430,12 @@ var SshTunnel = class {
156468
156430
  const r = await this.run([...this.config(), "-T", "-o", "BatchMode=yes", "-o", "ConnectTimeout=15", "--", this.opts.target, detachCommand(remote, this.opts.hubName ?? hostname3())], void 0, this.opts.detachTimeoutMs ?? SSH_DETACH_TIMEOUT_MS);
156469
156431
  this.ifClosed();
156470
156432
  if (r.code === 255) throw new Error(`ssh could not reach ${this.opts.target}: ${lastLine(r.stderr) || "no answer"}`);
156471
- if (r.stdout.includes(NOT_FOUND2) || r.code === 127 && !remote.command) throw notInstalled(this.where, remote);
156433
+ if (r.stdout.includes(NOT_FOUND2)) throw notInstalled(this.where, remote);
156472
156434
  let answer;
156473
156435
  try {
156474
156436
  answer = parseDetachLine(r.stdout);
156475
156437
  } catch (err) {
156476
- const why2 = lastLine(r.stderr);
156477
- throw new Error(`${err.message}${why2 ? `: ${why2}` : r.code ? ` (exit ${r.code})` : ""}`);
156438
+ throw new Error(noAnswer(err, r, remote));
156478
156439
  }
156479
156440
  if (answer.ok) {
156480
156441
  this.linkBound = false;
@@ -156597,14 +156558,14 @@ var SshTunnel = class {
156597
156558
  return this.connectionLine();
156598
156559
  }
156599
156560
  if (r.code === 255) throw new Error(`ssh could not reach ${this.opts.target}: ${lastLine(r.stderr) || "no answer"}`);
156600
- if (r.stdout.includes(NOT_FOUND2) || r.code === 127 && !remote.command) throw notInstalled(this.where, remote);
156561
+ if (r.stdout.includes(NOT_FOUND2)) throw notInstalled(this.where, remote);
156601
156562
  try {
156602
156563
  const line = parseConnectionLine(r.stdout, remote);
156603
156564
  return pair && this.pairs && line.credential ? { ...line, pairedAs: oneLineText(pair, 80) } : line;
156604
156565
  } catch (err) {
156605
156566
  const why2 = lastLine(r.stderr);
156606
156567
  if (r.code !== 0 && /cannot be read by this user \((EACCES|EPERM)\)/.test(why2)) throw new Error(why2.replace(/^centralu serve: /, ""));
156607
- throw new Error(`${err.message}${why2 ? `: ${why2}` : r.code ? ` (exit ${r.code})` : ""}`);
156568
+ throw new Error(noAnswer(err, r, remote));
156608
156569
  }
156609
156570
  }
156610
156571
  forward(localPort, remotePort, hosting = false) {
@@ -156746,8 +156707,7 @@ function ansiText(bytes2) {
156746
156707
  ansiDecoder = null;
156747
156708
  if (process.platform !== "win32") return null;
156748
156709
  try {
156749
- const root3 = process.env.SystemRoot && win329.isAbsolute(process.env.SystemRoot) ? process.env.SystemRoot : "C:\\Windows";
156750
- const out = execFileSync5(win329.join(root3, "System32", "reg.exe"), ["query", "HKLM\\SYSTEM\\CurrentControlSet\\Control\\Nls\\CodePage", "/v", "ACP"], { encoding: "utf8", timeout: 5e3, windowsHide: true });
156710
+ const out = execFileSync5(systemTool2(["reg.exe"]), ["query", "HKLM\\SYSTEM\\CurrentControlSet\\Control\\Nls\\CodePage", "/v", "ACP"], { encoding: "utf8", timeout: 5e3, windowsHide: true });
156751
156711
  const page = /\bACP\s+REG_SZ\s+(\d+)/.exec(out)?.[1];
156752
156712
  if (page) ansiDecoder = new TextDecoder(CODE_PAGE_NAMES[page] ?? `windows-${page}`, { fatal: true });
156753
156713
  } catch {
@@ -157401,6 +157361,17 @@ async function removeAutostart(c) {
157401
157361
  }
157402
157362
  }
157403
157363
 
157364
+ // packages/agent-host/src/links/hub-auto.ts
157365
+ async function askHubAuto(call2, allowed) {
157366
+ try {
157367
+ const r = asObj(await (allowed === void 0 ? call2("host.hubAuto", {}) : call2("host.setHubAuto", { allowed })));
157368
+ return r.allowed === true ? "on" : "off";
157369
+ } catch (err) {
157370
+ if (/^Unknown method/.test(errorMessage(err))) return "older";
157371
+ throw err;
157372
+ }
157373
+ }
157374
+
157404
157375
  // packages/agent-host/src/links/linked-machine-connection.ts
157405
157376
  import { randomBytes as randomBytes13 } from "node:crypto";
157406
157377
 
@@ -157722,6 +157693,11 @@ var RemoteClient = class {
157722
157693
  attempt = 0;
157723
157694
  reconnectTimer;
157724
157695
  handshakeTimer;
157696
+ stableTimer;
157697
+ /** The hello of this socket is younger than `stableMs`: a drop now makes it short-lived */
157698
+ settling = false;
157699
+ /** The hellos in a row that dropped within `stableMs`; cleared by a hello that stayed up that long and by every `updateEndpoint` */
157700
+ shortLived = 0;
157725
157701
  /** The refusal that came before the close, if one did: it names why the host closes */
157726
157702
  refusal;
157727
157703
  WS;
@@ -157808,7 +157784,9 @@ var RemoteClient = class {
157808
157784
  }
157809
157785
  /**
157810
157786
  * Points the client at another address or token (the tunnel came back on another port, or the token rotated), and
157811
- * the id of the credential the token is, when it was paired (#1405)
157787
+ * the id of the credential the token is, when it was paired (#1405). Every call, the same address or not, starts the
157788
+ * dials and the count of short-lived hellos over: the link has read the connection line again, which is the answer
157789
+ * the redials were a stand-in for
157812
157790
  */
157813
157791
  updateEndpoint(url2, token2, credential) {
157814
157792
  const sameHost = this.opts.url === url2;
@@ -157819,6 +157797,7 @@ var RemoteClient = class {
157819
157797
  this.lastSeq = 0;
157820
157798
  }
157821
157799
  this.attempt = 0;
157800
+ this.shortLived = 0;
157822
157801
  if (this.ws) {
157823
157802
  const old = this.ws;
157824
157803
  this.ws = null;
@@ -157887,6 +157866,14 @@ var RemoteClient = class {
157887
157866
  this.cursorValid = true;
157888
157867
  this.ready = true;
157889
157868
  this.attempt = 0;
157869
+ if (this.opts.stableMs !== void 0) {
157870
+ this.settling = true;
157871
+ this.stableTimer = setTimeout(() => {
157872
+ this.settling = false;
157873
+ this.shortLived = 0;
157874
+ }, this.opts.stableMs);
157875
+ this.stableTimer.unref?.();
157876
+ }
157890
157877
  this.on.hello?.({ resync, newLifetime: !sameLifetime, protocolVersion: frame.protocolVersion, ...frame.build ? { build: frame.build } : {}, ...typeof frame.elevated === "boolean" ? { elevated: frame.elevated } : {}, ...frame.logon ? { logon: frame.logon } : {}, ...frame.handover ? { handover: frame.handover } : {}, ...frame.updateContinued ? { updateContinued: frame.updateContinued } : {} });
157891
157878
  return;
157892
157879
  }
@@ -157958,11 +157945,13 @@ var RemoteClient = class {
157958
157945
  this.ws = null;
157959
157946
  const wasReady = this.ready;
157960
157947
  this.ready = false;
157948
+ if (wasReady && this.settling) this.shortLived++;
157949
+ this.settling = false;
157961
157950
  this.clearTimers();
157962
157951
  this.failAll("The link dropped");
157963
157952
  if (this.closed) return;
157964
157953
  if (wasReady) this.on.down?.(reason, detail);
157965
- if (this.opts.redials !== void 0 && this.attempt >= this.opts.redials) {
157954
+ if (this.opts.redials !== void 0 && (this.attempt >= this.opts.redials || this.shortLived >= this.opts.redials)) {
157966
157955
  this.on.gaveUp?.(reason);
157967
157956
  return;
157968
157957
  }
@@ -157983,6 +157972,7 @@ var RemoteClient = class {
157983
157972
  clearTimers() {
157984
157973
  clearTimeout(this.reconnectTimer);
157985
157974
  clearTimeout(this.handshakeTimer);
157975
+ clearTimeout(this.stableTimer);
157986
157976
  this.reconnectTimer = void 0;
157987
157977
  this.handshakeTimer = void 0;
157988
157978
  }
@@ -158123,7 +158113,7 @@ async function follow(c, id3) {
158123
158113
  const machine = c.machine ?? c.target;
158124
158114
  throw new Error(`The update of ${machine} could not be followed to its end from this computer; how it ended is shown once ${machine} is reached again`);
158125
158115
  }
158126
- await new Promise((r) => setTimeout(r, wait));
158116
+ await sleep(wait);
158127
158117
  }
158128
158118
  }
158129
158119
  var restoredNote = (version2, machine) => `The store on ${machine} was put back as it was before the dev build first opened it, since Centralu ${version2} could not read it as the dev build left it; what was done on the dev build since is not in it`;
@@ -158321,9 +158311,8 @@ var LinkedMachineConnection = class {
158321
158311
  client = null;
158322
158312
  /** The client when it stopped redialling (`gaveUp`, #1143) and is waiting, idle, for the next opening */
158323
158313
  gaveUp = null;
158324
- /** Sessions of the remote the hub shows / hides, by the remote's id. Events of any other session are not passed on */
158314
+ /** Sessions of the remote the hub shows, by the remote's id. Events of any other session are not passed on */
158325
158315
  shown = /* @__PURE__ */ new Set();
158326
- hidden = /* @__PURE__ */ new Set();
158327
158316
  /** The remote's coordinators as it last gave them: not shown, but read by a reader (#1941, `unshown`) */
158328
158317
  coordinators = /* @__PURE__ */ new Map();
158329
158318
  retryTimer;
@@ -158649,13 +158638,6 @@ var LinkedMachineConnection = class {
158649
158638
  isCurrent(c, line) {
158650
158639
  return !!line.credential && c.id === line.credential && this.isPairedHere(c);
158651
158640
  }
158652
- /**
158653
- * The credentials used on the machine under this hub's own label that this hub did not pair (#1405 review), and that
158654
- * `listed` does not name already, as used and gone. The machine's host leaves out a credential whose label's next one
158655
- * was paired the same way, taking it for its pairer again; under a hub's label only the hub can tell. Counted from the
158656
- * first use there of one this hub paired in its present run, so the credentials of its own earlier runs, which it does
158657
- * not remember, are not taken for another's
158658
- */
158659
158641
  /**
158660
158642
  * Marks, in a list the host answered with `nonce`, the entries whose `check` is of the secret this hub holds under
158661
158643
  * their id (`own`). An entry without a check (a host from before it) or a credential this hub holds no secret of is not
@@ -158667,6 +158649,13 @@ var LinkedMachineConnection = class {
158667
158649
  return c.check === void 0 || !verifier ? c : { ...c, own: c.check === credentialCheck(verifier, nonce) };
158668
158650
  }) ?? null;
158669
158651
  }
158652
+ /**
158653
+ * The credentials used on the machine under this hub's own label that this hub did not pair (#1405 review), and that
158654
+ * `listed` does not name already, as used and gone. The machine's host leaves out a credential whose label's next one
158655
+ * was paired the same way, taking it for its pairer again; under a hub's label only the hub can tell. Counted from the
158656
+ * first use there of one this hub paired in its present run, so the credentials of its own earlier runs, which it does
158657
+ * not remember, are not taken for another's
158658
+ */
158670
158659
  notPairedHere(line, listed2) {
158671
158660
  if (!line.pairedAs) return [];
158672
158661
  const first = this.usedUnderHubLabels.findIndex((u) => this.isPairedHere(u));
@@ -158854,7 +158843,6 @@ var LinkedMachineConnection = class {
158854
158843
  observe(kind, list2) {
158855
158844
  if (kind === "sessions") {
158856
158845
  this.shown.clear();
158857
- this.hidden.clear();
158858
158846
  this.coordinators.clear();
158859
158847
  for (const s of list2) this.classify(s);
158860
158848
  this.deps.mirror.replace(this.id, "sessions", list2.filter((s) => !hiddenKind(s)));
@@ -159208,6 +159196,7 @@ var LinkedMachineConnection = class {
159208
159196
  // A host that can prove it holds the token is asked to, before the token goes out (#838)
159209
159197
  {
159210
159198
  redials: LINK_REDIALS,
159199
+ stableMs: this.deps.stableMs ?? STABLE_MS,
159211
159200
  ...this.deps.client,
159212
159201
  url: endpoint.url,
159213
159202
  token: endpoint.token,
@@ -159351,8 +159340,7 @@ var LinkedMachineConnection = class {
159351
159340
  }
159352
159341
  classify(s) {
159353
159342
  if (!isObj(s) || typeof s.id !== "string") return;
159354
- if (hiddenKind(s)) this.hidden.add(s.id);
159355
- else this.shown.add(s.id);
159343
+ if (!hiddenKind(s)) this.shown.add(s.id);
159356
159344
  if (s.kind === "coordinator") this.coordinators.set(s.id, s);
159357
159345
  }
159358
159346
  transportDown(reason) {
@@ -159367,14 +159355,6 @@ var LinkedMachineConnection = class {
159367
159355
  nextStep() {
159368
159356
  return this.autoReconnect ? KEEPS_TRYING : WAITS_FOR_PERSON;
159369
159357
  }
159370
- /**
159371
- * Asks the machine again after a delay. `backoff` (the default): the next step of the backoff, and nothing at all
159372
- * while Auto reconnect is off (#1341), where only the person's Connect tries again (after the client's quick redials,
159373
- * which an off machine keeps). `within`: part of an attempt that
159374
- * is under way or of the person's own request (a refusal asked again at once, a Connect that met an opening), so it
159375
- * goes ahead either way. `reads`: the versions check looking again for a change on either side; with the switch off it
159376
- * reads the line and connects nothing (`readsOnly`)
159377
- */
159378
159358
  /** The link is up, or was just: remembers when, and keeps it for the next start of this host */
159379
159359
  seen() {
159380
159360
  this.lastConnectedAt = Date.now();
@@ -159385,6 +159365,14 @@ var LinkedMachineConnection = class {
159385
159365
  clearTimeout(this.retryTimer);
159386
159366
  this.nextAttemptAt = null;
159387
159367
  }
159368
+ /**
159369
+ * Asks the machine again after a delay. `backoff` (the default): the next step of the backoff, and nothing at all
159370
+ * while Auto reconnect is off (#1341), where only the person's Connect tries again (after the client's quick redials,
159371
+ * which an off machine keeps). `within`: part of an attempt that
159372
+ * is under way or of the person's own request (a refusal asked again at once, a Connect that met an opening), so it
159373
+ * goes ahead either way. `reads`: the versions check looking again for a change on either side; with the switch off it
159374
+ * reads the line and connects nothing (`readsOnly`)
159375
+ */
159388
159376
  retryLater(fixedMs, mode = "backoff") {
159389
159377
  if (this.stopped || this.held) return;
159390
159378
  if (mode === "backoff" && !this.autoReconnect && !this.inOperation()) return;
@@ -159806,13 +159794,10 @@ var LinkedMachine = class extends LinkedMachineConnection {
159806
159794
  const client = this.client;
159807
159795
  if (!client?.connected) return "unreachable";
159808
159796
  try {
159809
- const r = await (allowed === void 0 ? client.call("host.hubAuto", {}) : client.call("host.setHubAuto", { allowed }));
159810
- return r?.allowed === true ? "on" : "off";
159797
+ return await askHubAuto((method, params) => client.call(method, params), allowed);
159811
159798
  } catch (err) {
159812
- const message = err?.message ?? String(err);
159813
- if (/^Unknown method/.test(message)) return "older";
159814
159799
  if (!client.connected) return "unreachable";
159815
- throw rpcError(`${this.name} did not answer for its "Allow auto from a linked computer": ${message}`);
159800
+ throw rpcError(`${this.name} did not answer for its "Allow auto from a linked computer": ${errorMessage(err)}`);
159816
159801
  }
159817
159802
  }
159818
159803
  /**
@@ -160243,8 +160228,8 @@ function linkedSessions(machines, route = notRouted, hubName2, options = {}) {
160243
160228
  await settle(m);
160244
160229
  return m.reachable ? null : { ok: false, error: awaySentence(m, tail) };
160245
160230
  };
160246
- const attempt = async (m, run4) => {
160247
- const down = await reach(m, TAIL.tryAgain);
160231
+ const attempt = async (m, run4, tail = TAIL.tryAgain) => {
160232
+ const down = await reach(m, tail);
160248
160233
  if (down) return down;
160249
160234
  try {
160250
160235
  return { ok: true, ...await run4() };
@@ -160257,13 +160242,13 @@ function linkedSessions(machines, route = notRouted, hubName2, options = {}) {
160257
160242
  const m = find(id3);
160258
160243
  return m ? [m, null] : [null, { ok: false, error: refusal2(id3) }];
160259
160244
  };
160260
- const via = async (id3, run4) => {
160245
+ const via = async (id3, run4, tail) => {
160261
160246
  const [m, refused2] = found(id3);
160262
- return m ? attempt(m, () => run4(m)) : refused2;
160247
+ return m ? attempt(m, () => run4(m), tail) : refused2;
160263
160248
  };
160264
160249
  return [found, via];
160265
160250
  };
160266
- const [findSession, viaSession] = reaching(owner, (id3) => `Not a session of a linked machine: ${id3}`);
160251
+ const [, viaSession] = reaching(owner, (id3) => `Not a session of a linked machine: ${id3}`);
160267
160252
  const [findProject2, viaProject] = reaching(owner, (id3) => `Not a project of a linked machine: ${id3}`);
160268
160253
  const [findMachineRef, viaMachine] = reaching(machineById, (id3) => `No linked machine ${id3}`);
160269
160254
  const listOf = async (m, method) => {
@@ -160459,8 +160444,9 @@ function linkedSessions(machines, route = notRouted, hubName2, options = {}) {
160459
160444
  },
160460
160445
  hubAuto(machineId) {
160461
160446
  return viaMachine(machineId, async (m) => {
160462
- const r = await m.call("host.hubAuto", {});
160463
- return { allowed: r?.allowed === true };
160447
+ const state = await askHubAuto((method, params) => m.call(method, params));
160448
+ if (state === "older") throw new Error(`${m.name} runs a Centralu from before "Allow auto from a linked computer"; update it there`);
160449
+ return { allowed: state === "on" };
160464
160450
  });
160465
160451
  },
160466
160452
  origin(project2) {
@@ -160625,20 +160611,18 @@ function linkedSessions(machines, route = notRouted, hubName2, options = {}) {
160625
160611
  const p = (m.lastKnown("projects") ?? []).find((x) => isObj(x) && x.id === s.projectId);
160626
160612
  return { name: oneLine4(s.name), project: `${isObj(p) ? oneLine4(p.name) : "(none)"} on ${m.name}` };
160627
160613
  },
160628
- async lastWords(sessionId, maxChars, opts) {
160629
- const [m, refusal2] = findSession(sessionId);
160630
- if (!m) return refusal2;
160631
- const down = await reach(m, TAIL.none);
160632
- if (down) return down;
160633
- try {
160634
- const answer = await m.call("host.lastAnswer", { sessionId: m.q.strip(sessionId, "The session"), maxChars: Math.min(maxChars, ANSWER_MAX_CHARS2), ...opts?.turn ? { turn: true } : {}, ...opts?.since !== void 0 ? { since: opts.since } : {} });
160635
- const r = HostLastAnswerResult.safeParse(answer);
160636
- if (!r.success) return { ok: false, error: `${m.name} answered in a shape this Centralu does not read` };
160637
- const text3 = r.data.text.length > maxChars || r.data.length > r.data.text.length ? `${r.data.text.slice(0, maxChars)}\u2026` : r.data.text;
160638
- return { ok: true, text: text3, length: r.data.length, seq: r.data.seq };
160639
- } catch (err) {
160640
- return { ok: false, error: cause(err) };
160641
- }
160614
+ lastWords(sessionId, maxChars, opts) {
160615
+ return viaSession(
160616
+ sessionId,
160617
+ async (m) => {
160618
+ const answer = await m.call("host.lastAnswer", { sessionId: m.q.strip(sessionId, "The session"), maxChars: Math.min(maxChars, ANSWER_MAX_CHARS2), ...opts?.turn ? { turn: true } : {}, ...opts?.since !== void 0 ? { since: opts.since } : {} });
160619
+ const r = HostLastAnswerResult.safeParse(answer);
160620
+ if (!r.success) throw new Error(`${m.name} answered in a shape this Centralu does not read`);
160621
+ const text3 = r.data.text.length > maxChars || r.data.length > r.data.text.length ? `${r.data.text.slice(0, maxChars)}\u2026` : r.data.text;
160622
+ return { text: text3, length: r.data.length, seq: r.data.seq };
160623
+ },
160624
+ TAIL.none
160625
+ );
160642
160626
  },
160643
160627
  async list() {
160644
160628
  const perMachine = await Promise.all(
@@ -160656,42 +160640,40 @@ function linkedSessions(machines, route = notRouted, hubName2, options = {}) {
160656
160640
  machineOf(sessionId) {
160657
160641
  return owner(sessionId)?.name ?? null;
160658
160642
  },
160659
- async read(sessionId, limit, opts) {
160660
- const [m, refusal2] = findSession(sessionId);
160661
- if (!m) return refusal2;
160662
- const down = await reach(m, TAIL.readAgain);
160663
- if (down) return down;
160643
+ read(sessionId, limit, opts) {
160664
160644
  const toolMode = toolLinesOf(opts?.tools);
160665
- try {
160666
- const answer = await m.call("host.readSession", {
160667
- sessionId: m.q.strip(sessionId, "The session"),
160668
- ...limit !== void 0 ? { limit } : {},
160669
- ...opts?.around !== void 0 ? { around: opts.around } : {},
160670
- ...opts?.tools !== void 0 ? { toolLines: toolMode } : {},
160671
- ...opts?.maxChars !== void 0 ? { maxChars: budgetOf(opts.maxChars) } : {},
160672
- ...opts?.before !== void 0 ? { before: opts.before } : {},
160673
- ...opts?.from !== void 0 ? { from: opts.from } : {},
160674
- ...opts?.offset !== void 0 ? { offset: opts.offset } : {}
160675
- });
160676
- const r = HostReadSessionResult.safeParse(answer);
160677
- if (!r.success) return { ok: false, error: `${m.name} answered in a shape this Centralu does not read` };
160678
- const all = r.data.lines;
160679
- const kept = toolMode === "none" ? all.filter((l) => !isToolLine(l)) : all;
160680
- const spot = Math.min(Math.floor(Math.min(limit ?? 40, 200) / 2), all.length - 1);
160681
- const around = opts?.around !== void 0 && opts.before === void 0 && opts.from === void 0;
160682
- const center = !around ? null : Math.min(toolMode === "none" ? all.slice(0, spot).filter((l) => !isToolLine(l)).length : spot, kept.length - 1);
160683
- const lines = fitMachineLines(kept, budgetOf(opts?.maxChars), center);
160684
- return { ok: true, state: oneLine4(r.data.state, 40), lines: lines.map(fencedLine) };
160685
- } catch (err) {
160686
- return { ok: false, error: cause(err) };
160687
- }
160645
+ return viaSession(
160646
+ sessionId,
160647
+ async (m) => {
160648
+ const answer = await m.call("host.readSession", {
160649
+ sessionId: m.q.strip(sessionId, "The session"),
160650
+ ...limit !== void 0 ? { limit } : {},
160651
+ ...opts?.around !== void 0 ? { around: opts.around } : {},
160652
+ ...opts?.tools !== void 0 ? { toolLines: toolMode } : {},
160653
+ ...opts?.maxChars !== void 0 ? { maxChars: budgetOf(opts.maxChars) } : {},
160654
+ ...opts?.before !== void 0 ? { before: opts.before } : {},
160655
+ ...opts?.from !== void 0 ? { from: opts.from } : {},
160656
+ ...opts?.offset !== void 0 ? { offset: opts.offset } : {}
160657
+ });
160658
+ const r = HostReadSessionResult.safeParse(answer);
160659
+ if (!r.success) throw new Error(`${m.name} answered in a shape this Centralu does not read`);
160660
+ const all = r.data.lines;
160661
+ const kept = toolMode === "none" ? all.filter((l) => !isToolLine(l)) : all;
160662
+ const spot = Math.min(Math.floor(Math.min(limit ?? 40, 200) / 2), all.length - 1);
160663
+ const around = opts?.around !== void 0 && opts.before === void 0 && opts.from === void 0;
160664
+ const center = !around ? null : Math.min(toolMode === "none" ? all.slice(0, spot).filter((l) => !isToolLine(l)).length : spot, kept.length - 1);
160665
+ const lines = fitMachineLines(kept, budgetOf(opts?.maxChars), center);
160666
+ return { state: oneLine4(r.data.state, 40), lines: lines.map(fencedLine) };
160667
+ },
160668
+ TAIL.readAgain
160669
+ );
160688
160670
  },
160689
160671
  async recall(query, limit) {
160690
160672
  const notSearched = [];
160691
160673
  const hits = await Promise.all(
160692
160674
  [...machines()].map(async (m) => {
160693
160675
  if (!m.reachable) {
160694
- notSearched.push(`${m.name} (not reachable right now)`);
160676
+ notSearched.push(m.waitsForPerson ? `${m.name} ${NOT_CONNECTED_PHRASE}` : `${m.name} (${NOT_REACHABLE})`);
160695
160677
  return [];
160696
160678
  }
160697
160679
  try {
@@ -161100,7 +161082,7 @@ var Router = class {
161100
161082
  if (method === "apps.list" && isObj(params) && typeof params.machine === "string") {
161101
161083
  return this.byMachine(method, params, { result: (r, q2) => Array.isArray(r) ? r.map((a) => q2.app(a)) : r }, ctx);
161102
161084
  }
161103
- return this.merge(method, params);
161085
+ return this.merge(method, params, ctx);
161104
161086
  }
161105
161087
  };
161106
161088
  /** The linked machine an id names, or null for the hub's own (an unprefixed id, or a prefix no link has) */
@@ -161213,7 +161195,7 @@ var Router = class {
161213
161195
  * mirror for it, marked `unreachable` and with `live` as last known, so the UI neither drops
161214
161196
  * them nor tries to wake them (§5, "Recovery").
161215
161197
  */
161216
- async merge(method, params) {
161198
+ async merge(method, params, ctx) {
161217
161199
  const machines = [...this.opts.machines()];
161218
161200
  switch (method) {
161219
161201
  case "sessions.list":
@@ -161221,9 +161203,9 @@ var Router = class {
161221
161203
  case "projects.list":
161222
161204
  return this.mergeLists(machines, "projects", params);
161223
161205
  case "projects.reorder":
161224
- return this.reorderProjects(machines, params);
161206
+ return this.reorderProjects(machines, params, ctx);
161225
161207
  case "approvals.deleteRule":
161226
- return this.deleteRule(machines, params);
161208
+ return this.deleteRule(machines, params, ctx);
161227
161209
  default:
161228
161210
  return this.mergeSimple(method, machines, params);
161229
161211
  }
@@ -161285,7 +161267,7 @@ var Router = class {
161285
161267
  * out of it: a stale window cannot move what it never saw. The kept order forgets a project the hub no longer lists,
161286
161268
  * unless it is a linked machine's, which may only be out of reach.
161287
161269
  */
161288
- async reorderProjects(machines, params) {
161270
+ async reorderProjects(machines, params, ctx) {
161289
161271
  const { orderedIds } = RpcMethods["projects.reorder"].params.parse(params);
161290
161272
  const kept = this.opts.order?.read() ?? [];
161291
161273
  const full = keepUnseen(orderedIds, kept);
@@ -161302,7 +161284,7 @@ var Router = class {
161302
161284
  const ordered = perMachine.get(m);
161303
161285
  if (!ordered) return this.listOf(m, "projects");
161304
161286
  try {
161305
- return m.q.projects(await this.forward(m, "projects.reorder", { orderedIds: ordered }));
161287
+ return m.q.projects(await this.forward(m, "projects.reorder", { orderedIds: ordered }, ctx));
161306
161288
  } catch {
161307
161289
  return this.present(m, "projects", m.lastKnown("projects") ?? [], true);
161308
161290
  }
@@ -161315,13 +161297,13 @@ var Router = class {
161315
161297
  }
161316
161298
  return machines.length > 0 ? this.inPersonsOrder(all) : all;
161317
161299
  }
161318
- async deleteRule(machines, params) {
161300
+ async deleteRule(machines, params, ctx) {
161319
161301
  const id3 = isObj(params) ? params.id : void 0;
161320
161302
  const folded = typeof id3 === "number" ? decodeNumber(id3) : null;
161321
161303
  if (!folded) return this.opts.local("approvals.deleteRule", params);
161322
161304
  const m = machines.find((x) => x.q.slot === folded.slot);
161323
161305
  if (!m) throw rpcError("That rule belongs to a machine that is no longer linked");
161324
- return this.forward(m, "approvals.deleteRule", { id: folded.n });
161306
+ return this.forward(m, "approvals.deleteRule", { id: folded.n }, ctx);
161325
161307
  }
161326
161308
  /** The other merges: every reachable machine, each answer in the hub's terms, combined by kind */
161327
161309
  async mergeSimple(method, machines, params) {
@@ -161892,7 +161874,7 @@ var handedToken = launch.tokenOnStdin ? await readHandedToken(process.stdin).cat
161892
161874
  console.error(`[agent-host] the launcher did not hand over the token: ${e.message}`);
161893
161875
  process.exit(1);
161894
161876
  }) : launch.token;
161895
- var BUILD = true ? "3bbc42b" : "dev";
161877
+ var BUILD = true ? "62e7369" : "dev";
161896
161878
  var { values } = parseArgs({
161897
161879
  options: {
161898
161880
  port: { type: "string", default: "5175" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@centralu/host-linux-arm64",
3
- "version": "0.1.0-beta.50",
3
+ "version": "0.1.0-beta.52",
4
4
  "description": "Centralu's host without the window, for Linux on arm64: what a hub installs on a linked machine",
5
5
  "license": "MIT",
6
6
  "author": "ijun17",