usebeeline 0.1.30 → 0.1.33

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 +159 -45
  2. package/package.json +1 -1
@@ -16551,6 +16551,96 @@ function defaultSquireMcpClient(scope) {
16551
16551
  return new StdioSquireMcpClient({ scope });
16552
16552
  }
16553
16553
 
16554
+ // apps/body/dist/squire-share-renewals.js
16555
+ function shareResult(result) {
16556
+ const envelope = result;
16557
+ if (envelope?.isError || !Array.isArray(envelope?.content))
16558
+ throw new Error("Squire share renewal refused");
16559
+ const text2 = envelope.content.find((item) => item.type === "text")?.text;
16560
+ const body = JSON.parse(text2 ?? "{}");
16561
+ const expiresAt = typeof body.expires_at === "string" ? Date.parse(body.expires_at) : NaN;
16562
+ if (typeof body.url !== "string" || !/^https?:\/\/[^/]+\/s\/[A-Za-z0-9_-]{43}\/$/.test(body.url) || !Number.isFinite(expiresAt) || expiresAt <= Date.now())
16563
+ throw new Error("Squire share renewal returned an invalid lease");
16564
+ return { upstreamUrl: body.url, expiresAt };
16565
+ }
16566
+ var SquireShareRenewals = class {
16567
+ owns;
16568
+ persist;
16569
+ reaped;
16570
+ shares = /* @__PURE__ */ new Map();
16571
+ constructor(owns, persist, reaped) {
16572
+ this.owns = owns;
16573
+ this.persist = persist;
16574
+ this.reaped = reaped;
16575
+ }
16576
+ track(sessionId, client, result) {
16577
+ let lease;
16578
+ try {
16579
+ lease = shareResult(result);
16580
+ } catch {
16581
+ return;
16582
+ }
16583
+ this.stop(sessionId);
16584
+ const share = { sessionId, client, ...lease };
16585
+ this.shares.set(sessionId, share);
16586
+ this.schedule(share);
16587
+ }
16588
+ stop(sessionId) {
16589
+ const share = this.shares.get(sessionId);
16590
+ if (share?.timer)
16591
+ clearTimeout(share.timer);
16592
+ this.shares.delete(sessionId);
16593
+ }
16594
+ close() {
16595
+ for (const id of this.shares.keys())
16596
+ this.stop(id);
16597
+ }
16598
+ schedule(share, retry = false) {
16599
+ if (this.shares.get(share.sessionId) !== share)
16600
+ return;
16601
+ const remaining = share.expiresAt - Date.now();
16602
+ const margin = Math.min(4 * 6e4, Math.max(1, remaining / 2));
16603
+ const delay = retry ? 5e3 : Math.max(1, remaining - margin);
16604
+ share.timer = setTimeout(() => {
16605
+ void this.renew(share);
16606
+ }, delay);
16607
+ share.timer.unref?.();
16608
+ }
16609
+ async renew(share) {
16610
+ if (this.shares.get(share.sessionId) !== share)
16611
+ return;
16612
+ if (!this.owns(share.sessionId, share.client)) {
16613
+ this.stop(share.sessionId);
16614
+ this.reaped(share.sessionId);
16615
+ return;
16616
+ }
16617
+ try {
16618
+ const result = await share.client.requestMcp("tools/call", {
16619
+ name: "operate_share",
16620
+ arguments: { session_id: share.sessionId }
16621
+ });
16622
+ if (this.shares.get(share.sessionId) !== share)
16623
+ return;
16624
+ if (!this.owns(share.sessionId, share.client)) {
16625
+ this.stop(share.sessionId);
16626
+ this.reaped(share.sessionId);
16627
+ return;
16628
+ }
16629
+ const lease = shareResult(result);
16630
+ if (lease.upstreamUrl !== share.upstreamUrl || lease.expiresAt <= share.expiresAt)
16631
+ throw new Error("Squire did not renew the existing share");
16632
+ if (!await this.persist({ sessionId: share.sessionId, ...lease })) {
16633
+ this.stop(share.sessionId);
16634
+ return;
16635
+ }
16636
+ share.expiresAt = lease.expiresAt;
16637
+ this.schedule(share);
16638
+ } catch {
16639
+ this.schedule(share, true);
16640
+ }
16641
+ }
16642
+ };
16643
+
16554
16644
  // apps/body/dist/squire-session-registry.js
16555
16645
  var owners = /* @__PURE__ */ new Map();
16556
16646
  function registerSquireSession(sessionId, owner) {
@@ -16659,6 +16749,7 @@ var SquireTaskRelay = class {
16659
16749
  approvalRequestId;
16660
16750
  closed = false;
16661
16751
  callTail = Promise.resolve();
16752
+ shareRenewals;
16662
16753
  constructor(agentId, roomId, contextFile, authorize, home = homedir4(), makeClient = (callbacks) => new StdioSquireMcpClient({
16663
16754
  scope: { agentId, roomId },
16664
16755
  home,
@@ -16669,7 +16760,7 @@ var SquireTaskRelay = class {
16669
16760
  onNotification: callbacks.onNotification
16670
16761
  }), onApprovalDecided = () => {
16671
16762
  }, onSessionEnded = () => {
16672
- }) {
16763
+ }, onShareRenewed = async () => false) {
16673
16764
  this.agentId = agentId;
16674
16765
  this.roomId = roomId;
16675
16766
  this.contextFile = contextFile;
@@ -16677,10 +16768,13 @@ var SquireTaskRelay = class {
16677
16768
  this.makeClient = makeClient;
16678
16769
  this.onApprovalDecided = onApprovalDecided;
16679
16770
  this.onSessionEnded = onSessionEnded;
16771
+ this.shareRenewals = new SquireShareRenewals((id, client) => !this.closed && squireSessionOwner(id)?.client === client, onShareRenewed, (id) => this.onSessionEnded(id));
16680
16772
  }
16681
16773
  ended(ids) {
16682
- for (const id of ids)
16774
+ for (const id of ids) {
16775
+ this.shareRenewals.stop(id);
16683
16776
  this.onSessionEnded(id);
16777
+ }
16684
16778
  }
16685
16779
  async listen() {
16686
16780
  if (this.closed)
@@ -16774,6 +16868,7 @@ var SquireTaskRelay = class {
16774
16868
  if (this.closed)
16775
16869
  return;
16776
16870
  this.closed = true;
16871
+ this.shareRenewals.close();
16777
16872
  this.active = void 0;
16778
16873
  this.approvalRequestId = void 0;
16779
16874
  this.retire("helper-exit");
@@ -16985,6 +17080,8 @@ var SquireTaskRelay = class {
16985
17080
  if (this.activeScheduleId)
16986
17081
  for (const id of requestedIds)
16987
17082
  tagSquireSessionSchedule(id, this.activeScheduleId);
17083
+ if (safeToolName === "operate_share" && requestedIds.length === 1)
17084
+ this.shareRenewals.track(requestedIds[0], callClient, result);
16988
17085
  const approval = input.method === "tools/call" && squireApprovalFromMcp({ id: 1, method: input.method, params: input.params }, { id: 1, result });
16989
17086
  if (approval) {
16990
17087
  const ids = requestedIds.length ? requestedIds : [...task.sessions];
@@ -30453,7 +30550,7 @@ import { mkdir as mkdir13, rm as rm10, stat as stat8, writeFile as writeFile10 }
30453
30550
  import { dirname as dirname13, resolve as resolve16 } from "node:path";
30454
30551
  var UPDATE_CONVERGENCE_SLO_MS = 10 * 6e4;
30455
30552
  var DEFAULT_UPDATE_INTERVAL_MS = 3e4;
30456
- var UPDATE_DRAIN_DEADLINE_MS = UPDATE_CONVERGENCE_SLO_MS - 15e4;
30553
+ var UPDATE_DRAIN_DEADLINE_MS = 5 * 6e4;
30457
30554
  var MAX_HOST_RESTART_STAGGER_MS = 15e3;
30458
30555
  function managedRestartStaggerMs(agentId, desiredRelease, drainDeadlineAt, now2 = Date.now()) {
30459
30556
  const digest = createHash8("sha256").update(agentId).update("\0").update(desiredRelease).digest();
@@ -30655,8 +30752,8 @@ var ManagedUpdateHandoff = class _ManagedUpdateHandoff {
30655
30752
  }
30656
30753
  /**
30657
30754
  * Resolve one handoff tick against the daemon's authoritative turn registry.
30658
- * The coordinator keeps intake open while turns run and only closes it
30659
- * in a quiet gap before activating a staged release.
30755
+ * The coordinator keeps intake open until a quiet gap or the bounded
30756
+ * drain deadline before activating a staged release.
30660
30757
  */
30661
30758
  async restartRequest(readyToRestart) {
30662
30759
  const activeDrift = await this.check();
@@ -30665,7 +30762,8 @@ var ManagedUpdateHandoff = class _ManagedUpdateHandoff {
30665
30762
  const request = this.#restartRequest;
30666
30763
  if (!request)
30667
30764
  throw new Error("update drift was detected without an in-memory restart request");
30668
- if (!readyToRestart(request)) {
30765
+ const deadlineReached = this.#now() >= request.drainDeadlineAt;
30766
+ if (!readyToRestart(request, deadlineReached)) {
30669
30767
  return { kind: "waiting", request };
30670
30768
  }
30671
30769
  if (this.#stagedReleaseId) {
@@ -30700,11 +30798,10 @@ var ManagedUpdateHandoff = class _ManagedUpdateHandoff {
30700
30798
  });
30701
30799
  }
30702
30800
  this.#stagedReleaseId = void 0;
30703
- this.#requested = false;
30704
- this.#restartRequest = void 0;
30705
- this.#armRestart(stagedReleaseId);
30801
+ this.#requested = true;
30802
+ this.#restartRequest = request;
30706
30803
  }
30707
- return { kind: "restart", request: this.#restartRequest ?? request };
30804
+ return { kind: "restart", mode: deadlineReached ? "interrupted" : "drained", request: this.#restartRequest ?? request };
30708
30805
  }
30709
30806
  };
30710
30807
  async function coordinateManagedUpdateHandoff(update, readyToRestart, restart, waiting = async () => void 0) {
@@ -30715,7 +30812,7 @@ async function coordinateManagedUpdateHandoff(update, readyToRestart, restart, w
30715
30812
  await waiting(next.request);
30716
30813
  return "waiting-for-idle";
30717
30814
  }
30718
- await restart(next.request, "drained");
30815
+ await restart(next.request, next.mode ?? "drained");
30719
30816
  return "restarting";
30720
30817
  }
30721
30818
  var UPDATE_DRAIN_WAIT_LOG_INTERVAL_MS = 6e4;
@@ -30750,7 +30847,7 @@ var ManagedUpdateDrain = class {
30750
30847
  async #step() {
30751
30848
  const { update, quiesce, activeTurnCount } = this.#options;
30752
30849
  let armed;
30753
- const progress = await coordinateManagedUpdateHandoff(update, () => activeTurnCount() === 0 && quiesce(), async (request, mode) => {
30850
+ const progress = await coordinateManagedUpdateHandoff(update, (_request, deadlineReached) => (activeTurnCount() === 0 || deadlineReached) && quiesce(deadlineReached), async (request, mode) => {
30754
30851
  this.#disarm();
30755
30852
  await this.#options.restart(request, mode);
30756
30853
  this.#restarting = true;
@@ -30759,20 +30856,27 @@ var ManagedUpdateDrain = class {
30759
30856
  await this.#options.waiting?.(request);
30760
30857
  });
30761
30858
  if (armed && !this.#waitLogTimer)
30762
- this.#arm();
30859
+ this.#arm(armed);
30763
30860
  return progress;
30764
30861
  }
30765
- #arm() {
30862
+ #arm(request) {
30766
30863
  this.#logWaiting();
30767
- this.#scheduleWaitLog();
30864
+ this.#scheduleWaitLog(request);
30768
30865
  }
30769
- #scheduleWaitLog() {
30866
+ #scheduleWaitLog(request) {
30770
30867
  this.#waitLogTimer = this.#setTimer(() => {
30771
30868
  this.#waitLogTimer = void 0;
30772
30869
  if (this.#restarting)
30773
30870
  return;
30774
- void this.#resolve().catch((error) => this.#log(`[thin-core] update drain failed: ${String(error)}`));
30775
- }, UPDATE_DRAIN_WAIT_LOG_INTERVAL_MS);
30871
+ void this.#resolve().catch((error) => {
30872
+ if (this.#options.failed)
30873
+ this.#options.failed(error);
30874
+ else
30875
+ this.#log(`[thin-core] update drain failed: ${String(error)}`);
30876
+ if (!this.#restarting && this.#waitLogTimer === void 0)
30877
+ this.#scheduleWaitLog(request);
30878
+ });
30879
+ }, Math.min(UPDATE_DRAIN_WAIT_LOG_INTERVAL_MS, updateWaitMs(this.#options, request)));
30776
30880
  }
30777
30881
  #logWaiting() {
30778
30882
  this.#log(`[thin-core] update restart waiting: ${this.#options.activeTurnCount()} active turn(s); intake open; waiting for a quiet gap`);
@@ -30783,6 +30887,10 @@ var ManagedUpdateDrain = class {
30783
30887
  this.#waitLogTimer = void 0;
30784
30888
  }
30785
30889
  };
30890
+ function updateWaitMs(options, request) {
30891
+ const remaining = request.drainDeadlineAt - (options.now ?? Date.now)();
30892
+ return remaining > 0 ? remaining : UPDATE_DRAIN_WAIT_LOG_INTERVAL_MS;
30893
+ }
30786
30894
  function numberEnv(env, name, fallback) {
30787
30895
  const value = Number(env[name]);
30788
30896
  return Number.isFinite(value) && value >= 0 ? value : fallback;
@@ -31803,7 +31911,7 @@ var AcpRequestTimeoutError = class extends Error {
31803
31911
  this.name = "AcpRequestTimeoutError";
31804
31912
  }
31805
31913
  };
31806
- var TURN_BACKSTOP_MS = 30 * 6e4;
31914
+ var TURN_BACKSTOP_MS = 45 * 6e4;
31807
31915
  var AcpTurnBackstopError = class extends Error {
31808
31916
  timeoutMs;
31809
31917
  lastActivity;
@@ -32429,8 +32537,8 @@ ${String(error)}`);
32429
32537
  /**
32430
32538
  * Run one prompt turn. `timeoutMs` is an idle window, not a hard cap on
32431
32539
  * turn length: every inbound ACP message for this session re-arms it.
32432
- * Open tool calls suspend the backstop until completion. A silent session
32433
- * with no open tool is cancelled without stopping the runtime.
32540
+ * Tool progress re-arms the same bound; an open tool never disables it.
32541
+ * A silent session is cancelled without stopping sibling sessions.
32434
32542
  * `onChunk` is the ACP-boundary
32435
32543
  * streaming hook: called for every incremental `agent_message_chunk`
32436
32544
  * delta as it arrives, so a caller can project live text without waiting
@@ -32554,16 +32662,14 @@ ${String(error)}`);
32554
32662
  }
32555
32663
  }
32556
32664
  /**
32557
- * A tool owns its deadline. Silence while it is open is never a turn failure.
32558
- * After the last tool closes, start a full backstop window for this session.
32665
+ * Bound silence even when a tool stays open: a missing terminal tool update
32666
+ * must not leave a claimed turn running indefinitely.
32559
32667
  */
32560
32668
  resetPendingIdleTimeout(id) {
32561
32669
  const p = this.pending.get(id);
32562
32670
  if (!p || p.idleTimeoutMs === void 0)
32563
32671
  return;
32564
32672
  this.clearTimer(p);
32565
- if (p.openToolCalls?.size)
32566
- return;
32567
32673
  const { idleTimeoutMs, reject, detail, sessionId, lastActivity } = p;
32568
32674
  p.timer = setTimeout(() => {
32569
32675
  this.pending.delete(id);
@@ -32601,13 +32707,6 @@ ${String(error)}`);
32601
32707
  continue;
32602
32708
  const update = params?.update;
32603
32709
  pending.lastActivity = typeof update?.sessionUpdate === "string" ? update.sessionUpdate : method ?? `response:${response?.method ?? "request"}`;
32604
- if (typeof update?.toolCallId === "string" && (update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update")) {
32605
- if (update.status === "pending" || update.status === "in_progress") {
32606
- pending.openToolCalls?.add(update.toolCallId);
32607
- } else if (update.status === "completed" || update.status === "failed") {
32608
- pending.openToolCalls?.delete(update.toolCallId);
32609
- }
32610
- }
32611
32710
  this.resetPendingIdleTimeout(requestId);
32612
32711
  }
32613
32712
  }
@@ -32791,7 +32890,7 @@ ${String(error)}`);
32791
32890
  method,
32792
32891
  ...sessionId ? { sessionId } : {},
32793
32892
  ...detail ? { detail } : {},
32794
- ...backstop ? { idleTimeoutMs: timeoutMs, lastActivity: method, openToolCalls: /* @__PURE__ */ new Set() } : {}
32893
+ ...backstop ? { idleTimeoutMs: timeoutMs, lastActivity: method } : {}
32795
32894
  });
32796
32895
  if (backstop)
32797
32896
  this.resetPendingIdleTimeout(id);
@@ -35962,8 +36061,11 @@ async function shareChromiumPage(input) {
35962
36061
  if (message.method === "Page.frameNavigated" && message.params?.frame?.url) {
35963
36062
  try {
35964
36063
  const url = new URL(message.params.frame.url);
35965
- if (url.origin !== "null" && relay.readyState === import_websocket.default.OPEN)
35966
- relay.send(JSON.stringify({ type: "share-event", name: "navigation", detail: url.origin }));
36064
+ if (["http:", "https:"].includes(url.protocol) && relay.readyState === import_websocket.default.OPEN) {
36065
+ url.username = "";
36066
+ url.password = "";
36067
+ relay.send(JSON.stringify({ type: "share-event", name: "navigation", detail: url.href }));
36068
+ }
35967
36069
  } catch {
35968
36070
  }
35969
36071
  return;
@@ -43234,7 +43336,7 @@ var MonolithRoomTurnLoop = class {
43234
43336
  }, (sessionId) => {
43235
43337
  options.api.execute("endComputerSession", { roomId: options.roomId, sessionId }).catch(() => {
43236
43338
  });
43237
- });
43339
+ }, async (lease) => (await options.api.execute("refreshComputerSession", { roomId: options.roomId, ...lease })).live);
43238
43340
  this.options = { ...options, api: this.commandContext.bind(options.api) };
43239
43341
  options.grantRunner?.register(options.roomId, {
43240
43342
  workspaceId: options.workspaceId,
@@ -44277,6 +44379,8 @@ var MonolithRoomTurnLoop = class {
44277
44379
  void finishTrace("cancelled").catch(() => void 0);
44278
44380
  return;
44279
44381
  }
44382
+ if (error instanceof AcpTurnBackstopError)
44383
+ await this.discardSession();
44280
44384
  if (liveCornerOpened && error instanceof AcpTurnBackstopError) {
44281
44385
  console.log(`[thin-core] monolith Room ${this.options.roomId} turn ${item.id}: turn_backstop after opening a corner; the work continues in the corner`);
44282
44386
  this.options.onCornerOpened?.();
@@ -45493,7 +45597,7 @@ var MonolithCornerTurnLoop = class {
45493
45597
  }, (sessionId) => {
45494
45598
  options.api.execute("endComputerSession", { roomId: options.cornerId, sessionId }).catch(() => {
45495
45599
  });
45496
- });
45600
+ }, async (lease) => (await options.api.execute("refreshComputerSession", { roomId: options.cornerId, ...lease })).live);
45497
45601
  this.options = { ...options, api: this.commandContext.bind(options.api) };
45498
45602
  options.grantRunner?.register(options.cornerId, {
45499
45603
  workspaceId: options.workspaceId,
@@ -46733,6 +46837,8 @@ ${continuation}` : assembled;
46733
46837
  void finishTrace("cancelled").catch(() => void 0);
46734
46838
  return;
46735
46839
  }
46840
+ if (error instanceof AcpTurnBackstopError)
46841
+ await this.discardSession();
46736
46842
  const reason = distillTurnFailureReason(error);
46737
46843
  startTraceStats();
46738
46844
  await api.execute("postAgentTurnReceipt", {
@@ -50042,12 +50148,22 @@ async function runMachineHelper(options = {}) {
50042
50148
  env
50043
50149
  });
50044
50150
  }
50151
+ const recoverManagedRestart = (error) => {
50152
+ console.error("[helper] managed restart failed; serving again:", error);
50153
+ for (const agent of hosted.values())
50154
+ agent.lifecycle.resumeAfterFailedUpdate(() => agent.core.resumeServing());
50155
+ };
50045
50156
  const updateDrain = update ? new ManagedUpdateDrain({
50046
50157
  update,
50047
- quiesce: () => !forceUpdate?.active && lifecycle.serving && quiesceHostedAgents([...hosted.values()]),
50158
+ failed: recoverManagedRestart,
50159
+ quiesce: (deadlineReached) => !forceUpdate?.active && lifecycle.serving && quiesceHostedAgents([...hosted.values()], deadlineReached),
50048
50160
  activeTurnCount: () => [...hosted.values()].reduce((sum, agent) => sum + agent.core.activeTurnCount(), 0),
50049
- restart: async ({ desiredRelease }) => {
50050
- stoppingStatus = `update pending, converging; loaded_release=${loadedRelease ?? "unknown"}; desired_release=${desiredRelease}; drain complete; intake quiesced`;
50161
+ restart: async ({ desiredRelease }, mode) => {
50162
+ if (mode === "interrupted") {
50163
+ for (const agent of hosted.values())
50164
+ journalInterruptedTurns(agent.runtimeDir, agent.core.interruptForServerMinimum());
50165
+ }
50166
+ stoppingStatus = `update pending, converging; loaded_release=${loadedRelease ?? "unknown"}; desired_release=${desiredRelease}; drain ${mode}; intake quiesced`;
50051
50167
  await notifier.stopping(stoppingStatus).catch((error) => console.error("[helper] stopping notification failed:", error));
50052
50168
  restartHelper("update");
50053
50169
  },
@@ -50059,9 +50175,7 @@ async function runMachineHelper(options = {}) {
50059
50175
  try {
50060
50176
  await updateDrain?.tick();
50061
50177
  } catch (error) {
50062
- console.error("[helper] managed restart failed; serving again:", error);
50063
- for (const agent of hosted.values())
50064
- agent.lifecycle.resumeAfterFailedUpdate(() => agent.core.resumeServing());
50178
+ recoverManagedRestart(error);
50065
50179
  }
50066
50180
  void sweepHostDisk();
50067
50181
  };
@@ -50231,8 +50345,8 @@ async function runMachineHelper(options = {}) {
50231
50345
  }
50232
50346
  return finish(lifecycle.stopReason ?? "stopped");
50233
50347
  }
50234
- function quiesceHostedAgents(agents) {
50235
- if (agents.some((agent) => agent.core.activeTurnCount() > 0))
50348
+ function quiesceHostedAgents(agents, deadlineReached = false) {
50349
+ if (!deadlineReached && agents.some((agent) => agent.core.activeTurnCount() > 0))
50236
50350
  return false;
50237
50351
  if (agents.some((agent) => !agent.lifecycle.serving && !(agent.lifecycle.state.kind === "quiescing" && agent.lifecycle.state.reason === "update")))
50238
50352
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usebeeline",
3
- "version": "0.1.30",
3
+ "version": "0.1.33",
4
4
  "description": "Connect an AI coding agent to Beeline with one command.",
5
5
  "homepage": "https://usebeeline.app",
6
6
  "bugs": {