@bivy/bivy 0.20.1-staging.8 → 0.20.1

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 (120) hide show
  1. package/bin/access-view.mjs +43 -0
  2. package/bin/bivy.mjs +111 -14
  3. package/bin/cli-commands.mjs +1 -0
  4. package/bin/tailscale.mjs +10 -3
  5. package/dist/access.js +72 -0
  6. package/dist/apps/listeners.js +31 -3
  7. package/dist/apps/service.js +8 -1
  8. package/dist/apps/styles.css +4 -0
  9. package/dist/auth.js +18 -4
  10. package/dist/controllers/app-commands.js +7 -2
  11. package/dist/protocol/client-command-routes.js +2 -0
  12. package/dist/protocol/client-command-schemas.js +3 -1
  13. package/dist/remote/direct.js +2 -1
  14. package/dist/remote/index.js +1 -0
  15. package/dist/remote/tailnet.js +44 -0
  16. package/dist/server.js +40 -2
  17. package/dist/session/run-terminal.js +6 -2
  18. package/package.json +1 -1
  19. package/web/assets/AutomationsView-fnzmthnq.js +51 -0
  20. package/web/assets/{ChangesCard-sGQlkM2d.js → ChangesCard-BWUgQ40Y.js} +1 -1
  21. package/web/assets/{LibraryView-DYUo2624.js → LibraryView-JlSOji08.js} +1 -1
  22. package/web/assets/{ProviderConnect-DB-gwOwe.js → ProviderConnect-BFFZWHsW.js} +1 -1
  23. package/web/assets/Settings-BAHWxYDH.js +10 -0
  24. package/web/assets/{Sheet-BmAB1Fu_.js → Sheet-cVfLQVwf.js} +1 -1
  25. package/web/assets/{Terminal-Dkr504wc.js → Terminal-C3QFXkbI.js} +1 -1
  26. package/web/assets/{VoiceSettings-DaR6ba3A.js → VoiceSettings-CWOtAlAV.js} +1 -1
  27. package/web/assets/{abnfDiagram-VCTEODGH-CZTMfHPQ.js → abnfDiagram-VCTEODGH-7cQ-bgry.js} +1 -1
  28. package/web/assets/architecture-7GRP2DOG-DVlvlW8H.js +1 -0
  29. package/web/assets/{architectureDiagram-5GKGNRK7-BBR7u-Nc.js → architectureDiagram-5GKGNRK7-BWOpNQNn.js} +1 -1
  30. package/web/assets/{blockDiagram-I7D4REHJ-DU1iTe41.js → blockDiagram-I7D4REHJ-8eE7JD9I.js} +1 -1
  31. package/web/assets/{c4Diagram-7LVT6UL2-DoY25VIT.js → c4Diagram-7LVT6UL2-DgiPr6K1.js} +1 -1
  32. package/web/assets/channel-Coq89lsO.js +1 -0
  33. package/web/assets/{chunk-4HAMMTFA-CwLS5ngk.js → chunk-4HAMMTFA-C8GTT_rj.js} +1 -1
  34. package/web/assets/{chunk-75Z2AOVW-FHeNbdLy.js → chunk-75Z2AOVW-BRMKvs-_.js} +1 -1
  35. package/web/assets/{chunk-DU6HZSFF-CUNfwm6b.js → chunk-DU6HZSFF-A-GuRJzV.js} +1 -1
  36. package/web/assets/{chunk-F27PBJKO-BT9LzzRz.js → chunk-F27PBJKO-D34kNJfE.js} +1 -1
  37. package/web/assets/{chunk-GMAD6QVW-BUr3PoNi.js → chunk-GMAD6QVW-CzLW6Gbl.js} +1 -1
  38. package/web/assets/{chunk-GVQU2GXP-Dwa-Ue8e.js → chunk-GVQU2GXP-CqTFATNd.js} +1 -1
  39. package/web/assets/{chunk-IMKFNOWR-CBemkwVh.js → chunk-IMKFNOWR-BLo_6uPl.js} +1 -1
  40. package/web/assets/{chunk-L3NEJ4N5-AGhZr4q5.js → chunk-L3NEJ4N5-CFSYYra2.js} +1 -1
  41. package/web/assets/{chunk-OSK3NFVY-CxTtDDij.js → chunk-OSK3NFVY-DJ3F-6TB.js} +1 -1
  42. package/web/assets/{chunk-P2QGCYS3-DGmss7hg.js → chunk-P2QGCYS3-B3qbBF6d.js} +1 -1
  43. package/web/assets/{chunk-POPQ4Y6H-nsxQTUJd.js → chunk-POPQ4Y6H-DbN8xQ-f.js} +1 -1
  44. package/web/assets/{chunk-PWAF6VOD-CFEkzIZ2.js → chunk-PWAF6VOD-C7BBYtAa.js} +1 -1
  45. package/web/assets/{chunk-SHT3W25Y-BArb8KoG.js → chunk-SHT3W25Y-B3wT37BE.js} +1 -1
  46. package/web/assets/{chunk-SVP7TREG-CEFJfpYl.js → chunk-SVP7TREG-OXvICZat.js} +1 -1
  47. package/web/assets/{chunk-TICWLB2K-ByqSjpyA.js → chunk-TICWLB2K-CrmJ4sWX.js} +1 -1
  48. package/web/assets/classDiagram-ZZMXUADV-rFDDoTN3.js +1 -0
  49. package/web/assets/classDiagram-v2-VYDZK3BY-rFDDoTN3.js +1 -0
  50. package/web/assets/{cynefin-OW5HDTMX-BPd4eJmp.js → cynefin-OW5HDTMX-BFhdG87z.js} +1 -1
  51. package/web/assets/{cynefinDiagram-5FMLGOSQ-rm271vYO.js → cynefinDiagram-5FMLGOSQ-BlBRhJR5.js} +1 -1
  52. package/web/assets/{dagre-GXQ25YYZ-a4DVMh7o.js → dagre-GXQ25YYZ-COxHf3g-.js} +1 -1
  53. package/web/assets/{diagram-S7CK7UJ4-CR4kj8JQ.js → diagram-S7CK7UJ4-mWtNPBS9.js} +1 -1
  54. package/web/assets/{diagram-UQ7AKVKN-BVAidz_h.js → diagram-UQ7AKVKN-CAvoNcsc.js} +1 -1
  55. package/web/assets/{diagram-VSXAHHWV-P_KshIhr.js → diagram-VSXAHHWV-DA9LJqgE.js} +1 -1
  56. package/web/assets/{diagram-VX7I27RA-CfR4HucM.js → diagram-VX7I27RA-7RswVDku.js} +1 -1
  57. package/web/assets/{diagram-Z3DM3KII-CnxFHDS4.js → diagram-Z3DM3KII-DknRBMRd.js} +1 -1
  58. package/web/assets/{ebnfDiagram-PWID7BFC-0lvDIevQ.js → ebnfDiagram-PWID7BFC-hAmbemf1.js} +1 -1
  59. package/web/assets/{erDiagram-RLTQ6QDP-B5onTaVV.js → erDiagram-RLTQ6QDP-DxXSxQWj.js} +1 -1
  60. package/web/assets/eventmodeling-NTZA5JFV-CMDfKRrP.js +1 -0
  61. package/web/assets/flowDiagram-HODETNUW-BQ27YGHa.js +1 -0
  62. package/web/assets/{ganttDiagram-EL5Y4UJY-DcP58ot2.js → ganttDiagram-EL5Y4UJY-BV-zpuyt.js} +1 -1
  63. package/web/assets/{gitGraph-4MIJSDKK-DMgkg1Fa.js → gitGraph-4MIJSDKK-BfZe7_3E.js} +1 -1
  64. package/web/assets/{gitGraphDiagram-WWUBYQGX-ChZQlU2S.js → gitGraphDiagram-WWUBYQGX-3_hHUbBU.js} +1 -1
  65. package/web/assets/index-D36pVYmX.css +1 -0
  66. package/web/assets/{index-emCm7_bF.js → index-x_iImoSf.js} +2 -2
  67. package/web/assets/{info-A6RAGUB7-Bwm7-KTz.js → info-A6RAGUB7-DUWO2H1L.js} +1 -1
  68. package/web/assets/{infoDiagram-27XIBGKW-PpNSZSrV.js → infoDiagram-27XIBGKW-BKxWRZYw.js} +1 -1
  69. package/web/assets/{ishikawaDiagram-5VMMS53U-DXh6yXna.js → ishikawaDiagram-5VMMS53U-CxQdy8l3.js} +1 -1
  70. package/web/assets/{journeyDiagram-3NMN7TZE-DFEj5jkB.js → journeyDiagram-3NMN7TZE-DplhU31X.js} +1 -1
  71. package/web/assets/{kanban-definition-UXKFOSKX-C5Mvo17h.js → kanban-definition-UXKFOSKX-Cmls5FZv.js} +1 -1
  72. package/web/assets/{line-CqUbcJrv.js → line-NoU3PhOc.js} +1 -1
  73. package/web/assets/{mermaid-parser.core-BzUSMP_7.js → mermaid-parser.core-CyOqjoOF.js} +3 -3
  74. package/web/assets/{mermaid.core-CooqV3Py.js → mermaid.core-BbpKlZWj.js} +4 -4
  75. package/web/assets/{mindmap-definition-YA3MSWOX-CvsfS7bG.js → mindmap-definition-YA3MSWOX-CfDDhyG9.js} +1 -1
  76. package/web/assets/mount-GHVLdZcl.js +27 -0
  77. package/web/assets/{packet-AYTQ26CC-BzXGhTo4.js → packet-AYTQ26CC-CEyZqXlC.js} +1 -1
  78. package/web/assets/{pegDiagram-XKGWAZYB-BvdN2RIR.js → pegDiagram-XKGWAZYB-BsQ551jM.js} +1 -1
  79. package/web/assets/{pie-WAS4IAKB-CNH8JQby.js → pie-WAS4IAKB-Cy_8_ris.js} +1 -1
  80. package/web/assets/{pieDiagram-E7YTZNPT-C6LABPqT.js → pieDiagram-E7YTZNPT-DQHtyFkg.js} +1 -1
  81. package/web/assets/{quadrantDiagram-AXDQQJYC-D2ebcGIP.js → quadrantDiagram-AXDQQJYC-iSVEwT1y.js} +1 -1
  82. package/web/assets/{radar-RG4KPBEZ-B7uFPzC9.js → radar-RG4KPBEZ-BO-qdlYd.js} +1 -1
  83. package/web/assets/{railroad-74A4TZTK-CE9pdZ9o.js → railroad-74A4TZTK-BEuDeDCt.js} +1 -1
  84. package/web/assets/railroad-abnf-HS5TGJTU-BbGdsa6v.js +1 -0
  85. package/web/assets/railroad-ebnf-LZEXJU2U-qaRxJkxs.js +1 -0
  86. package/web/assets/railroad-peg-WCYAUIDC-C6hTOfSB.js +1 -0
  87. package/web/assets/{railroadDiagram-O6MQD6OU-vJc9tRJP.js → railroadDiagram-O6MQD6OU-Bmp5czol.js} +1 -1
  88. package/web/assets/{requirementDiagram-BXWQKSXE-CVLUnEfL.js → requirementDiagram-BXWQKSXE-CflpYj7d.js} +1 -1
  89. package/web/assets/{sankeyDiagram-P5KCCOFB-DkHI3ZIh.js → sankeyDiagram-P5KCCOFB-D3xDfaLR.js} +1 -1
  90. package/web/assets/{sequenceDiagram-WJ2MYXX4-BgosmpaZ.js → sequenceDiagram-WJ2MYXX4-C6ZWIJwX.js} +1 -1
  91. package/web/assets/settingsRoute-COWp-9BH.js +1 -0
  92. package/web/assets/{stateDiagram-D77RDMKH-BZ5Zqme7.js → stateDiagram-D77RDMKH-BXjL8DPv.js} +1 -1
  93. package/web/assets/stateDiagram-v2-MP3YSRHH-BCGm337k.js +1 -0
  94. package/web/assets/{swimlanes-42K2YHIH-rkMg8KEw.js → swimlanes-42K2YHIH-CkyaLI3E.js} +1 -1
  95. package/web/assets/swimlanesDiagram-VR7AAH4N-RZPHF_4s.js +8 -0
  96. package/web/assets/{timeline-definition-24CTP7MA-_z_HJLWC.js → timeline-definition-24CTP7MA-BdMWCl3_.js} +1 -1
  97. package/web/assets/{treeView-Q6P3EWNA-jR5Z1Fmf.js → treeView-Q6P3EWNA-C4MMyFq1.js} +1 -1
  98. package/web/assets/{treemap-WGGIJYW6-OtJhfdUS.js → treemap-WGGIJYW6-BUER7WW0.js} +1 -1
  99. package/web/assets/{useStore-CaPvUEwc.js → useStore-B5xeFBSp.js} +3 -3
  100. package/web/assets/{vennDiagram-4TSXK5OY-BFTbjE-l.js → vennDiagram-4TSXK5OY-CmcfuybF.js} +1 -1
  101. package/web/assets/{wardley-WFR3VGLG-BV0ly0l7.js → wardley-WFR3VGLG-CbuHqcZs.js} +1 -1
  102. package/web/assets/{wardleyDiagram-VM6X3IG4-DYvCqNRg.js → wardleyDiagram-VM6X3IG4-B-dD7NPY.js} +1 -1
  103. package/web/assets/{xychartDiagram-S5SC5T6Z-CaYy8yTC.js → xychartDiagram-S5SC5T6Z-DkJf1OlB.js} +1 -1
  104. package/web/index.html +2 -2
  105. package/web/sw.js +1 -1
  106. package/web/assets/AutomationsView-BnHdmp7P.js +0 -51
  107. package/web/assets/Settings-VYk0Et0A.js +0 -10
  108. package/web/assets/architecture-7GRP2DOG-MiVMMOHz.js +0 -1
  109. package/web/assets/channel-m_2Ky5Co.js +0 -1
  110. package/web/assets/classDiagram-ZZMXUADV-BM6g8SUg.js +0 -1
  111. package/web/assets/classDiagram-v2-VYDZK3BY-BM6g8SUg.js +0 -1
  112. package/web/assets/eventmodeling-NTZA5JFV-DviokIMu.js +0 -1
  113. package/web/assets/flowDiagram-HODETNUW-BtFCDXEP.js +0 -1
  114. package/web/assets/index-CsC63U16.css +0 -1
  115. package/web/assets/mount-BD-g3eYn.js +0 -26
  116. package/web/assets/railroad-abnf-HS5TGJTU-C_bKzcjL.js +0 -1
  117. package/web/assets/railroad-ebnf-LZEXJU2U-BzPEt-Wn.js +0 -1
  118. package/web/assets/railroad-peg-WCYAUIDC-CeL6qyBT.js +0 -1
  119. package/web/assets/stateDiagram-v2-MP3YSRHH-DtyGmcgM.js +0 -1
  120. package/web/assets/swimlanesDiagram-VR7AAH4N-CbnaKO-d.js +0 -8
@@ -0,0 +1,44 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // Copyright (c) 2026 Petter André Sjulstad
3
+ //
4
+ // The other machines on your tailnet that run Bivy, so a machine reached over
5
+ // Tailscale can offer the rest. Tailscale lists the peers; each is asked for
6
+ // Bivy's hello at its tailnet address, which only answers where `bivy
7
+ // tailscale` is on.
8
+ import { execFile } from "node:child_process";
9
+ /** Peers from `tailscale status --json`, without the machine itself. */
10
+ export function parseTailnetPeers(text) {
11
+ let status;
12
+ try {
13
+ status = JSON.parse(text);
14
+ }
15
+ catch {
16
+ return [];
17
+ }
18
+ return Object.values(status?.Peer ?? {})
19
+ .map((p) => ({ dnsName: String(p.DNSName ?? "").replace(/\.$/, ""), hostName: String(p.HostName ?? ""), online: p.Online === true, os: p.OS }))
20
+ .filter((p) => p.dnsName.endsWith(".ts.net"));
21
+ }
22
+ function tailscaleStatus() {
23
+ return new Promise((resolve) => {
24
+ execFile("tailscale", ["status", "--json"], { timeout: 3000, maxBuffer: 4 * 1024 * 1024 }, (error, stdout) => resolve(error ? "" : stdout));
25
+ });
26
+ }
27
+ async function hello(peer, fetchImpl) {
28
+ try {
29
+ const res = await fetchImpl(`https://${peer.dnsName}/api/direct/hello`, { signal: AbortSignal.timeout(2500) });
30
+ const body = await res.json();
31
+ if (!res.ok || body?.bivy !== true)
32
+ return null;
33
+ return { name: typeof body.name === "string" && body.name ? body.name : peer.hostName, url: `https://${peer.dnsName}`, nodeId: typeof body.nodeId === "string" ? body.nodeId : undefined, os: peer.os, online: true, self: false };
34
+ }
35
+ catch {
36
+ return null;
37
+ }
38
+ }
39
+ /** Online tailnet peers running Bivy over Tailscale. Offline peers aren't asked. */
40
+ export async function discoverTailnetMachines(deps = {}) {
41
+ const peers = parseTailnetPeers(await (deps.status ?? tailscaleStatus)()).filter((p) => p.online);
42
+ const found = await Promise.all(peers.map((p) => hello(p, deps.fetchImpl ?? fetch)));
43
+ return found.filter((m) => m !== null).sort((a, b) => a.name.localeCompare(b.name));
44
+ }
package/dist/server.js CHANGED
@@ -87,9 +87,11 @@ import { SessionEventCoalescer } from "./session-event-coalescer.js";
87
87
  import { readMachineTheme, watchMachineTheme } from "./machine-theme.js";
88
88
  import { authMiddleware, resolveAuth, isAuthorized, requestOriginAllowed, markRemoteListener } from "./auth.js";
89
89
  import { PairCodes } from "./pair-codes.js";
90
+ import { accessReport } from "./access.js";
91
+ import { hostedEndpoints } from "./hosted-endpoints.mjs";
90
92
  import http from "node:http";
91
93
  import { webAppDirs, webAppRouter } from "./web-app.js";
92
- import { RelayConnector, loadRelayConfig, loadDirectListenerConfig, soloCredentials } from "./remote/index.js";
94
+ import { RelayConnector, loadRelayConfig, loadDirectListenerConfig, discoverTailnetMachines, soloCredentials } from "./remote/index.js";
93
95
  import { readEphemeralTeardownConfig, shouldSelfTeardown, snapshotsDurableForTeardown, performSelfTeardown } from "./ephemeral-teardown.js";
94
96
  import { buildSessionSnapshot, applySessionSnapshot } from "./session/snapshot.js";
95
97
  import { clearTurnActivity } from "./session/turn-activity.js";
@@ -2297,6 +2299,25 @@ appRegistry.on("notes", (viewId) => {
2297
2299
  });
2298
2300
  }, NOTES_NOTIFY_MS).unref?.();
2299
2301
  });
2302
+ // Asking every peer takes a moment; a short cache keeps switcher opens instant.
2303
+ let tailnetCache = null;
2304
+ async function tailnetMachines() {
2305
+ const direct = loadDirectListenerConfig(appDir);
2306
+ if (!direct?.hostname)
2307
+ return [];
2308
+ if (!tailnetCache || Date.now() - tailnetCache.at > 15_000)
2309
+ tailnetCache = { at: Date.now(), machines: discoverTailnetMachines() };
2310
+ const self = { name: identity.name, url: `https://${direct.hostname}`, nodeId: identity.nodeId, os: process.platform, online: true, self: true };
2311
+ return [self, ...(await tailnetCache.machines).filter((m) => m.url !== self.url)];
2312
+ }
2313
+ function currentAccess() {
2314
+ const relay = loadRelayConfig(appDir);
2315
+ return accessReport({
2316
+ tailscaleHostname: loadDirectListenerConfig(appDir)?.hostname,
2317
+ relay: relay ? { controlPlaneUrl: relay.controlPlaneUrl, room: relay.room, roomToken: relay.roomToken } : null,
2318
+ hostedControlPlane: hostedEndpoints(process.env).controlPlane,
2319
+ });
2320
+ }
2300
2321
  const RELAY_COMMANDS = {
2301
2322
  ...createAppCommands(appService, (id) => { const record = resolveSession(id); return record ? harnessDirFor(record) : undefined; }, (app) => {
2302
2323
  const record = resolveSession(app.sessionId);
@@ -2314,6 +2335,14 @@ const RELAY_COMMANDS = {
2314
2335
  // (see runBivyUpdate). The node restarts itself when the update lands, so the
2315
2336
  // client just sees the socket reconnect on the new build; a failure to even
2316
2337
  // start reports back so the banner can show the manual command.
2338
+ // The machines on this machine's tailnet that run Bivy, this one first.
2339
+ async "tailnet.machines"(msg, ctx) {
2340
+ ctx.reply({ type: "tailnet.machines.ok", requestId: msg.requestId, machines: await tailnetMachines() });
2341
+ },
2342
+ // How this machine can be reached and what the next setup would add (src/access.ts).
2343
+ "access.get"(msg, ctx) {
2344
+ ctx.reply({ type: "access.get.ok", requestId: msg.requestId, ...currentAccess() });
2345
+ },
2317
2346
  "node.update"(_msg, ctx) {
2318
2347
  const result = runBivyUpdate();
2319
2348
  ctx.reply({ type: "node.update.result", ok: result.ok, error: result.error });
@@ -9478,6 +9507,12 @@ app.post("/api/auth/pair", sensitiveRateLimiter, (req, res) => {
9478
9507
  const { device, token } = accessDevices.create(name);
9479
9508
  res.json({ ok: true, device, token });
9480
9509
  });
9510
+ // Identifies a Bivy node to the other machines on its tailnet, so each can list
9511
+ // the rest (tailnet.machines). Public, like /healthz: it says only that Bivy
9512
+ // runs here and under which name; the direct listener is tailnet-only.
9513
+ app.get("/api/direct/hello", (_req, res) => {
9514
+ res.json({ bivy: true, name: identity.name, nodeId: identity.nodeId });
9515
+ });
9481
9516
  // All other /api routes require auth (loopback may bypass, per config and host
9482
9517
  // — see loopbackAllowed()/isMultiUserHost() in src/auth.ts).
9483
9518
  app.use("/api", authMiddleware(identity, {
@@ -12142,8 +12177,10 @@ const wss = new WebSocketServer({ server, path: "/ws" });
12142
12177
  // getting loopback trust; every caller needs a device token.
12143
12178
  const directWebApp = webAppRouter(webAppDirs(repoRoot));
12144
12179
  let directListener = null;
12180
+ let directOwner;
12145
12181
  async function reloadDirectListener() {
12146
12182
  const config = loadDirectListenerConfig(appDir);
12183
+ directOwner = config?.owner;
12147
12184
  if (directListener && directListener.port !== config?.port) {
12148
12185
  const previous = directListener;
12149
12186
  directListener = null;
@@ -12160,7 +12197,8 @@ async function reloadDirectListener() {
12160
12197
  directApp.use(directWebApp);
12161
12198
  directApp.use(app);
12162
12199
  const listener = http.createServer(directApp);
12163
- markRemoteListener(listener);
12200
+ // The owner's own tailnet devices come in on their Tailscale identity.
12201
+ markRemoteListener(listener, { trustedLogin: () => directOwner });
12164
12202
  const directWss = new WebSocketServer({ server: listener, path: "/ws" });
12165
12203
  directWss.on("connection", (socket, req) => wss.emit("connection", socket, req));
12166
12204
  await new Promise((resolve, reject) => {
@@ -180,7 +180,7 @@ export function createRunTerminals(deps) {
180
180
  deps.broadcast({ type: "terminal.idle", termId: id, at: Date.now() });
181
181
  broadcastRunTerminalList();
182
182
  const who = displayName || "An agent";
183
- deps.sendNotificationHint({ kind: "agent_waiting", title: `${who} is waiting`, body: `${who} has been quiet for a bit — it may be waiting for your input.` });
183
+ deps.sendNotificationHint({ kind: "agent_waiting", title: `${who} is waiting`, body: `${who} has been quiet for a bit — it may be waiting for your input.`, ...runTarget(id) });
184
184
  }, IDLE_NOTIFY_INTERVAL));
185
185
  }
186
186
  function clearRunIdleNotify(id) {
@@ -201,7 +201,11 @@ export function createRunTerminals(deps) {
201
201
  lastBellNotify.set(id, now);
202
202
  const meta = terminals.meta(id);
203
203
  const who = meta?.name?.trim() || meta?.label?.trim() || "A terminal";
204
- deps.sendNotificationHint({ kind: "terminal_bell", title: "Terminal bell", body: `${who} rang the terminal bell — it may be waiting for you.` });
204
+ deps.sendNotificationHint({ kind: "terminal_bell", title: "Terminal bell", body: `${who} rang the terminal bell — it may be waiting for you.`, ...runTarget(id) });
205
+ }
206
+ function runTarget(id) {
207
+ const sessionId = terminals.meta(id)?.sessionId;
208
+ return sessionId ? { sessionId, targetSessionId: sessionId } : {};
205
209
  }
206
210
  function clearBellNotify(id) {
207
211
  lastBellNotify.delete(id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bivy/bivy",
3
- "version": "0.20.1-staging.8",
3
+ "version": "0.20.1",
4
4
  "type": "module",
5
5
  "license": "AGPL-3.0-only",
6
6
  "description": "Run coding agents on machines you own. Open-source, self-hostable agent workspace.",