@centralu/host-linux-x64 0.1.0-beta.50 → 0.1.0-beta.51

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": "5f99652",
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:15.183Z",
8
+ "builtAt": "2026-11-07T05:42:39.476Z",
9
9
  "runtime": "system-node",
10
10
  "platform": "linux",
11
11
  "arch": "x64"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "centralu",
3
- "version": "0.1.0-beta.50",
3
+ "version": "0.1.0-beta.51",
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.51",
42
+ "@centralu/linux-arm64": "0.1.0-beta.51",
43
+ "@centralu/linux-x64": "0.1.0-beta.51",
44
+ "@centralu/win32-x64": "0.1.0-beta.51"
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.51";
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;
@@ -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) {
@@ -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
@@ -156468,7 +156424,7 @@ var SshTunnel = class {
156468
156424
  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
156425
  this.ifClosed();
156470
156426
  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);
156427
+ if (r.stdout.includes(NOT_FOUND2)) throw notInstalled(this.where, remote);
156472
156428
  let answer;
156473
156429
  try {
156474
156430
  answer = parseDetachLine(r.stdout);
@@ -156597,7 +156553,7 @@ var SshTunnel = class {
156597
156553
  return this.connectionLine();
156598
156554
  }
156599
156555
  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);
156556
+ if (r.stdout.includes(NOT_FOUND2)) throw notInstalled(this.where, remote);
156601
156557
  try {
156602
156558
  const line = parseConnectionLine(r.stdout, remote);
156603
156559
  return pair && this.pairs && line.credential ? { ...line, pairedAs: oneLineText(pair, 80) } : line;
@@ -156746,8 +156702,7 @@ function ansiText(bytes2) {
156746
156702
  ansiDecoder = null;
156747
156703
  if (process.platform !== "win32") return null;
156748
156704
  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 });
156705
+ const out = execFileSync5(systemTool2(["reg.exe"]), ["query", "HKLM\\SYSTEM\\CurrentControlSet\\Control\\Nls\\CodePage", "/v", "ACP"], { encoding: "utf8", timeout: 5e3, windowsHide: true });
156751
156706
  const page = /\bACP\s+REG_SZ\s+(\d+)/.exec(out)?.[1];
156752
156707
  if (page) ansiDecoder = new TextDecoder(CODE_PAGE_NAMES[page] ?? `windows-${page}`, { fatal: true });
156753
156708
  } catch {
@@ -157401,6 +157356,17 @@ async function removeAutostart(c) {
157401
157356
  }
157402
157357
  }
157403
157358
 
157359
+ // packages/agent-host/src/links/hub-auto.ts
157360
+ async function askHubAuto(call2, allowed) {
157361
+ try {
157362
+ const r = asObj(await (allowed === void 0 ? call2("host.hubAuto", {}) : call2("host.setHubAuto", { allowed })));
157363
+ return r.allowed === true ? "on" : "off";
157364
+ } catch (err) {
157365
+ if (/^Unknown method/.test(errorMessage(err))) return "older";
157366
+ throw err;
157367
+ }
157368
+ }
157369
+
157404
157370
  // packages/agent-host/src/links/linked-machine-connection.ts
157405
157371
  import { randomBytes as randomBytes13 } from "node:crypto";
157406
157372
 
@@ -157722,6 +157688,11 @@ var RemoteClient = class {
157722
157688
  attempt = 0;
157723
157689
  reconnectTimer;
157724
157690
  handshakeTimer;
157691
+ stableTimer;
157692
+ /** The hello of this socket is younger than `stableMs`: a drop now makes it short-lived */
157693
+ settling = false;
157694
+ /** The hellos in a row that dropped within `stableMs` */
157695
+ shortLived = 0;
157725
157696
  /** The refusal that came before the close, if one did: it names why the host closes */
157726
157697
  refusal;
157727
157698
  WS;
@@ -157819,6 +157790,7 @@ var RemoteClient = class {
157819
157790
  this.lastSeq = 0;
157820
157791
  }
157821
157792
  this.attempt = 0;
157793
+ this.shortLived = 0;
157822
157794
  if (this.ws) {
157823
157795
  const old = this.ws;
157824
157796
  this.ws = null;
@@ -157887,6 +157859,14 @@ var RemoteClient = class {
157887
157859
  this.cursorValid = true;
157888
157860
  this.ready = true;
157889
157861
  this.attempt = 0;
157862
+ if (this.opts.stableMs !== void 0) {
157863
+ this.settling = true;
157864
+ this.stableTimer = setTimeout(() => {
157865
+ this.settling = false;
157866
+ this.shortLived = 0;
157867
+ }, this.opts.stableMs);
157868
+ this.stableTimer.unref?.();
157869
+ }
157890
157870
  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
157871
  return;
157892
157872
  }
@@ -157958,11 +157938,13 @@ var RemoteClient = class {
157958
157938
  this.ws = null;
157959
157939
  const wasReady = this.ready;
157960
157940
  this.ready = false;
157941
+ if (wasReady && this.settling) this.shortLived++;
157942
+ this.settling = false;
157961
157943
  this.clearTimers();
157962
157944
  this.failAll("The link dropped");
157963
157945
  if (this.closed) return;
157964
157946
  if (wasReady) this.on.down?.(reason, detail);
157965
- if (this.opts.redials !== void 0 && this.attempt >= this.opts.redials) {
157947
+ if (this.opts.redials !== void 0 && (this.attempt >= this.opts.redials || this.shortLived >= this.opts.redials)) {
157966
157948
  this.on.gaveUp?.(reason);
157967
157949
  return;
157968
157950
  }
@@ -157983,6 +157965,7 @@ var RemoteClient = class {
157983
157965
  clearTimers() {
157984
157966
  clearTimeout(this.reconnectTimer);
157985
157967
  clearTimeout(this.handshakeTimer);
157968
+ clearTimeout(this.stableTimer);
157986
157969
  this.reconnectTimer = void 0;
157987
157970
  this.handshakeTimer = void 0;
157988
157971
  }
@@ -158123,7 +158106,7 @@ async function follow(c, id3) {
158123
158106
  const machine = c.machine ?? c.target;
158124
158107
  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
158108
  }
158126
- await new Promise((r) => setTimeout(r, wait));
158109
+ await sleep(wait);
158127
158110
  }
158128
158111
  }
158129
158112
  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 +158304,8 @@ var LinkedMachineConnection = class {
158321
158304
  client = null;
158322
158305
  /** The client when it stopped redialling (`gaveUp`, #1143) and is waiting, idle, for the next opening */
158323
158306
  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 */
158307
+ /** Sessions of the remote the hub shows, by the remote's id. Events of any other session are not passed on */
158325
158308
  shown = /* @__PURE__ */ new Set();
158326
- hidden = /* @__PURE__ */ new Set();
158327
158309
  /** The remote's coordinators as it last gave them: not shown, but read by a reader (#1941, `unshown`) */
158328
158310
  coordinators = /* @__PURE__ */ new Map();
158329
158311
  retryTimer;
@@ -158650,23 +158632,23 @@ var LinkedMachineConnection = class {
158650
158632
  return !!line.credential && c.id === line.credential && this.isPairedHere(c);
158651
158633
  }
158652
158634
  /**
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
- /**
158660
- * Marks, in a list the host answered with `nonce`, the entries whose `check` is of the secret this hub holds under
158661
- * their id (`own`). An entry without a check (a host from before it) or a credential this hub holds no secret of is not
158662
- * marked, and is compared by id alone
158663
- */
158635
+ * Marks, in a list the host answered with `nonce`, the entries whose `check` is of the secret this hub holds under
158636
+ * their id (`own`). An entry without a check (a host from before it) or a credential this hub holds no secret of is not
158637
+ * marked, and is compared by id alone
158638
+ */
158664
158639
  ownOf(list2, nonce) {
158665
158640
  return list2?.map((c) => {
158666
158641
  const verifier = this.pairedHere.get(c.id);
158667
158642
  return c.check === void 0 || !verifier ? c : { ...c, own: c.check === credentialCheck(verifier, nonce) };
158668
158643
  }) ?? null;
158669
158644
  }
158645
+ /**
158646
+ * The credentials used on the machine under this hub's own label that this hub did not pair (#1405 review), and that
158647
+ * `listed` does not name already, as used and gone. The machine's host leaves out a credential whose label's next one
158648
+ * was paired the same way, taking it for its pairer again; under a hub's label only the hub can tell. Counted from the
158649
+ * first use there of one this hub paired in its present run, so the credentials of its own earlier runs, which it does
158650
+ * not remember, are not taken for another's
158651
+ */
158670
158652
  notPairedHere(line, listed2) {
158671
158653
  if (!line.pairedAs) return [];
158672
158654
  const first = this.usedUnderHubLabels.findIndex((u) => this.isPairedHere(u));
@@ -158854,7 +158836,6 @@ var LinkedMachineConnection = class {
158854
158836
  observe(kind, list2) {
158855
158837
  if (kind === "sessions") {
158856
158838
  this.shown.clear();
158857
- this.hidden.clear();
158858
158839
  this.coordinators.clear();
158859
158840
  for (const s of list2) this.classify(s);
158860
158841
  this.deps.mirror.replace(this.id, "sessions", list2.filter((s) => !hiddenKind(s)));
@@ -159208,6 +159189,7 @@ var LinkedMachineConnection = class {
159208
159189
  // A host that can prove it holds the token is asked to, before the token goes out (#838)
159209
159190
  {
159210
159191
  redials: LINK_REDIALS,
159192
+ stableMs: this.deps.stableMs ?? STABLE_MS,
159211
159193
  ...this.deps.client,
159212
159194
  url: endpoint.url,
159213
159195
  token: endpoint.token,
@@ -159351,8 +159333,7 @@ var LinkedMachineConnection = class {
159351
159333
  }
159352
159334
  classify(s) {
159353
159335
  if (!isObj(s) || typeof s.id !== "string") return;
159354
- if (hiddenKind(s)) this.hidden.add(s.id);
159355
- else this.shown.add(s.id);
159336
+ if (!hiddenKind(s)) this.shown.add(s.id);
159356
159337
  if (s.kind === "coordinator") this.coordinators.set(s.id, s);
159357
159338
  }
159358
159339
  transportDown(reason) {
@@ -159367,14 +159348,6 @@ var LinkedMachineConnection = class {
159367
159348
  nextStep() {
159368
159349
  return this.autoReconnect ? KEEPS_TRYING : WAITS_FOR_PERSON;
159369
159350
  }
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
159351
  /** The link is up, or was just: remembers when, and keeps it for the next start of this host */
159379
159352
  seen() {
159380
159353
  this.lastConnectedAt = Date.now();
@@ -159385,6 +159358,14 @@ var LinkedMachineConnection = class {
159385
159358
  clearTimeout(this.retryTimer);
159386
159359
  this.nextAttemptAt = null;
159387
159360
  }
159361
+ /**
159362
+ * Asks the machine again after a delay. `backoff` (the default): the next step of the backoff, and nothing at all
159363
+ * while Auto reconnect is off (#1341), where only the person's Connect tries again (after the client's quick redials,
159364
+ * which an off machine keeps). `within`: part of an attempt that
159365
+ * is under way or of the person's own request (a refusal asked again at once, a Connect that met an opening), so it
159366
+ * goes ahead either way. `reads`: the versions check looking again for a change on either side; with the switch off it
159367
+ * reads the line and connects nothing (`readsOnly`)
159368
+ */
159388
159369
  retryLater(fixedMs, mode = "backoff") {
159389
159370
  if (this.stopped || this.held) return;
159390
159371
  if (mode === "backoff" && !this.autoReconnect && !this.inOperation()) return;
@@ -159806,13 +159787,10 @@ var LinkedMachine = class extends LinkedMachineConnection {
159806
159787
  const client = this.client;
159807
159788
  if (!client?.connected) return "unreachable";
159808
159789
  try {
159809
- const r = await (allowed === void 0 ? client.call("host.hubAuto", {}) : client.call("host.setHubAuto", { allowed }));
159810
- return r?.allowed === true ? "on" : "off";
159790
+ return await askHubAuto((method, params) => client.call(method, params), allowed);
159811
159791
  } catch (err) {
159812
- const message = err?.message ?? String(err);
159813
- if (/^Unknown method/.test(message)) return "older";
159814
159792
  if (!client.connected) return "unreachable";
159815
- throw rpcError(`${this.name} did not answer for its "Allow auto from a linked computer": ${message}`);
159793
+ throw rpcError(`${this.name} did not answer for its "Allow auto from a linked computer": ${errorMessage(err)}`);
159816
159794
  }
159817
159795
  }
159818
159796
  /**
@@ -160243,8 +160221,8 @@ function linkedSessions(machines, route = notRouted, hubName2, options = {}) {
160243
160221
  await settle(m);
160244
160222
  return m.reachable ? null : { ok: false, error: awaySentence(m, tail) };
160245
160223
  };
160246
- const attempt = async (m, run4) => {
160247
- const down = await reach(m, TAIL.tryAgain);
160224
+ const attempt = async (m, run4, tail = TAIL.tryAgain) => {
160225
+ const down = await reach(m, tail);
160248
160226
  if (down) return down;
160249
160227
  try {
160250
160228
  return { ok: true, ...await run4() };
@@ -160257,13 +160235,13 @@ function linkedSessions(machines, route = notRouted, hubName2, options = {}) {
160257
160235
  const m = find(id3);
160258
160236
  return m ? [m, null] : [null, { ok: false, error: refusal2(id3) }];
160259
160237
  };
160260
- const via = async (id3, run4) => {
160238
+ const via = async (id3, run4, tail) => {
160261
160239
  const [m, refused2] = found(id3);
160262
- return m ? attempt(m, () => run4(m)) : refused2;
160240
+ return m ? attempt(m, () => run4(m), tail) : refused2;
160263
160241
  };
160264
160242
  return [found, via];
160265
160243
  };
160266
- const [findSession, viaSession] = reaching(owner, (id3) => `Not a session of a linked machine: ${id3}`);
160244
+ const [, viaSession] = reaching(owner, (id3) => `Not a session of a linked machine: ${id3}`);
160267
160245
  const [findProject2, viaProject] = reaching(owner, (id3) => `Not a project of a linked machine: ${id3}`);
160268
160246
  const [findMachineRef, viaMachine] = reaching(machineById, (id3) => `No linked machine ${id3}`);
160269
160247
  const listOf = async (m, method) => {
@@ -160459,8 +160437,9 @@ function linkedSessions(machines, route = notRouted, hubName2, options = {}) {
160459
160437
  },
160460
160438
  hubAuto(machineId) {
160461
160439
  return viaMachine(machineId, async (m) => {
160462
- const r = await m.call("host.hubAuto", {});
160463
- return { allowed: r?.allowed === true };
160440
+ const state = await askHubAuto((method, params) => m.call(method, params));
160441
+ if (state === "older") throw new Error(`${m.name} runs a Centralu from before "Allow auto from a linked computer"`);
160442
+ return { allowed: state === "on" };
160464
160443
  });
160465
160444
  },
160466
160445
  origin(project2) {
@@ -160625,20 +160604,18 @@ function linkedSessions(machines, route = notRouted, hubName2, options = {}) {
160625
160604
  const p = (m.lastKnown("projects") ?? []).find((x) => isObj(x) && x.id === s.projectId);
160626
160605
  return { name: oneLine4(s.name), project: `${isObj(p) ? oneLine4(p.name) : "(none)"} on ${m.name}` };
160627
160606
  },
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
- }
160607
+ lastWords(sessionId, maxChars, opts) {
160608
+ return viaSession(
160609
+ sessionId,
160610
+ async (m) => {
160611
+ 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 } : {} });
160612
+ const r = HostLastAnswerResult.safeParse(answer);
160613
+ if (!r.success) throw new Error(`${m.name} answered in a shape this Centralu does not read`);
160614
+ const text3 = r.data.text.length > maxChars || r.data.length > r.data.text.length ? `${r.data.text.slice(0, maxChars)}\u2026` : r.data.text;
160615
+ return { text: text3, length: r.data.length, seq: r.data.seq };
160616
+ },
160617
+ TAIL.none
160618
+ );
160642
160619
  },
160643
160620
  async list() {
160644
160621
  const perMachine = await Promise.all(
@@ -160656,42 +160633,40 @@ function linkedSessions(machines, route = notRouted, hubName2, options = {}) {
160656
160633
  machineOf(sessionId) {
160657
160634
  return owner(sessionId)?.name ?? null;
160658
160635
  },
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;
160636
+ read(sessionId, limit, opts) {
160664
160637
  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
- }
160638
+ return viaSession(
160639
+ sessionId,
160640
+ async (m) => {
160641
+ const answer = await m.call("host.readSession", {
160642
+ sessionId: m.q.strip(sessionId, "The session"),
160643
+ ...limit !== void 0 ? { limit } : {},
160644
+ ...opts?.around !== void 0 ? { around: opts.around } : {},
160645
+ ...opts?.tools !== void 0 ? { toolLines: toolMode } : {},
160646
+ ...opts?.maxChars !== void 0 ? { maxChars: budgetOf(opts.maxChars) } : {},
160647
+ ...opts?.before !== void 0 ? { before: opts.before } : {},
160648
+ ...opts?.from !== void 0 ? { from: opts.from } : {},
160649
+ ...opts?.offset !== void 0 ? { offset: opts.offset } : {}
160650
+ });
160651
+ const r = HostReadSessionResult.safeParse(answer);
160652
+ if (!r.success) throw new Error(`${m.name} answered in a shape this Centralu does not read`);
160653
+ const all = r.data.lines;
160654
+ const kept = toolMode === "none" ? all.filter((l) => !isToolLine(l)) : all;
160655
+ const spot = Math.min(Math.floor(Math.min(limit ?? 40, 200) / 2), all.length - 1);
160656
+ const around = opts?.around !== void 0 && opts.before === void 0 && opts.from === void 0;
160657
+ const center = !around ? null : Math.min(toolMode === "none" ? all.slice(0, spot).filter((l) => !isToolLine(l)).length : spot, kept.length - 1);
160658
+ const lines = fitMachineLines(kept, budgetOf(opts?.maxChars), center);
160659
+ return { state: oneLine4(r.data.state, 40), lines: lines.map(fencedLine) };
160660
+ },
160661
+ TAIL.readAgain
160662
+ );
160688
160663
  },
160689
160664
  async recall(query, limit) {
160690
160665
  const notSearched = [];
160691
160666
  const hits = await Promise.all(
160692
160667
  [...machines()].map(async (m) => {
160693
160668
  if (!m.reachable) {
160694
- notSearched.push(`${m.name} (not reachable right now)`);
160669
+ notSearched.push(`${m.name} (${m.waitsForPerson ? "not connected; ask the person to connect it in Settings, Machines, Connect" : NOT_REACHABLE})`);
160695
160670
  return [];
160696
160671
  }
160697
160672
  try {
@@ -161100,7 +161075,7 @@ var Router = class {
161100
161075
  if (method === "apps.list" && isObj(params) && typeof params.machine === "string") {
161101
161076
  return this.byMachine(method, params, { result: (r, q2) => Array.isArray(r) ? r.map((a) => q2.app(a)) : r }, ctx);
161102
161077
  }
161103
- return this.merge(method, params);
161078
+ return this.merge(method, params, ctx);
161104
161079
  }
161105
161080
  };
161106
161081
  /** The linked machine an id names, or null for the hub's own (an unprefixed id, or a prefix no link has) */
@@ -161213,7 +161188,7 @@ var Router = class {
161213
161188
  * mirror for it, marked `unreachable` and with `live` as last known, so the UI neither drops
161214
161189
  * them nor tries to wake them (§5, "Recovery").
161215
161190
  */
161216
- async merge(method, params) {
161191
+ async merge(method, params, ctx) {
161217
161192
  const machines = [...this.opts.machines()];
161218
161193
  switch (method) {
161219
161194
  case "sessions.list":
@@ -161221,9 +161196,9 @@ var Router = class {
161221
161196
  case "projects.list":
161222
161197
  return this.mergeLists(machines, "projects", params);
161223
161198
  case "projects.reorder":
161224
- return this.reorderProjects(machines, params);
161199
+ return this.reorderProjects(machines, params, ctx);
161225
161200
  case "approvals.deleteRule":
161226
- return this.deleteRule(machines, params);
161201
+ return this.deleteRule(machines, params, ctx);
161227
161202
  default:
161228
161203
  return this.mergeSimple(method, machines, params);
161229
161204
  }
@@ -161285,7 +161260,7 @@ var Router = class {
161285
161260
  * out of it: a stale window cannot move what it never saw. The kept order forgets a project the hub no longer lists,
161286
161261
  * unless it is a linked machine's, which may only be out of reach.
161287
161262
  */
161288
- async reorderProjects(machines, params) {
161263
+ async reorderProjects(machines, params, ctx) {
161289
161264
  const { orderedIds } = RpcMethods["projects.reorder"].params.parse(params);
161290
161265
  const kept = this.opts.order?.read() ?? [];
161291
161266
  const full = keepUnseen(orderedIds, kept);
@@ -161302,7 +161277,7 @@ var Router = class {
161302
161277
  const ordered = perMachine.get(m);
161303
161278
  if (!ordered) return this.listOf(m, "projects");
161304
161279
  try {
161305
- return m.q.projects(await this.forward(m, "projects.reorder", { orderedIds: ordered }));
161280
+ return m.q.projects(await this.forward(m, "projects.reorder", { orderedIds: ordered }, ctx));
161306
161281
  } catch {
161307
161282
  return this.present(m, "projects", m.lastKnown("projects") ?? [], true);
161308
161283
  }
@@ -161315,13 +161290,13 @@ var Router = class {
161315
161290
  }
161316
161291
  return machines.length > 0 ? this.inPersonsOrder(all) : all;
161317
161292
  }
161318
- async deleteRule(machines, params) {
161293
+ async deleteRule(machines, params, ctx) {
161319
161294
  const id3 = isObj(params) ? params.id : void 0;
161320
161295
  const folded = typeof id3 === "number" ? decodeNumber(id3) : null;
161321
161296
  if (!folded) return this.opts.local("approvals.deleteRule", params);
161322
161297
  const m = machines.find((x) => x.q.slot === folded.slot);
161323
161298
  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 });
161299
+ return this.forward(m, "approvals.deleteRule", { id: folded.n }, ctx);
161325
161300
  }
161326
161301
  /** The other merges: every reachable machine, each answer in the hub's terms, combined by kind */
161327
161302
  async mergeSimple(method, machines, params) {
@@ -161892,7 +161867,7 @@ var handedToken = launch.tokenOnStdin ? await readHandedToken(process.stdin).cat
161892
161867
  console.error(`[agent-host] the launcher did not hand over the token: ${e.message}`);
161893
161868
  process.exit(1);
161894
161869
  }) : launch.token;
161895
- var BUILD = true ? "3bbc42b" : "dev";
161870
+ var BUILD = true ? "5f99652" : "dev";
161896
161871
  var { values } = parseArgs({
161897
161872
  options: {
161898
161873
  port: { type: "string", default: "5175" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@centralu/host-linux-x64",
3
- "version": "0.1.0-beta.50",
3
+ "version": "0.1.0-beta.51",
4
4
  "description": "Centralu's host without the window, for Linux on x86-64: what a hub installs on a linked machine",
5
5
  "license": "MIT",
6
6
  "author": "ijun17",