usebeeline 0.0.108 → 0.0.110

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.
Files changed (2) hide show
  1. package/dist/usebeeline.mjs +196 -27
  2. package/package.json +1 -1
@@ -8476,7 +8476,7 @@ async function installGoogleTool(options) {
8476
8476
  return fail("connector type", `${options.connectorType} is not a Google tool connector`);
8477
8477
  }
8478
8478
  push(step("Google credentials resolved", "running", { output: "resolving\u2026" }));
8479
- const resolved = options.resolveCredentials ? await options.resolveCredentials() : await (async () => {
8479
+ const resolved = options.resolvedCredentials ? await options.resolvedCredentials : options.resolveCredentials ? await options.resolveCredentials() : await (async () => {
8480
8480
  const oneClick = await readGoogleCredentialsFromVault(options.squire);
8481
8481
  return oneClick.source === "squire" ? { ...oneClick } : { ...loadManualGoogleCredentials(options.home, options.env) };
8482
8482
  })();
@@ -8515,7 +8515,31 @@ function describe(error) {
8515
8515
 
8516
8516
  // apps/body/dist/connector-squire.js
8517
8517
  import { execFile, spawn as spawn2 } from "node:child_process";
8518
- var SQUIRE_CONNECT_PACKAGE = "@trusty-squire/mcp@next";
8518
+ var SQUIRE_MCP_NAME = "@trusty-squire/mcp";
8519
+ var SQUIRE_CONNECT_PACKAGE = `${SQUIRE_MCP_NAME}@latest`;
8520
+ var activeConnectSession;
8521
+ function isProcessAlive(pid) {
8522
+ if (pid === void 0 || pid <= 0)
8523
+ return false;
8524
+ try {
8525
+ process.kill(pid, 0);
8526
+ return true;
8527
+ } catch (error) {
8528
+ return error?.code === "EPERM";
8529
+ }
8530
+ }
8531
+ function releaseSquireConnectSession(log2) {
8532
+ const claim = activeConnectSession;
8533
+ activeConnectSession = void 0;
8534
+ if (!claim)
8535
+ return;
8536
+ if (isProcessAlive(claim.pid)) {
8537
+ log2?.(`released trusty-squire connect session claim (pid ${String(claim.pid)} still live \u2014 a fresh connect owns the browser now)`);
8538
+ claim.abort();
8539
+ } else {
8540
+ log2?.("cleared dead trusty-squire connect session claim (owner process gone)");
8541
+ }
8542
+ }
8519
8543
  var defaultShellRunner = (command, args) => new Promise((resolve31) => {
8520
8544
  execFile(command, [...args], { timeout: 12e4, maxBuffer: 4 * 1024 * 1024, encoding: "utf8" }, (error, stdout6, stderr) => {
8521
8545
  const code = error?.code;
@@ -8537,7 +8561,7 @@ var defaultStreamedRunner = (command, args) => new Promise((resolve31) => {
8537
8561
  const safetyTimer = setTimeout(() => {
8538
8562
  if (!resolved) {
8539
8563
  resolved = true;
8540
- resolve31({ stdout: stdout6, stderr, signIn: void 0, abort: () => {
8564
+ resolve31({ stdout: stdout6, stderr, pid: child.pid ?? void 0, signIn: void 0, abort: () => {
8541
8565
  } });
8542
8566
  }
8543
8567
  child.kill();
@@ -8546,6 +8570,12 @@ var defaultStreamedRunner = (command, args) => new Promise((resolve31) => {
8546
8570
  clearTimeout(safetyTimer);
8547
8571
  child.kill();
8548
8572
  };
8573
+ const claim = {
8574
+ pid: child.pid ?? void 0,
8575
+ claimedAt: Date.now(),
8576
+ abort
8577
+ };
8578
+ activeConnectSession = claim;
8549
8579
  const finish = (result) => {
8550
8580
  if (!resolved) {
8551
8581
  resolved = true;
@@ -8570,11 +8600,11 @@ ${stderr}`;
8570
8600
  checkOutput();
8571
8601
  });
8572
8602
  child.on("close", () => {
8573
- finish({ stdout: stdout6, stderr, signIn: void 0, abort: () => {
8603
+ finish({ stdout: stdout6, stderr, pid: child.pid ?? void 0, signIn: void 0, abort: () => {
8574
8604
  } });
8575
8605
  });
8576
8606
  child.on("error", () => {
8577
- finish({ stdout: stdout6, stderr, signIn: void 0, abort: () => {
8607
+ finish({ stdout: stdout6, stderr, pid: child.pid ?? void 0, signIn: void 0, abort: () => {
8578
8608
  } });
8579
8609
  });
8580
8610
  });
@@ -8595,17 +8625,57 @@ function parseConnectOutput(output) {
8595
8625
  }
8596
8626
  return { method: "streamed-page", url };
8597
8627
  }
8598
- async function installedSquireVersion(run2) {
8599
- const probe = await run2("npx", ["-y", SQUIRE_CONNECT_PACKAGE, "--version"]);
8628
+ async function installedSquireVersion(run2, spec = SQUIRE_CONNECT_PACKAGE, preferOnline = false) {
8629
+ const probe = await run2("npx", [
8630
+ ...preferOnline ? ["--prefer-online"] : [],
8631
+ "-y",
8632
+ spec,
8633
+ "--version"
8634
+ ]);
8600
8635
  const version = probe.stdout.match(/\d+\.\d+\.\d+[^\s]*/)?.[0];
8601
8636
  return version;
8602
8637
  }
8638
+ async function currentSquireRelease(run2) {
8639
+ const probe = await run2("npm", ["view", SQUIRE_MCP_NAME, "version"]);
8640
+ if (probe.code !== 0)
8641
+ return void 0;
8642
+ return probe.stdout.match(/\d+\.\d+\.\d+[^\s]*/)?.[0];
8643
+ }
8644
+ async function resolveSquireConnectSpec(run2) {
8645
+ const currentRelease = await currentSquireRelease(run2);
8646
+ const first = await installedSquireVersion(run2);
8647
+ if (currentRelease === void 0 || first === currentRelease) {
8648
+ return {
8649
+ npxArgs: ["-y", SQUIRE_CONNECT_PACKAGE],
8650
+ ...first !== void 0 ? { resolvedVersion: first } : {},
8651
+ ...currentRelease !== void 0 ? { currentRelease } : {},
8652
+ reResolved: false
8653
+ };
8654
+ }
8655
+ const fresh = await installedSquireVersion(run2, SQUIRE_CONNECT_PACKAGE, true);
8656
+ if (fresh === currentRelease) {
8657
+ return {
8658
+ npxArgs: ["--prefer-online", "-y", SQUIRE_CONNECT_PACKAGE],
8659
+ resolvedVersion: fresh,
8660
+ currentRelease,
8661
+ reResolved: true
8662
+ };
8663
+ }
8664
+ return {
8665
+ npxArgs: ["-y", `${SQUIRE_MCP_NAME}@${currentRelease}`],
8666
+ ...fresh !== void 0 ? { resolvedVersion: fresh } : {},
8667
+ currentRelease,
8668
+ reResolved: true
8669
+ };
8670
+ }
8603
8671
  function parseSignedInAs(output) {
8604
8672
  return output.match(/signed in as ([^\s,;]+)/i)?.[1];
8605
8673
  }
8606
8674
  async function installSquire(options) {
8607
8675
  const run2 = options.run ?? defaultShellRunner;
8608
8676
  const streamRun = options.streamRun ?? defaultStreamedRunner;
8677
+ const log2 = options.log ?? (() => {
8678
+ });
8609
8679
  const steps = [step2("helper reached", "done")];
8610
8680
  const emit = () => options.onProgress?.([...steps]);
8611
8681
  const push = (next) => {
@@ -8618,9 +8688,13 @@ async function installSquire(options) {
8618
8688
  return { status: "error", steps, errorMessage: reason };
8619
8689
  };
8620
8690
  emit();
8691
+ releaseSquireConnectSession(log2);
8692
+ const resolution = await resolveSquireConnectSpec(run2);
8693
+ if (resolution.reResolved) {
8694
+ log2(`trusty-squire stale copy ${resolution.resolvedVersion ?? "unknown"} re-resolved against current release ${resolution.currentRelease}`);
8695
+ }
8621
8696
  const install = await streamRun("npx", [
8622
- "-y",
8623
- SQUIRE_CONNECT_PACKAGE,
8697
+ ...resolution.npxArgs,
8624
8698
  "connect",
8625
8699
  "--force-relogin=google",
8626
8700
  "--target=codex",
@@ -8631,7 +8705,7 @@ async function installSquire(options) {
8631
8705
  push(step2("trusty-squire installed", "failed", stderr || "connect printed no sign-in URL"));
8632
8706
  return fail(stderr || "the trusty-squire connect command printed no sign-in surface");
8633
8707
  }
8634
- const version = await installedSquireVersion(run2);
8708
+ const version = resolution.resolvedVersion;
8635
8709
  push(step2(`trusty-squire${version ? ` ${version}` : ""} installed`, "done"));
8636
8710
  const signIn = install.signIn;
8637
8711
  const signedInAs = parseSignedInAs(`${install.stdout}
@@ -8788,9 +8862,10 @@ var StdioSquireMcpClient = class {
8788
8862
  }
8789
8863
  initialize() {
8790
8864
  const child = (this.options.spawn ?? spawn3)(this.options.command ?? "npx", [
8791
- // `@next` RC: only the RC coordinates with a running Trusty Squire broker
8792
- // for concurrent sessions; stable `latest` cannot share its browser.
8793
- ...this.options.args ?? ["-y", "@trusty-squire/mcp@next"]
8865
+ // `@latest`: the current release, never a source-level version pin
8866
+ // (captain). Only the vault MCP server the agent spawns; the connect
8867
+ // install path verifies freshness itself (connector-squire.ts).
8868
+ ...this.options.args ?? ["-y", "@trusty-squire/mcp@latest"]
8794
8869
  ]);
8795
8870
  this.child = child;
8796
8871
  this.buffer = "";
@@ -8899,7 +8974,12 @@ var ConnectorAssignmentLoop = class {
8899
8974
  this.log = options.log ?? (() => {
8900
8975
  });
8901
8976
  this.install = options.install ?? installSquire;
8902
- this.installGoogle = options.installGoogle ?? ((connectorType, onProgress) => installGoogleTool({ connectorType, home: this.googleHome(), onProgress }));
8977
+ this.installGoogle = options.installGoogle ?? ((connectorType, onProgress, sharedCredentials) => installGoogleTool({
8978
+ connectorType,
8979
+ home: this.googleHome(),
8980
+ onProgress,
8981
+ resolvedCredentials: sharedCredentials ? sharedCredentials() : this.resolveGoogleCredentials()
8982
+ }));
8903
8983
  this.googleHomeDir = options.googleHome ?? process.env.BEELINE_AGENT_HOME ?? process.cwd();
8904
8984
  this.readVaultFn = options.readVault ?? readVault;
8905
8985
  this.revokeGrantsFn = options.revokeGrants ?? revokeGrants;
@@ -8941,15 +9021,60 @@ var ConnectorAssignmentLoop = class {
8941
9021
  this.log(`connector assignments unavailable: ${describe2(error)}`);
8942
9022
  return;
8943
9023
  }
9024
+ let googleBatch;
8944
9025
  for (const assignment of assignments) {
8945
9026
  const key = `${assignment.kind}:${assignment.connectorId}`;
8946
9027
  if (assignment.kind === "uninstall")
8947
9028
  continue;
9029
+ if (assignment.kind === "install" && isGoogleToolConnectorType(assignment.connectorType)) {
9030
+ if (this.inFlight.has(key))
9031
+ continue;
9032
+ this.inFlight.add(key);
9033
+ (googleBatch ??= []).push(assignment);
9034
+ continue;
9035
+ }
8948
9036
  if (this.inFlight.has(key))
8949
9037
  continue;
8950
9038
  this.inFlight.add(key);
8951
9039
  void this.handle(assignment).catch((error) => this.log(`connector assignment ${key} failed: ${describe2(error)}`)).finally(() => this.inFlight.delete(key));
8952
9040
  }
9041
+ if (googleBatch)
9042
+ this.flushGoogleBatch(googleBatch);
9043
+ }
9044
+ flushGoogleBatch(batch) {
9045
+ const keys = batch.map((a2) => `${a2.kind}:${a2.connectorId}`);
9046
+ void this.runGoogleBatch(batch).catch((error) => this.log(`google connector installs failed: ${describe2(error)}`)).finally(() => {
9047
+ for (const key of keys)
9048
+ this.inFlight.delete(key);
9049
+ });
9050
+ }
9051
+ /** The Google tool connectors ride ONE grant: every install in the batch
9052
+ * shares one credential resolution (the single Google consent) and the
9053
+ * installs run one at a time. Each tool still verifies and fails
9054
+ * independently — a grant that cannot serve one tool's scope refuses that
9055
+ * tool alone, never its siblings. */
9056
+ async runGoogleBatch(batch) {
9057
+ let shared;
9058
+ const sharedCredentials = () => shared ??= this.resolveGoogleCredentials();
9059
+ for (const assignment of batch) {
9060
+ await this.runGoogleInstall(assignment.connectorId, assignment.connectorType, sharedCredentials);
9061
+ }
9062
+ }
9063
+ /** The ONE grant resolution shared by every Google tool install of a
9064
+ * drain: the Squire one-click vault path first, then the manual
9065
+ * credentials path. Never rejects — a failure resolves as an unusable
9066
+ * grant each install reports through its own steps. */
9067
+ resolveGoogleCredentials() {
9068
+ return (async () => {
9069
+ try {
9070
+ const oneClick = await readGoogleCredentialsFromVault(this.squire());
9071
+ if (oneClick.source === "squire")
9072
+ return oneClick;
9073
+ } catch (error) {
9074
+ this.log(`google one-click grant lookup failed: ${describe2(error)}`);
9075
+ }
9076
+ return loadManualGoogleCredentials(this.googleHome(), process.env);
9077
+ })();
8953
9078
  }
8954
9079
  squire() {
8955
9080
  this.mcp ??= defaultSquireMcpClient();
@@ -8957,11 +9082,7 @@ var ConnectorAssignmentLoop = class {
8957
9082
  }
8958
9083
  async handle(assignment) {
8959
9084
  if (assignment.kind === "install") {
8960
- if (isGoogleToolConnectorType(assignment.connectorType)) {
8961
- await this.runGoogleInstall(assignment.connectorId, assignment.connectorType);
8962
- } else {
8963
- await this.runInstall(assignment.connectorId);
8964
- }
9085
+ await this.runInstall(assignment.connectorId);
8965
9086
  } else if (assignment.kind === "sync")
8966
9087
  await this.runSync();
8967
9088
  else if (assignment.kind === "revoke-grants")
@@ -8971,8 +9092,9 @@ var ConnectorAssignmentLoop = class {
8971
9092
  googleHome() {
8972
9093
  return this.googleHomeDir;
8973
9094
  }
8974
- /** Install one Google tool connector (Gmail/Calendar/Drive/YouTube). */
8975
- async runGoogleInstall(connectorId, connectorType) {
9095
+ /** Install one Google tool connector (Gmail/Calendar/Drive/YouTube),
9096
+ * riding the drain's shared grant resolution. */
9097
+ async runGoogleInstall(connectorId, connectorType, sharedCredentials) {
8976
9098
  const report = async (steps) => {
8977
9099
  try {
8978
9100
  await this.api.execute("postConnectorStatus", { agentId: this.agentId, connectorId, steps });
@@ -8980,7 +9102,7 @@ var ConnectorAssignmentLoop = class {
8980
9102
  this.log(`step report failed: ${describe2(error)}`);
8981
9103
  }
8982
9104
  };
8983
- const result = await this.installGoogle(connectorType, report);
9105
+ const result = await this.installGoogle(connectorType, report, sharedCredentials);
8984
9106
  if (result.status === "error") {
8985
9107
  await this.api.execute("postConnectorStatus", {
8986
9108
  agentId: this.agentId,
@@ -9008,7 +9130,8 @@ var ConnectorAssignmentLoop = class {
9008
9130
  const result = await this.install({
9009
9131
  workspaceId: this.agentId,
9010
9132
  mcp: this.squire(),
9011
- onProgress: report
9133
+ onProgress: report,
9134
+ log: (message) => this.log(`[trusty-squire] ${message}`)
9012
9135
  });
9013
9136
  if (result.status === "error") {
9014
9137
  await this.api.execute("postConnectorStatus", {
@@ -17902,6 +18025,7 @@ var DaemonApiClient = class {
17902
18025
  liveReconnect;
17903
18026
  liveReconnectDelayMs = 1e3;
17904
18027
  liveRooms = /* @__PURE__ */ new Map();
18028
+ roomsChangedListener;
17905
18029
  constructor(baseUrl, daemonToken, agentId, fetchImpl = fetch, webSocketFactory = (url, protocols) => new wrapper_default(url, protocols)) {
17906
18030
  this.baseUrl = baseUrl;
17907
18031
  this.daemonToken = daemonToken;
@@ -17948,6 +18072,12 @@ var DaemonApiClient = class {
17948
18072
  }
17949
18073
  };
17950
18074
  }
18075
+ /** Register the one listener invoked when the server reports this agent's
18076
+ * Room/corner memberships changed — the wake that discovers a freshly
18077
+ * created Room without waiting for the reconciliation heartbeat. */
18078
+ setRoomsChangedListener(listener) {
18079
+ this.roomsChangedListener = listener;
18080
+ }
17951
18081
  updateLiveCursor(roomId, cursor3) {
17952
18082
  const room = this.liveRooms.get(roomId);
17953
18083
  if (room && cursor3)
@@ -17981,6 +18111,7 @@ var DaemonApiClient = class {
17981
18111
  this.liveReconnectDelayMs = 1e3;
17982
18112
  for (const roomId of this.liveRooms.keys())
17983
18113
  this.sendLiveSubscription(roomId);
18114
+ this.roomsChangedListener?.();
17984
18115
  };
17985
18116
  socket.onmessage = (message) => {
17986
18117
  let value;
@@ -17992,6 +18123,10 @@ var DaemonApiClient = class {
17992
18123
  if (!value || typeof value !== "object")
17993
18124
  return;
17994
18125
  const event = value;
18126
+ if (event.type === "rooms-changed") {
18127
+ this.roomsChangedListener?.();
18128
+ return;
18129
+ }
17995
18130
  if (event.type === "subscribed" && typeof event.roomId === "string") {
17996
18131
  const capabilities = event.capabilities;
17997
18132
  this.liveRooms.get(event.roomId)?.onState?.(true, {
@@ -19259,7 +19394,7 @@ var BEELINE_ROOM_CAPABILITIES = [
19259
19394
  "Files and photos people share are downloaded for you: read them at the local path named in the prompt (photos may also arrive inline); never fetch the reference URL.",
19260
19395
  "To create a file (this Room has no other way to write one), call beeline-agent write_scratch_file with a relative path and content - text by default, or base64 for bytes you computed; it returns a path in your writable session home. To send a file, call beeline-agent post_artifact with a path inside your checkout or anywhere in your writable session home (wherever a file you or your harness generated actually landed, including one you just wrote), or with html/bytes content directly; it is uploaded and attached to your reply, and title and mime default from the file when you post by path. write_scratch_file produces the file, not a picture - turning it into a raster image needs a converter, which needs shell, which this Room does not have.",
19261
19396
  "To run something later or repeatedly, call beeline-agent create_schedule (interval in minutes or a 5-field cron, optional maxRuns); list_schedules / delete_schedule manage them.",
19262
- `To react to things that HAPPEN in this Room rather than only to what is said to you, call beeline-agent subscribe_events with the kinds you want (${SERVER_EVENT_KINDS.join(", ")}); each one then wakes you for a turn. It replaces your list, so send every kind you want - list_event_subscriptions shows the current one. You do this yourself: nobody has to configure it for you. grant-decided carries the grant id and status and resumes the turn that asked for the grant.`,
19397
+ `To react to things that HAPPEN in this Room rather than only to what is said to you, call beeline-agent subscribe_events with the kinds you want (${SERVER_EVENT_KINDS.join(", ")}); each one then wakes you for a turn. Subscriptions are per Room and cover every way the event lands: joining a Room you subscribed to wakes you, and so does a person arriving in the Workspace when that arrival projects into this Room - subscribe to joined in an onboarding Room and every newcomer wakes you, exactly like a greeter. It replaces your list, so send every kind you want - list_event_subscriptions shows the current one. You do this yourself: nobody has to configure it for you. grant-decided carries the grant id and status and resumes the turn that asked for the grant.`,
19263
19398
  "To state something that happened so the Room and other agents can act on it, call beeline-agent emit_event with your own agent:<slug> kind, one sentence, and optionally the agent members to wake. Chains of events are bounded and a refused emit posts nothing.",
19264
19399
  "When repository work is needed, you MUST call beeline-agent open_corner with a name of at most three words - it titles the corner everywhere - and a complete objective of no more than 24 words. The host-governed call is the only way to start write work.",
19265
19400
  "Never open a corner from your own reading of a person's ask. For every ask, first reply on one line `Proposed corner: <name> \u2014 <objective>`, using the exact title and objective you would pass to open_corner, then stop and wait. If one message contains several asks, list one numbered `Proposed corner:` line per ask; `go on 1 and 3` opens exactly those objectives and leaves the others proposed. A person's `go`, `yes`, or equivalent opens exactly the proposed corner; if they edit it, their edited text is the objective. Skip this ceremony only when the message itself explicitly commands a corner and states its scope, such as `open a corner and do X` or `go build X in a corner`; open that scope immediately.",
@@ -19412,7 +19547,7 @@ Then take exactly one action:
19412
19547
  }
19413
19548
 
19414
19549
  // apps/body/dist/external-mcp-capabilities.js
19415
- var SQUIRE_MCP_VERSION = "next";
19550
+ var SQUIRE_MCP_VERSION = "latest";
19416
19551
  var SQUIRE_MCP_PACKAGE = `@trusty-squire/mcp@${SQUIRE_MCP_VERSION}`;
19417
19552
  function isTrustySquireMcpLaunch(command, args = []) {
19418
19553
  return [command, ...args].some((value) => {
@@ -24509,6 +24644,25 @@ var DEFAULT_ROOM_WATCHDOG_STALE_MS = 9e4;
24509
24644
  var DEFAULT_RECONCILE_HEARTBEAT_MS = 6e4;
24510
24645
  var DEFAULT_DRAIN_DEADLINE_MS = 30 * 6e4;
24511
24646
  var CORNER_BRANCH_DELETE_ATTEMPTS = 3;
24647
+ var DiscoveryWakes = class {
24648
+ arrived = 0;
24649
+ served = 0;
24650
+ /** Called for every agent-directed `rooms-changed` wake. */
24651
+ wake() {
24652
+ this.arrived += 1;
24653
+ }
24654
+ needsFastReconcile() {
24655
+ return this.arrived !== this.served;
24656
+ }
24657
+ /** Snapshot at reconcile entry: the wake count its reads will cover. */
24658
+ beginReconcile() {
24659
+ return this.arrived;
24660
+ }
24661
+ /** Clear only wakes the completed start pass actually served. */
24662
+ completeReconcile(covered) {
24663
+ this.served = Math.max(this.served, covered);
24664
+ }
24665
+ };
24512
24666
  function shouldPostInitialCornerWorkingState(restore, isOpener = true) {
24513
24667
  return isOpener && !restore.featureBranch && !restore.lifecycle?.branch && !restore.lifecycle?.pr;
24514
24668
  }
@@ -24685,6 +24839,11 @@ var RoomRuntimeCoordinator = class {
24685
24839
  workspaceRemovalConfirmations = 0;
24686
24840
  roomRemovalConfirmations = /* @__PURE__ */ new Map();
24687
24841
  confirmationPending = false;
24842
+ /** Agent-directed discovery wakes (#1369), counted instead of flagged: a
24843
+ * wake that arrives while a reconcile is already running must survive its
24844
+ * start-pass clearing, or a corner opened mid-reconcile waits a heartbeat
24845
+ * for a wake the daemon already received. */
24846
+ discoveryWakes = new DiscoveryWakes();
24688
24847
  /** One command-grant runner per daemon; Rooms and corners register their checkouts on it. */
24689
24848
  grantRunner;
24690
24849
  grantRunnerServer;
@@ -24705,6 +24864,9 @@ var RoomRuntimeCoordinator = class {
24705
24864
  });
24706
24865
  this.grantRunnerServer = new GrantRunnerServer(this.grantRunner);
24707
24866
  this.connectorUsage = new ConnectorUsageRecorder();
24867
+ this.options.daemonApi.setRoomsChangedListener?.(() => {
24868
+ this.discoveryWakes.wake();
24869
+ });
24708
24870
  this.watchdogStaleMs = options.watchdogStaleMs ?? DEFAULT_ROOM_WATCHDOG_STALE_MS;
24709
24871
  this.reconcileHeartbeatMs = options.reconcileHeartbeatMs ?? DEFAULT_RECONCILE_HEARTBEAT_MS;
24710
24872
  this.drainDeadlineMs = options.drainDeadlineMs ?? DEFAULT_DRAIN_DEADLINE_MS;
@@ -24735,7 +24897,7 @@ var RoomRuntimeCoordinator = class {
24735
24897
  return this.scheduler.snapshot();
24736
24898
  }
24737
24899
  needsFastReconcile() {
24738
- return this.confirmationPending;
24900
+ return this.confirmationPending || this.discoveryWakes.needsFastReconcile();
24739
24901
  }
24740
24902
  reconcileHeartbeatIntervalMs() {
24741
24903
  return this.reconcileHeartbeatMs;
@@ -24771,6 +24933,7 @@ var RoomRuntimeCoordinator = class {
24771
24933
  }
24772
24934
  async reconcile() {
24773
24935
  this.confirmationPending = false;
24936
+ const coveredWakes = this.discoveryWakes.beginReconcile();
24774
24937
  const bootstrap = await this.options.daemonApi.execute("getDaemonBootstrap", {
24775
24938
  agentId: this.agent.publicKey
24776
24939
  });
@@ -24838,8 +25001,13 @@ var RoomRuntimeCoordinator = class {
24838
25001
  }
24839
25002
  }
24840
25003
  await mapWithConcurrency(desiredTopRooms, ROOM_JOIN_CONCURRENCY, async (roomId) => {
24841
- if (!this.running.has(roomId))
25004
+ if (this.running.has(roomId))
25005
+ return;
25006
+ try {
24842
25007
  await this.startRoom(roomId);
25008
+ } catch (error) {
25009
+ console.error(`[thin-core] failed to start Room ${roomId}:`, error);
25010
+ }
24843
25011
  });
24844
25012
  await mapWithConcurrency([...desiredCorners.values()], ROOM_JOIN_CONCURRENCY, async (corner) => {
24845
25013
  if (!this.running.has(corner.cornerId))
@@ -24847,6 +25015,7 @@ var RoomRuntimeCoordinator = class {
24847
25015
  });
24848
25016
  for (const running of this.running.values())
24849
25017
  running.body.requestReconciliation();
25018
+ this.discoveryWakes.completeReconcile(coveredWakes);
24850
25019
  return "member";
24851
25020
  }
24852
25021
  roomRecord(roomId) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usebeeline",
3
- "version": "0.0.108",
3
+ "version": "0.0.110",
4
4
  "description": "Connect an AI coding agent to Beeline with one command.",
5
5
  "homepage": "https://usebeeline.app",
6
6
  "bugs": {