usebeeline 0.0.109 → 0.0.111

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/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  <p align="center"><strong>Team messaging for agents and humans.</strong></p>
10
10
  <p align="center">One Room for your people and your coding agents. Talk it through, hand off the work, watch it merge.</p>
11
11
 
12
- `usebeeline` connects **a coding agent you already run — Claude Code, Codex, Goose, Pi, or Grok — to a Room in the Beeline app on your phone**. One command on the machine where the agent lives, and it walks into the conversation as a member: it reads what your teammates actually said, answers when it is tagged, and takes work away when someone asks it to. In top-level Rooms and corners, it can also continue the conversation with the person it last addressed, without piling on from another agent. Nothing is retyped into a prompt box.
12
+ `usebeeline` connects **a coding agent you already run — Claude Code, Codex, Goose, Pi, Grok, or Cursor — to a Room in the Beeline app on your phone**. One command on the machine where the agent lives, and it walks into the conversation as a member: it reads what your teammates actually said, answers when it is tagged, and takes work away when someone asks it to. In top-level Rooms and corners, it can also continue the conversation with the person it last addressed, without piling on from another agent. Nothing is retyped into a prompt box.
13
13
 
14
14
  The agent stays on your machine. Your provider key stays on your machine. What crosses the wire is the conversation, and — when repository work starts — a pull request.
15
15
 
@@ -6092,16 +6092,18 @@ var AGENT_KINDS = [
6092
6092
  "goose",
6093
6093
  "pi",
6094
6094
  "grok",
6095
+ "cursor",
6095
6096
  "reference",
6096
6097
  "custom"
6097
6098
  ];
6098
- var AUTO_DETECT_AGENT_KINDS = ["codex", "claude", "goose", "pi", "grok"];
6099
+ var AUTO_DETECT_AGENT_KINDS = ["codex", "claude", "goose", "pi", "grok", "cursor"];
6099
6100
  var AGENT_EXECUTABLES = {
6100
6101
  codex: "codex",
6101
6102
  claude: "claude",
6102
6103
  goose: "goose",
6103
6104
  pi: "pi",
6104
- grok: "grok"
6105
+ grok: "grok",
6106
+ cursor: "cursor-agent"
6105
6107
  };
6106
6108
  var ADAPTER_INSTALL_COMMANDS = {
6107
6109
  codex: {
@@ -6115,6 +6117,10 @@ var ADAPTER_INSTALL_COMMANDS = {
6115
6117
  pi: {
6116
6118
  command: "npm",
6117
6119
  args: ["install", "-g", "pi-acp"]
6120
+ },
6121
+ cursor: {
6122
+ command: "npm",
6123
+ args: ["install", "-g", "cursor-agent-acp"]
6118
6124
  }
6119
6125
  };
6120
6126
  function adapterInstallHint(kind) {
@@ -6295,6 +6301,11 @@ function resolveAgentCommand(opts) {
6295
6301
  const command2 = requireExecutable("pi-acp", env, cwd, `Pi needs an ACP adapter. Install it with \`${adapterInstallHint("pi")}\`, then retry with \`--agent pi\`.`);
6296
6302
  return { kind: typedKind, command: command2, args: [] };
6297
6303
  }
6304
+ if (typedKind === "cursor") {
6305
+ requireExecutable("cursor-agent", env, cwd, "Cursor Agent CLI not found. Install it from https://cursor.com/docs/cli, then retry with `--agent cursor`.");
6306
+ const command2 = requireExecutable("cursor-agent-acp", env, cwd, `Cursor ACP adapter not found. Install it with \`${adapterInstallHint("cursor")}\`, then retry with \`--agent cursor\`.`);
6307
+ return { kind: typedKind, command: command2, args: [] };
6308
+ }
6298
6309
  if (typedKind === "grok") {
6299
6310
  const command2 = requireExecutable("grok", env, cwd, "Grok CLI not found. Install it with `curl -fsSL https://x.ai/cli/install.sh | bash`, then retry with `--agent grok`.");
6300
6311
  return { kind: typedKind, command: command2, args: ["agent", "stdio"] };
@@ -6762,6 +6773,13 @@ var PROFILES = [
6762
6773
  enforcement: "allowlisted",
6763
6774
  note: "buzz-agent mounts only the MCP servers the daemon passes; it has no operator-global tool config"
6764
6775
  }
6776
+ },
6777
+ {
6778
+ match: /(^|[/\\])cursor-agent-acp(\.[a-z]+)?$/i,
6779
+ profile: {
6780
+ enforcement: "config-isolated",
6781
+ note: "cursor-agent-acp bridges cursor-agent to ACP; cursor-agent loads MCP servers from ~/.cursor/mcp.json, so an isolated CURSOR_HOME scopes the session"
6782
+ }
6765
6783
  }
6766
6784
  ];
6767
6785
  function sessionToolScopeMeta(agentCommand) {
@@ -18025,6 +18043,8 @@ var DaemonApiClient = class {
18025
18043
  liveReconnect;
18026
18044
  liveReconnectDelayMs = 1e3;
18027
18045
  liveRooms = /* @__PURE__ */ new Map();
18046
+ roomsChangedListener;
18047
+ configChangedListener;
18028
18048
  constructor(baseUrl, daemonToken, agentId, fetchImpl = fetch, webSocketFactory = (url, protocols) => new wrapper_default(url, protocols)) {
18029
18049
  this.baseUrl = baseUrl;
18030
18050
  this.daemonToken = daemonToken;
@@ -18071,6 +18091,18 @@ var DaemonApiClient = class {
18071
18091
  }
18072
18092
  };
18073
18093
  }
18094
+ /** Register the one listener invoked when the server reports this agent's
18095
+ * Room/corner memberships changed — the wake that discovers a freshly
18096
+ * created Room without waiting for the reconciliation heartbeat. */
18097
+ setRoomsChangedListener(listener) {
18098
+ this.roomsChangedListener = listener;
18099
+ }
18100
+ /** Register the one listener invoked when the server reports the agent's
18101
+ * model/effort selection changed — the wake that hot-restarts every
18102
+ * retained session (agent-wide; the event names no single Room). */
18103
+ setConfigChangedListener(listener) {
18104
+ this.configChangedListener = listener;
18105
+ }
18074
18106
  updateLiveCursor(roomId, cursor3) {
18075
18107
  const room = this.liveRooms.get(roomId);
18076
18108
  if (room && cursor3)
@@ -18104,6 +18136,7 @@ var DaemonApiClient = class {
18104
18136
  this.liveReconnectDelayMs = 1e3;
18105
18137
  for (const roomId of this.liveRooms.keys())
18106
18138
  this.sendLiveSubscription(roomId);
18139
+ this.roomsChangedListener?.();
18107
18140
  };
18108
18141
  socket.onmessage = (message) => {
18109
18142
  let value;
@@ -18115,6 +18148,14 @@ var DaemonApiClient = class {
18115
18148
  if (!value || typeof value !== "object")
18116
18149
  return;
18117
18150
  const event = value;
18151
+ if (event.type === "rooms-changed") {
18152
+ this.roomsChangedListener?.();
18153
+ return;
18154
+ }
18155
+ if (event.type === "config-changed") {
18156
+ this.configChangedListener?.();
18157
+ return;
18158
+ }
18118
18159
  if (event.type === "subscribed" && typeof event.roomId === "string") {
18119
18160
  const capabilities = event.capabilities;
18120
18161
  this.liveRooms.get(event.roomId)?.onState?.(true, {
@@ -18498,6 +18539,10 @@ var HARNESS_HOME_STATE_DIRS = [
18498
18539
  {
18499
18540
  match: /(^|[/\\])grok(\.[a-z]+)?$/i,
18500
18541
  dirs: [".grok"]
18542
+ },
18543
+ {
18544
+ match: /(^|[/\\])cursor-agent-acp(\.[a-z]+)?$/i,
18545
+ dirs: [".cursor"]
18501
18546
  }
18502
18547
  ];
18503
18548
  function harnessHomeStateDirs(agentCommand, home = homedir3()) {
@@ -19369,6 +19414,7 @@ function isAgentPairingCode(value) {
19369
19414
 
19370
19415
  // apps/body/dist/beeline-skill.js
19371
19416
  var USING_BEELINE_SKILL_NAME = "using-beeline";
19417
+ var BEELINE_TRIAGE_SKILL_NAME = "beeline-triage";
19372
19418
  var BEELINE_REVIEW_SKILL_NAME = "beeline-review";
19373
19419
  function isConfiguredReviewer(agentHandle, reviewerHandle) {
19374
19420
  return Boolean(agentHandle && reviewerHandle && agentHandle.replace(/^@/, "") === reviewerHandle.replace(/^@/, ""));
@@ -19382,9 +19428,10 @@ var BEELINE_ROOM_CAPABILITIES = [
19382
19428
  "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.",
19383
19429
  "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.",
19384
19430
  "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.",
19385
- `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.`,
19431
+ `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.`,
19386
19432
  "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.",
19387
19433
  "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.",
19434
+ "Before emitting `Proposed corner:` or calling open_corner, consult the release-versioned beeline-triage skill and follow it. An unclear request requires a question; a warranted-work or desirability warning does not block the proposal or corner.",
19388
19435
  "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.",
19389
19436
  "Agreement is not action: never merely acknowledge an ask. Reply with a proposed corner, a question, or a line beginning `parked:` with the reason.",
19390
19437
  "When open_corner succeeds, the server posts the corner card: do not announce or restate the opening. End the turn with nothing more unless the person asked something else.",
@@ -19476,6 +19523,11 @@ Follow these steps in order. Do not skip or reorder them.
19476
19523
 
19477
19524
  ## 2. P0 - OBJECTIVE FULFILLED, DEMONSTRATED
19478
19525
 
19526
+ Before judging the implementation, independently repeat the two judgment legs from request triage:
19527
+
19528
+ - **Work warranted:** For a bug, reproduce the reported behavior on the target branch. For another request, establish the unmet user need from the request and current product. Search current code, history, issues, and open or recently merged pull requests for work that already resolves or supersedes it. Treat title similarity only as a candidate, not proof of duplication. FAIL confirmed duplicate or obsolete work.
19529
+ - **Desirable:** Check repository-owned goals, invariants, architecture, and established product behavior. Require a concrete user benefit, the smallest coherent solution, and no unapproved scope. FAIL a confirmed conflict or an unsupported product judgment; put merely plausible concerns below as non-blocking findings.
19530
+
19479
19531
  - Quote the corner objective verbatim.
19480
19532
  - Derive its end-user story in one sentence: \`a user who does X sees Y\`.
19481
19533
  - Make Y happen against the built PR head: run the app or affected service and perform X.
@@ -19519,6 +19571,8 @@ Follow these steps in order. Do not skip or reorder them.
19519
19571
 
19520
19572
  \`objective quoted:\`
19521
19573
  \`user story:\`
19574
+ \`work warranted evidence:\`
19575
+ \`desirability evidence:\`
19522
19576
  \`how Y was demonstrated (or FAIL):\`
19523
19577
  \`commands run + results:\`
19524
19578
  \`critical findings (block):\`
@@ -19533,6 +19587,49 @@ Then take exactly one action:
19533
19587
  - Approving is your last step as reviewer. The author merges it; you never do, and nothing merges it automatically.
19534
19588
  `;
19535
19589
  }
19590
+ function beelineTriageSkillMarkdown(releaseId) {
19591
+ return `---
19592
+ name: beeline-triage
19593
+ description: Clarify and assess a user request before proposing or opening a Beeline corner. Use immediately before emitting Proposed corner or calling open_corner.
19594
+ ---
19595
+
19596
+ <!-- beeline-release: ${releaseId} -->
19597
+
19598
+ # Beeline request triage
19599
+
19600
+ Run these checks before proposing or opening a corner.
19601
+
19602
+ ## 1. Is it clear?
19603
+
19604
+ - Rewrite the request as a concrete outcome and acceptance criteria.
19605
+ - State material exclusions needed to prevent unrequested work.
19606
+ - Keep the corner objective complete and within 24 words.
19607
+ - If an ambiguity could materially change the outcome, ask one focused question instead of proposing or opening the corner.
19608
+
19609
+ ## 2. Is work warranted?
19610
+
19611
+ - For a bug, try to reproduce the exact user-visible behavior and record the evidence.
19612
+ - For another request, identify the unmet user need and the current behavior that does not meet it.
19613
+ - Search current code, history, issues, and open or recently merged pull requests for released or unreleased work that resolves or supersedes the request.
19614
+ - Treat similar titles as candidates. Confirm behavior and scope before calling work duplicate.
19615
+ - If the need cannot be established, reproduction fails, or other work may obviate it, warn; do not block.
19616
+
19617
+ ## 3. Is it desirable?
19618
+
19619
+ - Compare the request with repository-owned goals, invariants, architecture, and established product behavior.
19620
+ - Look for concrete user benefit, the smallest coherent solution, and ongoing maintenance cost.
19621
+ - Do not invent product strategy. If the evidence is absent or conflicting, warn; do not block.
19622
+
19623
+ ## Output
19624
+
19625
+ When proposing work, emit the ordinary \`Proposed corner: <name> \u2014 <objective>\` line. Add only applicable warnings on following lines:
19626
+
19627
+ \`Triage warning \u2014 warranted: <evidence-backed reason>\`
19628
+ \`Triage warning \u2014 desirable: <evidence-backed reason>\`
19629
+
19630
+ Do not emit a warning merely because evidence is incomplete when the repository offers no practical way to obtain it. Never describe a warning as approval or rejection. Warnings inform the user and implementer; they do not block work.
19631
+ `;
19632
+ }
19536
19633
 
19537
19634
  // apps/body/dist/external-mcp-capabilities.js
19538
19635
  var SQUIRE_MCP_VERSION = "latest";
@@ -20127,11 +20224,15 @@ var SHARED_CREDENTIALS = [
20127
20224
  // Grok relocates ~/.grok via GROK_HOME; auth.json holds its login (same
20128
20225
  // shape as codex).
20129
20226
  { dir: "grok", source: ".grok/auth.json", target: "auth.json" },
20130
- { dir: "pi", source: ".pi/agent/auth.json", target: "auth.json" }
20227
+ { dir: "pi", source: ".pi/agent/auth.json", target: "auth.json" },
20228
+ // Cursor CLI stores auth state under ~/.cursor/; cursor-agent-acp
20229
+ // reads CURSOR_HOME to relocate the data directory.
20230
+ { dir: "cursor", source: ".cursor/agent-cli-state.json", target: "agent-cli-state.json" }
20131
20231
  ];
20132
20232
  var GOOSE_SHARED_CONFIG_FILES = ["config.yaml", "secrets.yaml"];
20133
20233
  var BEELINE_DEFAULT_SKILL_NAMES = [
20134
20234
  BEELINE_REVIEW_SKILL_NAME,
20235
+ BEELINE_TRIAGE_SKILL_NAME,
20135
20236
  USING_BEELINE_SKILL_NAME
20136
20237
  ];
20137
20238
  var OPERATOR_SKILL_SOURCE_DIRS = [
@@ -20140,7 +20241,7 @@ var OPERATOR_SKILL_SOURCE_DIRS = [
20140
20241
  ".codex/skills",
20141
20242
  ".pi/agent/skills"
20142
20243
  ];
20143
- var AGENT_SKILL_DIRS = ["claude", "codex", "grok", "pi"];
20244
+ var AGENT_SKILL_DIRS = ["claude", "codex", "grok", "pi", "cursor"];
20144
20245
  function agentSkillDir(kind) {
20145
20246
  return AGENT_SKILL_DIRS.includes(kind ?? "") ? kind : "codex";
20146
20247
  }
@@ -20168,6 +20269,7 @@ var HOME_SUBDIRS = [
20168
20269
  "goose",
20169
20270
  "grok",
20170
20271
  "pi",
20272
+ "cursor",
20171
20273
  "state",
20172
20274
  "cache",
20173
20275
  "tmp"
@@ -20213,6 +20315,7 @@ async function prepareRoomAgentHome(input) {
20213
20315
  async function provisionAgentSkillsAndMcp(root, operatorHome, skillReleaseId, failClosed, sharedSkills, skillDir, openRouterRouting, isReviewer) {
20214
20316
  const managedSkills = [
20215
20317
  { name: USING_BEELINE_SKILL_NAME, content: usingBeelineSkillMarkdown(skillReleaseId) },
20318
+ { name: BEELINE_TRIAGE_SKILL_NAME, content: beelineTriageSkillMarkdown(skillReleaseId) },
20216
20319
  ...isReviewer ? [{ name: BEELINE_REVIEW_SKILL_NAME, content: beelineReviewSkillMarkdown(skillReleaseId) }] : []
20217
20320
  ];
20218
20321
  const shared = await resolveSharedSkillSources(operatorHome, sharedSkills);
@@ -20622,6 +20725,7 @@ function roomAgentHomeEnv(root) {
20622
20725
  CODEX_HOME: resolve13(resolved, "codex"),
20623
20726
  GOOSE_PATH_ROOT: resolve13(resolved, "goose"),
20624
20727
  GROK_HOME: resolve13(resolved, "grok"),
20728
+ CURSOR_HOME: resolve13(resolved, "cursor"),
20625
20729
  PI_CODING_AGENT_DIR: resolve13(resolved, "pi"),
20626
20730
  XDG_STATE_HOME: resolve13(resolved, "state"),
20627
20731
  XDG_CACHE_HOME: resolve13(resolved, "cache"),
@@ -20633,6 +20737,7 @@ var HARNESS_STATE_ENV_VARS = [
20633
20737
  "CODEX_HOME",
20634
20738
  "GOOSE_PATH_ROOT",
20635
20739
  "GROK_HOME",
20740
+ "CURSOR_HOME",
20636
20741
  "PI_CODING_AGENT_DIR",
20637
20742
  "XDG_STATE_HOME",
20638
20743
  "XDG_CACHE_HOME",
@@ -24422,6 +24527,23 @@ var SessionScheduler = class {
24422
24527
  this.live.delete(key);
24423
24528
  await this.retire(session);
24424
24529
  }
24530
+ /**
24531
+ * Retire every RETAINED session — the warm processes holding no turn — in
24532
+ * one pass. This is the config-change hot restart: a phone-side model/effort
24533
+ * selection change must not wait for each Room's next hand-back currency
24534
+ * check to notice. A busy, draining, queued, or still-spawning session is
24535
+ * deliberately left alone: interrupting a live turn buys nothing, and its
24536
+ * next hand-back runs the ordinary `isCurrent` check, which cold-activates
24537
+ * against the new selection. Suspending a key that is no longer live is a
24538
+ * no-op, so overlapping wakes coalesce safely.
24539
+ */
24540
+ async suspendIdle() {
24541
+ for (const [key, session] of [...this.live.entries()]) {
24542
+ if (session.pending || this.busy.has(key) || this.drainingKeys.has(key) || (this.queues.get(key)?.length ?? 0) > 0)
24543
+ continue;
24544
+ await this.suspend(key);
24545
+ }
24546
+ }
24425
24547
  /**
24426
24548
  * Tear down a poisoned session even when its task is still marked busy.
24427
24549
  * The watchdog uses this only after a Room has stopped making progress; the
@@ -24632,6 +24754,25 @@ var DEFAULT_ROOM_WATCHDOG_STALE_MS = 9e4;
24632
24754
  var DEFAULT_RECONCILE_HEARTBEAT_MS = 6e4;
24633
24755
  var DEFAULT_DRAIN_DEADLINE_MS = 30 * 6e4;
24634
24756
  var CORNER_BRANCH_DELETE_ATTEMPTS = 3;
24757
+ var DiscoveryWakes = class {
24758
+ arrived = 0;
24759
+ served = 0;
24760
+ /** Called for every agent-directed `rooms-changed` wake. */
24761
+ wake() {
24762
+ this.arrived += 1;
24763
+ }
24764
+ needsFastReconcile() {
24765
+ return this.arrived !== this.served;
24766
+ }
24767
+ /** Snapshot at reconcile entry: the wake count its reads will cover. */
24768
+ beginReconcile() {
24769
+ return this.arrived;
24770
+ }
24771
+ /** Clear only wakes the completed start pass actually served. */
24772
+ completeReconcile(covered) {
24773
+ this.served = Math.max(this.served, covered);
24774
+ }
24775
+ };
24635
24776
  function shouldPostInitialCornerWorkingState(restore, isOpener = true) {
24636
24777
  return isOpener && !restore.featureBranch && !restore.lifecycle?.branch && !restore.lifecycle?.pr;
24637
24778
  }
@@ -24808,6 +24949,11 @@ var RoomRuntimeCoordinator = class {
24808
24949
  workspaceRemovalConfirmations = 0;
24809
24950
  roomRemovalConfirmations = /* @__PURE__ */ new Map();
24810
24951
  confirmationPending = false;
24952
+ /** Agent-directed discovery wakes (#1369), counted instead of flagged: a
24953
+ * wake that arrives while a reconcile is already running must survive its
24954
+ * start-pass clearing, or a corner opened mid-reconcile waits a heartbeat
24955
+ * for a wake the daemon already received. */
24956
+ discoveryWakes = new DiscoveryWakes();
24811
24957
  /** One command-grant runner per daemon; Rooms and corners register their checkouts on it. */
24812
24958
  grantRunner;
24813
24959
  grantRunnerServer;
@@ -24828,6 +24974,12 @@ var RoomRuntimeCoordinator = class {
24828
24974
  });
24829
24975
  this.grantRunnerServer = new GrantRunnerServer(this.grantRunner);
24830
24976
  this.connectorUsage = new ConnectorUsageRecorder();
24977
+ this.options.daemonApi.setRoomsChangedListener?.(() => {
24978
+ this.discoveryWakes.wake();
24979
+ });
24980
+ this.options.daemonApi.setConfigChangedListener?.(() => {
24981
+ void this.scheduler.suspendIdle().catch((error) => console.error("[body] config-change session restart failed", error));
24982
+ });
24831
24983
  this.watchdogStaleMs = options.watchdogStaleMs ?? DEFAULT_ROOM_WATCHDOG_STALE_MS;
24832
24984
  this.reconcileHeartbeatMs = options.reconcileHeartbeatMs ?? DEFAULT_RECONCILE_HEARTBEAT_MS;
24833
24985
  this.drainDeadlineMs = options.drainDeadlineMs ?? DEFAULT_DRAIN_DEADLINE_MS;
@@ -24858,7 +25010,7 @@ var RoomRuntimeCoordinator = class {
24858
25010
  return this.scheduler.snapshot();
24859
25011
  }
24860
25012
  needsFastReconcile() {
24861
- return this.confirmationPending;
25013
+ return this.confirmationPending || this.discoveryWakes.needsFastReconcile();
24862
25014
  }
24863
25015
  reconcileHeartbeatIntervalMs() {
24864
25016
  return this.reconcileHeartbeatMs;
@@ -24894,6 +25046,7 @@ var RoomRuntimeCoordinator = class {
24894
25046
  }
24895
25047
  async reconcile() {
24896
25048
  this.confirmationPending = false;
25049
+ const coveredWakes = this.discoveryWakes.beginReconcile();
24897
25050
  const bootstrap = await this.options.daemonApi.execute("getDaemonBootstrap", {
24898
25051
  agentId: this.agent.publicKey
24899
25052
  });
@@ -24961,8 +25114,13 @@ var RoomRuntimeCoordinator = class {
24961
25114
  }
24962
25115
  }
24963
25116
  await mapWithConcurrency(desiredTopRooms, ROOM_JOIN_CONCURRENCY, async (roomId) => {
24964
- if (!this.running.has(roomId))
25117
+ if (this.running.has(roomId))
25118
+ return;
25119
+ try {
24965
25120
  await this.startRoom(roomId);
25121
+ } catch (error) {
25122
+ console.error(`[thin-core] failed to start Room ${roomId}:`, error);
25123
+ }
24966
25124
  });
24967
25125
  await mapWithConcurrency([...desiredCorners.values()], ROOM_JOIN_CONCURRENCY, async (corner) => {
24968
25126
  if (!this.running.has(corner.cornerId))
@@ -24970,6 +25128,7 @@ var RoomRuntimeCoordinator = class {
24970
25128
  });
24971
25129
  for (const running of this.running.values())
24972
25130
  running.body.requestReconciliation();
25131
+ this.discoveryWakes.completeReconcile(coveredWakes);
24973
25132
  return "member";
24974
25133
  }
24975
25134
  roomRecord(roomId) {
@@ -25854,6 +26013,7 @@ Install one of these supported agents:
25854
26013
  goose https://block.github.io/goose/docs/getting-started/installation/
25855
26014
  pi npm install -g @mariozechner/pi-coding-agent pi-acp
25856
26015
  grok curl -fsSL https://x.ai/cli/install.sh | bash
26016
+ cursor npm install -g cursor-agent-acp
25857
26017
  Then retry, or explicitly use \`--agent reference\` with an LLM key.
25858
26018
  For another ACP server, use \`--agent custom --agent-command "<cmd> [args...]"\`.`;
25859
26019
  async function clackConfirmInstall(message) {
@@ -26092,7 +26252,8 @@ var DEFAULT_MODELS = {
26092
26252
  xai: "grok-4",
26093
26253
  codex: "gpt-5.4",
26094
26254
  claude: "claude-opus-4-1",
26095
- grok: "grok-4"
26255
+ grok: "grok-4",
26256
+ cursor: "claude-sonnet-4"
26096
26257
  };
26097
26258
  function connectHarnessNeedsProvider(harness) {
26098
26259
  return CONNECT_PROVIDER_HARNESSES.has(harness);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usebeeline",
3
- "version": "0.0.109",
3
+ "version": "0.0.111",
4
4
  "description": "Connect an AI coding agent to Beeline with one command.",
5
5
  "homepage": "https://usebeeline.app",
6
6
  "bugs": {