usebeeline 0.0.58 → 0.0.61

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 +206 -155
  2. package/package.json +1 -1
@@ -16944,8 +16944,6 @@ var DaemonApiClient = class {
16944
16944
  liveReconnect;
16945
16945
  liveReconnectDelayMs = 1e3;
16946
16946
  liveRooms = /* @__PURE__ */ new Map();
16947
- parityPending = /* @__PURE__ */ new Map();
16948
- parityMisses = /* @__PURE__ */ new Map();
16949
16947
  constructor(baseUrl, daemonToken, agentId, fetchImpl = fetch, webSocketFactory = (url, protocols) => new wrapper_default(url, protocols)) {
16950
16948
  this.baseUrl = baseUrl;
16951
16949
  this.daemonToken = daemonToken;
@@ -16957,18 +16955,21 @@ var DaemonApiClient = class {
16957
16955
  connection() {
16958
16956
  return { baseUrl: this.baseUrl, daemonToken: this.daemonToken, agentId: this.agentId };
16959
16957
  }
16960
- /** Add one Room to this agent's shared live socket. Polling remains active in Phase 2. */
16961
- liveSubscribe(roomId, cursor3, onItems) {
16958
+ /** Add one Room to this agent's shared live socket. */
16959
+ liveSubscribe(roomId, cursor3, onItems, onState, presence) {
16962
16960
  const existing = this.liveRooms.get(roomId);
16963
16961
  if (existing) {
16964
16962
  existing.cursor = cursor3 ?? existing.cursor;
16965
16963
  existing.onItems = onItems ?? existing.onItems;
16964
+ existing.onState = onState ?? existing.onState;
16965
+ existing.presence = presence ?? existing.presence;
16966
16966
  } else {
16967
16967
  this.liveRooms.set(roomId, {
16968
16968
  ...cursor3 ? { cursor: cursor3 } : {},
16969
16969
  pushedIds: /* @__PURE__ */ new Set(),
16970
- parityMissIds: /* @__PURE__ */ new Set(),
16971
- ...onItems ? { onItems } : {}
16970
+ ...onItems ? { onItems } : {},
16971
+ ...onState ? { onState } : {},
16972
+ ...presence ? { presence } : {}
16972
16973
  });
16973
16974
  }
16974
16975
  this.ensureLiveSocket();
@@ -16976,11 +16977,6 @@ var DaemonApiClient = class {
16976
16977
  this.sendLiveSubscription(roomId);
16977
16978
  return () => {
16978
16979
  this.liveRooms.delete(roomId);
16979
- const pending = this.parityPending.get(roomId);
16980
- if (pending)
16981
- for (const timer of pending.values())
16982
- clearTimeout(timer);
16983
- this.parityPending.delete(roomId);
16984
16980
  if (this.liveSocket?.readyState === wrapper_default.OPEN) {
16985
16981
  this.liveSocket.send(JSON.stringify({ type: "unsubscribe", roomId }));
16986
16982
  }
@@ -16996,42 +16992,6 @@ var DaemonApiClient = class {
16996
16992
  if (room && cursor3)
16997
16993
  room.cursor = cursor3;
16998
16994
  }
16999
- /** Count a poll delivery only if push still has not produced its id after a grace window. */
17000
- notePolled(roomId, items) {
17001
- const room = this.liveRooms.get(roomId);
17002
- if (!room)
17003
- return;
17004
- const now2 = Date.now();
17005
- if (room.parityReportedAt === void 0 || now2 - room.parityReportedAt >= 6e4) {
17006
- room.parityReportedAt = now2;
17007
- console.info(`[thin-core] push parity room=${roomId} poll_only=${room.parityMissIds.size}`);
17008
- }
17009
- let pending = this.parityPending.get(roomId);
17010
- if (!pending) {
17011
- pending = /* @__PURE__ */ new Map();
17012
- this.parityPending.set(roomId, pending);
17013
- }
17014
- for (const item of items) {
17015
- if (room.pushedIds.has(item.id) || pending.has(item.id))
17016
- continue;
17017
- const timer = setTimeout(() => {
17018
- pending.delete(item.id);
17019
- if (room.pushedIds.has(item.id) || room.parityMissIds.has(item.id))
17020
- return;
17021
- room.parityMissIds.add(item.id);
17022
- while (room.parityMissIds.size > 1e4)
17023
- room.parityMissIds.delete(room.parityMissIds.values().next().value);
17024
- const count = room.parityMissIds.size;
17025
- this.parityMisses.set(roomId, count);
17026
- console.warn(`[thin-core] push parity miss room=${roomId} count=${count}`);
17027
- }, 5e3);
17028
- timer.unref?.();
17029
- pending.set(item.id, timer);
17030
- }
17031
- }
17032
- pushParityMissCount(roomId) {
17033
- return this.parityMisses.get(roomId) ?? 0;
17034
- }
17035
16995
  async execute(name, input) {
17036
16996
  const candidate = input;
17037
16997
  if (typeof candidate.agentId === "string" && candidate.agentId !== this.agentId) {
@@ -17069,6 +17029,14 @@ var DaemonApiClient = class {
17069
17029
  if (!value || typeof value !== "object")
17070
17030
  return;
17071
17031
  const event = value;
17032
+ if (event.type === "subscribed" && typeof event.roomId === "string") {
17033
+ const capabilities = event.capabilities;
17034
+ this.liveRooms.get(event.roomId)?.onState?.(true, {
17035
+ pushIntake: capabilities?.pushIntake === true,
17036
+ connectionPresence: capabilities?.connectionPresence === true
17037
+ });
17038
+ return;
17039
+ }
17072
17040
  if (event.type !== "inbox" || typeof event.roomId !== "string" || !Array.isArray(event.items))
17073
17041
  return;
17074
17042
  const room = this.liveRooms.get(event.roomId);
@@ -17083,14 +17051,6 @@ var DaemonApiClient = class {
17083
17051
  if (!room.pushedIds.has(id))
17084
17052
  items.push(candidate);
17085
17053
  room.pushedIds.add(id);
17086
- if (room.parityMissIds.delete(id)) {
17087
- this.parityMisses.set(event.roomId, room.parityMissIds.size);
17088
- console.info(`[thin-core] push parity room=${event.roomId} poll_only=${room.parityMissIds.size}`);
17089
- }
17090
- const pending = this.parityPending.get(event.roomId)?.get(id);
17091
- if (pending)
17092
- clearTimeout(pending);
17093
- this.parityPending.get(event.roomId)?.delete(id);
17094
17054
  }
17095
17055
  }
17096
17056
  while (room.pushedIds.size > 1e4)
@@ -17102,6 +17062,8 @@ var DaemonApiClient = class {
17102
17062
  if (this.liveSocket !== socket)
17103
17063
  return;
17104
17064
  this.liveSocket = void 0;
17065
+ for (const room of this.liveRooms.values())
17066
+ room.onState?.(false);
17105
17067
  if (!this.liveRooms.size)
17106
17068
  return;
17107
17069
  const delay = this.liveReconnectDelayMs;
@@ -17119,7 +17081,8 @@ var DaemonApiClient = class {
17119
17081
  this.liveSocket.send(JSON.stringify({
17120
17082
  type: "subscribe",
17121
17083
  roomId,
17122
- ...room.cursor ? { cursor: room.cursor } : {}
17084
+ ...room.cursor ? { cursor: room.cursor } : {},
17085
+ ...room.presence
17123
17086
  }));
17124
17087
  }
17125
17088
  };
@@ -20807,6 +20770,14 @@ function cornerClosePollMs(random = Math.random) {
20807
20770
  var MonolithCornerTurnLoop = class {
20808
20771
  options;
20809
20772
  agent;
20773
+ reconciliationRequested = true;
20774
+ wakeIntake;
20775
+ /** Called by the daemon's one slow workspace reconciliation sweep. */
20776
+ requestReconciliation() {
20777
+ this.reconciliationRequested = true;
20778
+ this.wakeIntake?.();
20779
+ this.wakeIntake = void 0;
20780
+ }
20810
20781
  client;
20811
20782
  sessionId;
20812
20783
  /** The configuration the live session baked in; a change invalidates it. */
@@ -20951,11 +20922,9 @@ var MonolithCornerTurnLoop = class {
20951
20922
  })
20952
20923
  }) : {};
20953
20924
  const command = this.options.config.agentCommand ?? this.options.config.agentBinary;
20954
- let githubEnv = {
20955
- GH_TOKEN: this.options.githubToken,
20956
- GITHUB_TOKEN: this.options.githubToken
20957
- };
20958
- if (this.options.config.runtimeConfigPath && this.options.config.agentHomeRoot) {
20925
+ const repository = this.options.repository;
20926
+ let githubEnv = repository ? { GH_TOKEN: repository.githubToken, GITHUB_TOKEN: repository.githubToken } : {};
20927
+ if (repository && this.options.config.runtimeConfigPath && this.options.config.agentHomeRoot) {
20959
20928
  const gitBinary = (await execFileAsync3("which", ["git"])).stdout.trim();
20960
20929
  const ghBinary = await execFileAsync3("which", ["gh"]).then((result) => result.stdout.trim()).catch(() => void 0);
20961
20930
  githubEnv = await installCornerGitHubWrappers({
@@ -20994,7 +20963,7 @@ var MonolithCornerTurnLoop = class {
20994
20963
  mode: "edit",
20995
20964
  cwd: this.options.worktreePath,
20996
20965
  worktreePath: this.options.worktreePath,
20997
- gitCommonDir: this.options.gitCommonDir,
20966
+ ...repository ? { gitCommonDir: repository.gitCommonDir } : {},
20998
20967
  protectedPaths: [this.options.runtime.supervisorRoot],
20999
20968
  harnessStateDirs: stateDirs,
21000
20969
  harnessHomeStateDirs: homeStateDirs,
@@ -21017,19 +20986,21 @@ var MonolithCornerTurnLoop = class {
21017
20986
  this.client = (this.options.createAcpClient ?? ((value) => new AcpClient(value)))(clientOptions);
21018
20987
  await this.client.start();
21019
20988
  const servers = [
21020
- {
21021
- name: "buzz-dev-mcp",
21022
- command: this.options.config.mcpBinary,
21023
- args: [],
21024
- // ACP hosts launch stdio MCP servers with an explicit, sanitized env.
21025
- // This token is minted for this exact corner and is also the credential
21026
- // helper's password source, so its shell commands need the same scope as
21027
- // the corner harness without inheriting any host credentials.
21028
- env: [
21029
- { name: "GH_TOKEN", value: this.options.githubToken },
21030
- { name: "GITHUB_TOKEN", value: this.options.githubToken }
21031
- ]
21032
- },
20989
+ ...repository ? [
20990
+ {
20991
+ name: "buzz-dev-mcp",
20992
+ command: this.options.config.mcpBinary,
20993
+ args: [],
20994
+ // ACP hosts launch stdio MCP servers with an explicit, sanitized env.
20995
+ // This token is minted for this exact corner and is also the credential
20996
+ // helper's password source, so its shell commands need the same scope as
20997
+ // the corner harness without inheriting any host credentials.
20998
+ env: [
20999
+ { name: "GH_TOKEN", value: repository.githubToken },
21000
+ { name: "GITHUB_TOKEN", value: repository.githubToken }
21001
+ ]
21002
+ }
21003
+ ] : [],
21033
21004
  beelineAgentMcpServer(this.options.config, this.options.api, {
21034
21005
  roomId: this.options.parentRoomId,
21035
21006
  workspaceId: this.options.workspaceId,
@@ -21060,17 +21031,23 @@ var MonolithCornerTurnLoop = class {
21060
21031
  systemPrompt: [
21061
21032
  identityInstructions,
21062
21033
  personaInstructions,
21063
- `You are in an isolated git worktree on ${this.options.featureBranch}, targeting ${this.options.targetBranch}.`,
21064
- "Work normally with the full coding tools. Commit and push only this feature branch. Use gh to open its pull request.",
21065
- `This corner is shared: any of its member agents may be addressed in it and work on ${this.options.featureBranch}. Run git pull --rebase origin ${this.options.featureBranch} before you push, and never force-push it.`,
21066
- "PR-opening turn rule: as soon as a pull request exists, print its full GitHub URL as your final response and end the turn immediately. Do not call pr_checks_status in that same turn and do not wait for checks inside it. Then stay idle until a later corner fact or human message starts another turn.",
21067
- 'Never merge because local tests pass or because gh reports passing checks. On a later turn triggered by a server-posted checks-passed note, call beeline-agent pr_checks_status. Merge only when it returns checks="passed", held=false, and approvalPending=false.',
21068
- "Merge the PR yourself only after the checks-passed event shows every check green; if any check failed or is still running, say exactly which and stop - never merge red.",
21069
- "If any human in this corner says hold or do not merge, do not merge until a later human explicitly resumes it.",
21070
- "Do not tag the user when a corner turn finishes: the server posts the merge summary card and its push already cover completion. Tag a human only mid-turn, and only when you need a decision or input.",
21071
- 'GitHub check and merge notes are server lines already in the corner: never restate them (no "checks passed", "CI is green", "PR ready for review"). On a checks turn, say nothing unless you act - a merge or a pushed fix - and then one short line about that.',
21072
- "A human approval in the app asks the server to merge. When approval is pending, wait for the server close request instead of racing it with gh. If checks passed, no hold exists, and no approval is pending, merge the pull request yourself with gh.",
21073
- "Never push directly to the target branch. Never merge a different pull request."
21034
+ ...repository ? [
21035
+ `You are in an isolated git worktree on ${repository.featureBranch}, targeting ${repository.targetBranch}.`,
21036
+ "Work normally with the full coding tools. Commit and push only this feature branch. Use gh to open its pull request.",
21037
+ `This corner is shared: any of its member agents may be addressed in it and work on ${repository.featureBranch}. Run git pull --rebase origin ${repository.featureBranch} before you push, and never force-push it.`,
21038
+ "PR-opening turn rule: as soon as a pull request exists, print its full GitHub URL as your final response and end the turn immediately. Do not call pr_checks_status in that same turn and do not wait for checks inside it. Then stay idle until a later corner fact or human message starts another turn.",
21039
+ 'Never merge because local tests pass or because gh reports passing checks. On a later turn triggered by a server-posted checks-passed note, call beeline-agent pr_checks_status. Merge only when it returns checks="passed", held=false, and approvalPending=false.',
21040
+ "Merge the PR yourself only after the checks-passed event shows every check green; if any check failed or is still running, say exactly which and stop - never merge red.",
21041
+ "If any human in this corner says hold or do not merge, do not merge until a later human explicitly resumes it.",
21042
+ "Do not tag the user when a corner turn finishes: the server posts the merge summary card and its push already cover completion. Tag a human only mid-turn, and only when you need a decision or input.",
21043
+ 'GitHub check and merge notes are server lines already in the corner: never restate them (no "checks passed", "CI is green", "PR ready for review"). On a checks turn, say nothing unless you act - a merge or a pushed fix - and then one short line about that.',
21044
+ "A human approval in the app asks the server to merge. When approval is pending, wait for the server close request instead of racing it with gh. If checks passed, no hold exists, and no approval is pending, merge the pull request yourself with gh.",
21045
+ "Never push directly to the target branch. Never merge a different pull request."
21046
+ ] : [
21047
+ "This is a chat-only corner with no repository or GitHub workflow.",
21048
+ "Work in this corner's writable workspace. Use write_scratch_file or ordinary tools to create files, then attach_file to send them back to the corner.",
21049
+ "Do not initialize a repository, create a branch, push, open a pull request, or wait for GitHub checks."
21050
+ ]
21074
21051
  ].filter(Boolean).join("\n\n")
21075
21052
  });
21076
21053
  this.sessionId = opened.sessionId;
@@ -21203,7 +21180,7 @@ ${this.options.objective}`,
21203
21180
  ${trigger}`,
21204
21181
  ...attachmentPromptLines(attachments, delivered, this.acceptsImages())
21205
21182
  ].join("\n"),
21206
- "Continue the objective. Obey the PR checks and human hold rules in your session instructions.",
21183
+ this.options.repository ? "Continue the objective. Obey the PR checks and human hold rules in your session instructions." : "Continue the objective. Attach completed files before calling close_corner.",
21207
21184
  MAINTAIN_ASSIGNED_IDENTITY_DIRECTIVE
21208
21185
  ].filter(Boolean).join("\n\n");
21209
21186
  const stream = new AgentTurnStream({
@@ -21353,10 +21330,13 @@ ${trigger}`,
21353
21330
  * fails with that sentence and the server inscribes it in the corner.
21354
21331
  */
21355
21332
  async syncBranch() {
21356
- const token = await this.options.api.execute("getRoomGitHubToken", { roomId: this.options.parentRoomId }).then((granted) => granted.token).catch(() => this.options.githubToken);
21333
+ const repository = this.options.repository;
21334
+ if (!repository)
21335
+ return;
21336
+ const token = await this.options.api.execute("getRoomGitHubToken", { roomId: this.options.parentRoomId }).then((granted) => granted.token).catch(() => repository.githubToken);
21357
21337
  await syncCornerBranch({
21358
21338
  worktreePath: this.options.worktreePath,
21359
- featureBranch: this.options.featureBranch,
21339
+ featureBranch: repository.featureBranch,
21360
21340
  env: { ...process.env, GH_TOKEN: token, GITHUB_TOKEN: token, GIT_TERMINAL_PROMPT: "0" }
21361
21341
  });
21362
21342
  }
@@ -21384,15 +21364,23 @@ ${trigger}`,
21384
21364
  const processedInboxIds = /* @__PURE__ */ new Set();
21385
21365
  const pushedInbox = [];
21386
21366
  let pendingPushedCursor;
21387
- let wakeForPush;
21367
+ let liveConnected = false;
21388
21368
  const rewindSupported = Array.isArray(activation.rewindIds);
21389
21369
  for (const id of activation.rewindIds ?? [])
21390
21370
  processedInboxIds.add(id);
21391
21371
  const stopLive = api.liveSubscribe?.(cornerId, cursor3, (items, pushedCursor) => {
21392
21372
  pushedInbox.push(...items);
21393
21373
  pendingPushedCursor = laterInboxCursor(pendingPushedCursor, pushedCursor);
21394
- wakeForPush?.();
21395
- wakeForPush = void 0;
21374
+ this.wakeIntake?.();
21375
+ this.wakeIntake = void 0;
21376
+ }, (connected, capabilities) => {
21377
+ liveConnected = connected && capabilities?.pushIntake === true;
21378
+ this.wakeIntake?.();
21379
+ this.wakeIntake = void 0;
21380
+ }, {
21381
+ ...this.options.config.daemonReleaseVersion ? { releaseVersion: this.options.config.daemonReleaseVersion } : {},
21382
+ ...this.options.config.daemonSourceSha ? { sourceSha: this.options.config.daemonSourceSha } : {},
21383
+ available: !this.options.config.modelUnavailable
21396
21384
  });
21397
21385
  const history = await api.execute("getRoomConversation", { roomId: cornerId, limit: 200 });
21398
21386
  await this.roster().catch(() => void 0);
@@ -21407,14 +21395,14 @@ ${trigger}`,
21407
21395
  let pollWithoutWait = false;
21408
21396
  while (!signal?.aborted) {
21409
21397
  try {
21410
- const pollNow = pushedInbox.length === 0;
21398
+ const pollNow = pushedInbox.length > 0 || !liveConnected || this.reconciliationRequested;
21411
21399
  const inbox = !pollNow ? { items: [], cursor: void 0, closeRequested: false } : await api.execute("getCornerCloseRequests", {
21412
21400
  cornerId,
21413
21401
  ...cursor3 ? { after: cursor3 } : {},
21414
21402
  ...rewindSupported ? { rewind: true } : {}
21415
21403
  });
21416
21404
  if (pollNow) {
21417
- api.notePolled?.(cornerId, inbox.items.filter((item) => !processedInboxIds.has(item.id)));
21405
+ this.reconciliationRequested = false;
21418
21406
  }
21419
21407
  if (inbox.closeRequested) {
21420
21408
  await this.options.onCloseRequested();
@@ -21473,10 +21461,9 @@ This answers your grant request; resume the paused work. If approved and it is a
21473
21461
  if (pollNow)
21474
21462
  this.options.onPoll();
21475
21463
  await Promise.race([
21476
- wait(pollWithoutWait ? 0 : this.options.pollMs ?? cornerClosePollMs(), signal),
21477
- waitForWake(api, cornerId, signal),
21464
+ wait(pollWithoutWait ? 0 : liveConnected ? 2147483647 : this.options.pollMs ?? cornerClosePollMs(), signal),
21478
21465
  pushedInbox.length ? Promise.resolve() : new Promise((resolve30) => {
21479
- wakeForPush = resolve30;
21466
+ this.wakeIntake = resolve30;
21480
21467
  })
21481
21468
  ]);
21482
21469
  pollWithoutWait = false;
@@ -21490,27 +21477,12 @@ This answers your grant request; resume the paused work. If approved and it is a
21490
21477
  }
21491
21478
  } finally {
21492
21479
  stopLive?.();
21480
+ this.wakeIntake = void 0;
21493
21481
  this.options.grantRunner?.unregister(cornerId);
21494
21482
  await this.options.scheduler.suspend(cornerId);
21495
21483
  }
21496
21484
  }
21497
21485
  };
21498
- async function waitForWake(api, cornerId, signal) {
21499
- if (signal?.aborted)
21500
- return;
21501
- try {
21502
- await api.execute("waitForCornerWake", { cornerId });
21503
- } catch {
21504
- await never(signal);
21505
- }
21506
- }
21507
- async function never(signal) {
21508
- if (signal?.aborted)
21509
- return;
21510
- await new Promise((resolveNever) => {
21511
- signal?.addEventListener("abort", () => resolveNever(), { once: true });
21512
- });
21513
- }
21514
21486
  async function wait(ms, signal) {
21515
21487
  if (signal?.aborted)
21516
21488
  return;
@@ -21658,6 +21630,14 @@ function agentReplyMentionIds(text2, roster, authorId) {
21658
21630
  var MonolithRoomTurnLoop = class {
21659
21631
  options;
21660
21632
  agent;
21633
+ reconciliationRequested = true;
21634
+ wakeIntake;
21635
+ /** Called by the daemon's one slow workspace reconciliation sweep. */
21636
+ requestReconciliation() {
21637
+ this.reconciliationRequested = true;
21638
+ this.wakeIntake?.();
21639
+ this.wakeIntake = void 0;
21640
+ }
21661
21641
  client;
21662
21642
  sessionId;
21663
21643
  /** The configuration the live session baked in; a change invalidates it. */
@@ -22252,7 +22232,10 @@ var MonolithRoomTurnLoop = class {
22252
22232
  async run() {
22253
22233
  const { api, roomId, signal } = this.options;
22254
22234
  const status = this.options.config.modelUnavailable ? "offline" : "online";
22255
- const postPresence = async (presence) => {
22235
+ let legacyPresence = false;
22236
+ let legacyPresenceHeartbeat;
22237
+ let legacyPresenceFallback;
22238
+ const postLegacyPresence = async (presence) => {
22256
22239
  await api.execute("postAgentPresence", {
22257
22240
  agentId: this.agent.publicKey,
22258
22241
  roomId,
@@ -22260,17 +22243,30 @@ var MonolithRoomTurnLoop = class {
22260
22243
  ...this.options.config.daemonReleaseVersion ? { releaseVersion: this.options.config.daemonReleaseVersion } : {},
22261
22244
  ...this.options.config.daemonSourceSha ? { sourceSha: this.options.config.daemonSourceSha } : {}
22262
22245
  });
22263
- this.options.health.presence(presence);
22264
22246
  };
22265
- await postPresence(status);
22266
- const heartbeat = setInterval(() => void postPresence(status).catch((error) => console.error(`[thin-core] monolith Room ${roomId} presence heartbeat failed:`, error)), 3e4);
22267
- heartbeat.unref?.();
22247
+ const useLegacyPresence = () => {
22248
+ if (legacyPresence)
22249
+ return;
22250
+ legacyPresence = true;
22251
+ void postLegacyPresence(status).catch((error) => console.error(`[thin-core] monolith Room ${roomId} presence fallback failed:`, error));
22252
+ legacyPresenceHeartbeat = setInterval(() => void postLegacyPresence(status).catch((error) => console.error(`[thin-core] monolith Room ${roomId} presence fallback failed:`, error)), 3e4);
22253
+ legacyPresenceHeartbeat.unref?.();
22254
+ };
22255
+ const stopLegacyPresence = () => {
22256
+ legacyPresence = false;
22257
+ clearTimeout(legacyPresenceFallback);
22258
+ legacyPresenceFallback = void 0;
22259
+ clearInterval(legacyPresenceHeartbeat);
22260
+ legacyPresenceHeartbeat = void 0;
22261
+ };
22268
22262
  let cursor3;
22269
22263
  const processedInboxIds = /* @__PURE__ */ new Set();
22270
22264
  const pushedInbox = [];
22271
22265
  let pendingPushedCursor;
22272
- let wakeForPush;
22266
+ let liveConnected = false;
22273
22267
  let stopLive;
22268
+ legacyPresenceFallback = setTimeout(useLegacyPresence, 1e3);
22269
+ legacyPresenceFallback.unref?.();
22274
22270
  try {
22275
22271
  const activation = await api.execute("getRoomInbox", { roomId, startAtLatest: true });
22276
22272
  cursor3 = activation.cursor;
@@ -22280,15 +22276,28 @@ var MonolithRoomTurnLoop = class {
22280
22276
  stopLive = api.liveSubscribe?.(roomId, cursor3, (items, pushedCursor) => {
22281
22277
  pushedInbox.push(...items);
22282
22278
  pendingPushedCursor = laterInboxCursor(pendingPushedCursor, pushedCursor);
22283
- wakeForPush?.();
22284
- wakeForPush = void 0;
22279
+ this.wakeIntake?.();
22280
+ this.wakeIntake = void 0;
22281
+ }, (connected, capabilities) => {
22282
+ liveConnected = connected && capabilities?.pushIntake === true;
22283
+ if (connected && capabilities?.connectionPresence !== true)
22284
+ useLegacyPresence();
22285
+ else if (connected)
22286
+ stopLegacyPresence();
22287
+ this.options.health.presence(connected && !this.options.config.modelUnavailable ? "online" : "offline");
22288
+ this.wakeIntake?.();
22289
+ this.wakeIntake = void 0;
22290
+ }, {
22291
+ ...this.options.config.daemonReleaseVersion ? { releaseVersion: this.options.config.daemonReleaseVersion } : {},
22292
+ ...this.options.config.daemonSourceSha ? { sourceSha: this.options.config.daemonSourceSha } : {},
22293
+ available: !this.options.config.modelUnavailable
22285
22294
  });
22286
22295
  while (!signal?.aborted) {
22287
22296
  try {
22288
22297
  if (!this.activeTurn && this.queuedTurns.length) {
22289
22298
  this.startPrompt(this.queuedTurns.shift());
22290
22299
  }
22291
- const pollNow = pushedInbox.length === 0;
22300
+ const pollNow = pushedInbox.length === 0 && (!liveConnected || this.reconciliationRequested);
22292
22301
  const inbox = !pollNow ? { items: [], cursor: void 0 } : await api.execute("getRoomInbox", {
22293
22302
  roomId,
22294
22303
  ...cursor3 ? { after: cursor3 } : {},
@@ -22296,7 +22305,7 @@ var MonolithRoomTurnLoop = class {
22296
22305
  limit: 200
22297
22306
  });
22298
22307
  if (pollNow) {
22299
- api.notePolled?.(roomId, inbox.items.filter((item) => !processedInboxIds.has(item.id)));
22308
+ this.reconciliationRequested = false;
22300
22309
  }
22301
22310
  const delivered = [...pushedInbox.splice(0), ...inbox.items];
22302
22311
  for (const item of delivered) {
@@ -22331,9 +22340,9 @@ var MonolithRoomTurnLoop = class {
22331
22340
  this.options.health.poll();
22332
22341
  if (!pushedInbox.length) {
22333
22342
  await Promise.race([
22334
- wait2(this.options.pollMs ?? 1e3, signal),
22343
+ wait2(liveConnected ? 2147483647 : this.options.pollMs ?? 1e3, signal),
22335
22344
  new Promise((resolve30) => {
22336
- wakeForPush = resolve30;
22345
+ this.wakeIntake = resolve30;
22337
22346
  })
22338
22347
  ]);
22339
22348
  }
@@ -22347,13 +22356,17 @@ var MonolithRoomTurnLoop = class {
22347
22356
  }
22348
22357
  } finally {
22349
22358
  stopLive?.();
22350
- clearInterval(heartbeat);
22359
+ this.wakeIntake = void 0;
22351
22360
  this.options.grantRunner?.unregister(roomId);
22352
22361
  if (this.activeTurn?.phase === "prompting" && this.client && this.sessionId) {
22353
22362
  this.client.sessionCancel(this.sessionId);
22354
22363
  }
22355
22364
  await this.activeTurn?.promise;
22356
- await postPresence("offline").catch((error) => console.error(`[thin-core] monolith Room ${roomId} offline presence failed:`, error));
22365
+ clearTimeout(legacyPresenceFallback);
22366
+ clearInterval(legacyPresenceHeartbeat);
22367
+ if (legacyPresence) {
22368
+ await postLegacyPresence("offline").catch((error) => console.error(`[thin-core] monolith Room ${roomId} offline presence failed:`, error));
22369
+ }
22357
22370
  await this.options.scheduler.suspend(roomId);
22358
22371
  }
22359
22372
  }
@@ -22844,7 +22857,14 @@ async function materializeCornerWorktree(input) {
22844
22857
  "credential.https://github.com.helper",
22845
22858
  "!f() { echo username=x-access-token; echo password=$GH_TOKEN; }; f"
22846
22859
  ]);
22847
- await execFileAsync4("git", ["-C", path, "config", "--worktree", "user.name", input.committer.name]);
22860
+ await execFileAsync4("git", [
22861
+ "-C",
22862
+ path,
22863
+ "config",
22864
+ "--worktree",
22865
+ "user.name",
22866
+ input.committer.name
22867
+ ]);
22848
22868
  await execFileAsync4("git", [
22849
22869
  "-C",
22850
22870
  path,
@@ -22873,6 +22893,13 @@ async function mapWithConcurrency(values, limit, visit) {
22873
22893
  }));
22874
22894
  }
22875
22895
  var execFileAsync4 = promisify4(execFile5);
22896
+ async function removeCornerScratchWorkspace(input) {
22897
+ const expected = resolve19(input.roomRoot, "scratch");
22898
+ if (resolve19(input.scratchPath) !== expected) {
22899
+ throw new Error(`refusing to remove scratch outside corner ${input.cornerId}`);
22900
+ }
22901
+ await rm4(expected, { recursive: true, force: true });
22902
+ }
22876
22903
  var RoomRuntimeCoordinator = class {
22877
22904
  configPath;
22878
22905
  baseConfig;
@@ -23037,6 +23064,8 @@ var RoomRuntimeCoordinator = class {
23037
23064
  await running.promise.catch(() => void 0);
23038
23065
  if (running.worktree)
23039
23066
  await this.reapCornerWorktree(running.worktree);
23067
+ else if (running.scratch)
23068
+ await this.reapCornerScratch(running.scratch);
23040
23069
  }
23041
23070
  await mapWithConcurrency(desiredTopRooms, ROOM_JOIN_CONCURRENCY, async (roomId) => {
23042
23071
  if (!this.running.has(roomId))
@@ -23046,6 +23075,8 @@ var RoomRuntimeCoordinator = class {
23046
23075
  if (!this.running.has(corner.cornerId))
23047
23076
  await this.startCorner(corner);
23048
23077
  });
23078
+ for (const running of this.running.values())
23079
+ running.body.requestReconciliation();
23049
23080
  return "member";
23050
23081
  }
23051
23082
  roomRecord(roomId) {
@@ -23069,8 +23100,7 @@ var RoomRuntimeCoordinator = class {
23069
23100
  return void 0;
23070
23101
  }
23071
23102
  }
23072
- roomConfig(roomId) {
23073
- const workspaceRoot = this.roomRoot(roomId);
23103
+ roomConfig(roomId, workspaceRoot = this.roomRoot(roomId)) {
23074
23104
  const agentHomeRoot = this.roomAgentHomeRoot(workspaceRoot, true);
23075
23105
  return {
23076
23106
  ...this.baseConfig,
@@ -23174,7 +23204,7 @@ var RoomRuntimeCoordinator = class {
23174
23204
  return;
23175
23205
  this.startingCorners.add(corner.cornerId);
23176
23206
  try {
23177
- const [restore, repository, conversation, granted] = await Promise.all([
23207
+ const [restore, repository, conversation] = await Promise.all([
23178
23208
  this.options.daemonApi.execute("getCornerRestoreState", { cornerId: corner.cornerId }),
23179
23209
  this.options.daemonApi.execute("getRoomRepositoryState", {
23180
23210
  roomId: corner.parentRoomId
@@ -23186,28 +23216,35 @@ var RoomRuntimeCoordinator = class {
23186
23216
  roomId: corner.cornerId,
23187
23217
  limit: 200,
23188
23218
  window: "earliest"
23189
- }),
23190
- this.options.daemonApi.execute("getRoomGitHubToken", {
23191
- roomId: corner.parentRoomId
23192
23219
  })
23193
23220
  ]);
23194
- if (repository.resolution !== "repository" || !repository.remote || !repository.key) {
23195
- throw new Error("corner parent Room has no verified repository binding");
23221
+ if (repository.resolution === "unverified") {
23222
+ throw new Error("corner parent Room repository state is not verified yet");
23223
+ }
23224
+ if (repository.resolution === "repository" && (!repository.remote || !repository.key)) {
23225
+ throw new Error("corner parent Room has an incomplete repository binding");
23196
23226
  }
23197
23227
  const objective = conversation.items.find((item) => item.type === "message")?.body.trim();
23198
23228
  if (!objective)
23199
23229
  throw new Error("corner has no durable objective post");
23200
- const targetBranch = repository.targetBranch || "main";
23201
- const featureBranch = restore.featureBranch ?? `feature/corner-${corner.cornerId.replaceAll("-", "").slice(0, 12)}`;
23202
- const worktree = await this.materializeCornerWorktree({
23230
+ const repositoryBacked = repository.resolution === "repository";
23231
+ const targetBranch = repositoryBacked ? repository.targetBranch || "main" : void 0;
23232
+ const featureBranch = repositoryBacked ? restore.featureBranch ?? `feature/corner-${corner.cornerId.replaceAll("-", "").slice(0, 12)}` : void 0;
23233
+ const granted = repositoryBacked ? await this.options.daemonApi.execute("getRoomGitHubToken", {
23234
+ roomId: corner.parentRoomId
23235
+ }) : void 0;
23236
+ const worktree = repositoryBacked ? await this.materializeCornerWorktree({
23203
23237
  cornerId: corner.cornerId,
23204
23238
  remote: repository.remote,
23205
23239
  targetBranch,
23206
23240
  featureBranch,
23207
23241
  token: granted.token
23208
- });
23242
+ }) : void 0;
23243
+ const workspacePath = worktree?.path ?? resolve19(this.roomRoot(corner.cornerId), "scratch");
23244
+ if (!worktree)
23245
+ await mkdir11(workspacePath, { recursive: true, mode: 448 });
23209
23246
  const isOpener = !corner.openedBy || corner.openedBy === this.agent.publicKey;
23210
- if (shouldPostInitialCornerWorkingState(restore, isOpener)) {
23247
+ if (worktree && shouldPostInitialCornerWorkingState(restore, isOpener)) {
23211
23248
  await this.options.daemonApi.execute("postCornerRemoteState", {
23212
23249
  cornerId: corner.cornerId,
23213
23250
  branch: featureBranch,
@@ -23226,23 +23263,27 @@ var RoomRuntimeCoordinator = class {
23226
23263
  workspaceId: this.runtime.communityId,
23227
23264
  ...corner.openedBy ? { openedBy: corner.openedBy } : {},
23228
23265
  objective,
23229
- featureBranch,
23230
- targetBranch,
23231
- worktreePath: worktree.path,
23232
- gitCommonDir: worktree.gitCommonDir,
23233
- githubToken: granted.token,
23266
+ worktreePath: workspacePath,
23267
+ ...worktree ? {
23268
+ repository: {
23269
+ featureBranch,
23270
+ targetBranch,
23271
+ gitCommonDir: worktree.gitCommonDir,
23272
+ githubToken: granted.token
23273
+ }
23274
+ } : {},
23234
23275
  runtime: this.runtime,
23235
- config: this.roomConfig(corner.cornerId),
23276
+ config: this.roomConfig(corner.cornerId, worktree ? void 0 : workspacePath),
23236
23277
  api: this.options.daemonApi,
23237
23278
  scheduler: this.scheduler,
23238
23279
  signal: controller.signal,
23239
23280
  onPoll: () => this.notePoll(corner.cornerId),
23240
23281
  onFailure: (retryInMs) => this.noteFailure(corner.cornerId, retryInMs),
23241
- onCloseRequested: () => this.reapCornerWorktree({
23282
+ onCloseRequested: () => worktree ? this.reapCornerWorktree({
23242
23283
  ...worktree,
23243
23284
  cornerId: corner.cornerId,
23244
23285
  branch: featureBranch
23245
- })
23286
+ }) : this.reapCornerScratch({ path: workspacePath, cornerId: corner.cornerId })
23246
23287
  });
23247
23288
  const promise = loop.run().catch((error) => {
23248
23289
  if (!controller.signal.aborted) {
@@ -23260,14 +23301,17 @@ var RoomRuntimeCoordinator = class {
23260
23301
  lastPollAt: startedAt,
23261
23302
  backoffUntil: 0,
23262
23303
  recovering: false,
23263
- worktree: {
23264
- ...worktree,
23265
- cornerId: corner.cornerId,
23266
- branch: featureBranch
23267
- }
23304
+ ...worktree ? {
23305
+ worktree: {
23306
+ ...worktree,
23307
+ cornerId: corner.cornerId,
23308
+ branch: featureBranch
23309
+ }
23310
+ } : {},
23311
+ ...!worktree ? { scratch: { path: workspacePath, cornerId: corner.cornerId } } : {}
23268
23312
  });
23269
23313
  this.reportedCornerStartFailures.delete(corner.cornerId);
23270
- console.log(`[thin-core] serving corner ${corner.cornerId} on ${featureBranch} at ${worktree.path}`);
23314
+ console.log(worktree ? `[thin-core] serving corner ${corner.cornerId} on ${featureBranch} at ${workspacePath}` : `[thin-core] serving chat-only corner ${corner.cornerId} at ${workspacePath}`);
23271
23315
  } catch (error) {
23272
23316
  console.error(`[thin-core] failed to start corner ${corner.cornerId}:`, error);
23273
23317
  await this.reportCornerStartFailure(corner.cornerId, error);
@@ -23334,6 +23378,13 @@ var RoomRuntimeCoordinator = class {
23334
23378
  checks: "unknown"
23335
23379
  });
23336
23380
  }
23381
+ async reapCornerScratch(scratch) {
23382
+ await removeCornerScratchWorkspace({
23383
+ cornerId: scratch.cornerId,
23384
+ roomRoot: this.roomRoot(scratch.cornerId),
23385
+ scratchPath: scratch.path
23386
+ });
23387
+ }
23337
23388
  notePoll(roomId) {
23338
23389
  const room = this.running.get(roomId);
23339
23390
  if (!room)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usebeeline",
3
- "version": "0.0.58",
3
+ "version": "0.0.61",
4
4
  "description": "Connect an AI coding agent to Beeline with one command.",
5
5
  "homepage": "https://usebeeline.app",
6
6
  "bugs": {