@bivy/bivy 0.20.6-staging.1 → 0.20.6-staging.3

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 (110) hide show
  1. package/bin/bivy.mjs +8 -1
  2. package/dist/apps/styles.css +54 -2
  3. package/dist/controllers/session-control.js +1 -3
  4. package/dist/server.js +40 -25
  5. package/dist/session/run-terminal.js +27 -3
  6. package/dist/terminal.js +1 -1
  7. package/package.json +1 -1
  8. package/web/assets/AutomationsView-BM8OM-LI.js +51 -0
  9. package/web/assets/{ChangesCard-CvoN20L_.js → ChangesCard-BQVgcOmR.js} +1 -1
  10. package/web/assets/LibraryView-CtIZ6j7-.js +2 -0
  11. package/web/assets/{ProviderConnect-NBSZCsr7.js → ProviderConnect-DVo_6v0N.js} +1 -1
  12. package/web/assets/Settings-BNoANEok.js +10 -0
  13. package/web/assets/{Sheet-CasWIEum.js → Sheet-CpIoowo5.js} +2 -2
  14. package/web/assets/{Terminal-DgmjWWQy.js → Terminal-LZ8azLT7.js} +6 -6
  15. package/web/assets/{VoiceSettings-DGKH9WLj.js → VoiceSettings-D_AqVrLp.js} +1 -1
  16. package/web/assets/{abnfDiagram-VCTEODGH-BRrCmuS-.js → abnfDiagram-VCTEODGH-q_V5KCFh.js} +1 -1
  17. package/web/assets/architecture-7GRP2DOG-B70g2stv.js +1 -0
  18. package/web/assets/{architectureDiagram-5GKGNRK7-BUwe8I2s.js → architectureDiagram-5GKGNRK7-DdXDQzrJ.js} +1 -1
  19. package/web/assets/{blockDiagram-I7D4REHJ-BXmJPr7n.js → blockDiagram-I7D4REHJ-D4y7qPKE.js} +1 -1
  20. package/web/assets/{c4Diagram-7LVT6UL2-CAkDUCpJ.js → c4Diagram-7LVT6UL2--VZeCr3J.js} +1 -1
  21. package/web/assets/channel-BjjvW3d2.js +1 -0
  22. package/web/assets/{chunk-4HAMMTFA-Dp_qmfIt.js → chunk-4HAMMTFA-BQNwB4-r.js} +1 -1
  23. package/web/assets/{chunk-75Z2AOVW-LIBFXHNW.js → chunk-75Z2AOVW-DaDi66X7.js} +1 -1
  24. package/web/assets/{chunk-DU6HZSFF-CHYFDPen.js → chunk-DU6HZSFF-Cz_K-PAg.js} +1 -1
  25. package/web/assets/{chunk-F27PBJKO-CW0w3qr4.js → chunk-F27PBJKO-CiA_VeMu.js} +1 -1
  26. package/web/assets/{chunk-GMAD6QVW-D70RC31m.js → chunk-GMAD6QVW-C6DIMkbp.js} +1 -1
  27. package/web/assets/{chunk-GVQU2GXP-BoMG4q_g.js → chunk-GVQU2GXP-ChQklEPw.js} +1 -1
  28. package/web/assets/{chunk-IMKFNOWR-DGuQZMQs.js → chunk-IMKFNOWR-vESMkHnU.js} +1 -1
  29. package/web/assets/{chunk-L3NEJ4N5-CPcSUaRw.js → chunk-L3NEJ4N5-DyRydj9b.js} +1 -1
  30. package/web/assets/{chunk-OSK3NFVY-QO8XYQf4.js → chunk-OSK3NFVY-uLvbjNZD.js} +1 -1
  31. package/web/assets/{chunk-P2QGCYS3-NmNC30bu.js → chunk-P2QGCYS3-C0ZbEWz8.js} +1 -1
  32. package/web/assets/{chunk-POPQ4Y6H-Bn53i2aN.js → chunk-POPQ4Y6H-UtEO9cBl.js} +1 -1
  33. package/web/assets/{chunk-PWAF6VOD-Ckjg0E7O.js → chunk-PWAF6VOD-BCKRGdT1.js} +1 -1
  34. package/web/assets/{chunk-SHT3W25Y-AZaqIQ5z.js → chunk-SHT3W25Y-D5xRgo_J.js} +1 -1
  35. package/web/assets/{chunk-SVP7TREG-n1FtRhfL.js → chunk-SVP7TREG-BBN7bkx7.js} +1 -1
  36. package/web/assets/{chunk-TICWLB2K-D-7UBqOb.js → chunk-TICWLB2K-D8rfNasl.js} +1 -1
  37. package/web/assets/classDiagram-ZZMXUADV--z5hYE5X.js +1 -0
  38. package/web/assets/classDiagram-v2-VYDZK3BY--z5hYE5X.js +1 -0
  39. package/web/assets/{cynefin-OW5HDTMX-CdsOpojj.js → cynefin-OW5HDTMX-B7BMuYuW.js} +1 -1
  40. package/web/assets/{cynefinDiagram-5FMLGOSQ-DHMkAw-p.js → cynefinDiagram-5FMLGOSQ-BA7LlBfv.js} +1 -1
  41. package/web/assets/{dagre-GXQ25YYZ-CGgIWy71.js → dagre-GXQ25YYZ-D9BwYxSN.js} +1 -1
  42. package/web/assets/{diagram-S7CK7UJ4-CAkU32pw.js → diagram-S7CK7UJ4-Ci4j13kz.js} +1 -1
  43. package/web/assets/{diagram-UQ7AKVKN-B9BI2i8L.js → diagram-UQ7AKVKN-DrMYnLGf.js} +1 -1
  44. package/web/assets/{diagram-VSXAHHWV-D_isYzYo.js → diagram-VSXAHHWV-Jk90pm4l.js} +1 -1
  45. package/web/assets/{diagram-VX7I27RA-BmqvhWpM.js → diagram-VX7I27RA-B_LPKIbj.js} +1 -1
  46. package/web/assets/{diagram-Z3DM3KII-Cn2lnK6g.js → diagram-Z3DM3KII-D-54FSSp.js} +1 -1
  47. package/web/assets/{ebnfDiagram-PWID7BFC-CA6jD4w3.js → ebnfDiagram-PWID7BFC-85GLU1Qx.js} +1 -1
  48. package/web/assets/{erDiagram-RLTQ6QDP-D8vwiypv.js → erDiagram-RLTQ6QDP-Cjqf9w-j.js} +1 -1
  49. package/web/assets/eventmodeling-NTZA5JFV-Bx1t8Ws7.js +1 -0
  50. package/web/assets/flowDiagram-HODETNUW-DTcYWz1a.js +1 -0
  51. package/web/assets/{ganttDiagram-EL5Y4UJY-D9TnkbWD.js → ganttDiagram-EL5Y4UJY-CllFDNIA.js} +1 -1
  52. package/web/assets/{gitGraph-4MIJSDKK-DmkQMKd4.js → gitGraph-4MIJSDKK-JbabbGQn.js} +1 -1
  53. package/web/assets/{gitGraphDiagram-WWUBYQGX-k633BXm0.js → gitGraphDiagram-WWUBYQGX-Bs0agVOW.js} +1 -1
  54. package/web/assets/{index-Dbc5eEmV.js → index-D1m9cVXn.js} +2 -2
  55. package/web/assets/index-Hl-z7c8G.css +1 -0
  56. package/web/assets/{info-A6RAGUB7-DIrl4iZa.js → info-A6RAGUB7-DoJ_vMJT.js} +1 -1
  57. package/web/assets/{infoDiagram-27XIBGKW-hNpj-RTV.js → infoDiagram-27XIBGKW-DIRLvOQG.js} +1 -1
  58. package/web/assets/{ishikawaDiagram-5VMMS53U-B5R97iuH.js → ishikawaDiagram-5VMMS53U-CHKavP5c.js} +1 -1
  59. package/web/assets/{journeyDiagram-3NMN7TZE-_zmo4-I1.js → journeyDiagram-3NMN7TZE-1ayVb9xW.js} +1 -1
  60. package/web/assets/{kanban-definition-UXKFOSKX-CFaqoxxH.js → kanban-definition-UXKFOSKX-BpuMJyfm.js} +1 -1
  61. package/web/assets/{line-DvqHwrR2.js → line--EI5rOWQ.js} +1 -1
  62. package/web/assets/{mermaid-parser.core-B79mvmV7.js → mermaid-parser.core-CexICl1c.js} +3 -3
  63. package/web/assets/{mermaid.core-fLmZLs32.js → mermaid.core-pumIMO4c.js} +4 -4
  64. package/web/assets/{mindmap-definition-YA3MSWOX-BUe0_gH7.js → mindmap-definition-YA3MSWOX-DFz_-UZn.js} +1 -1
  65. package/web/assets/mount--plvVxhF.js +27 -0
  66. package/web/assets/{packet-AYTQ26CC-Aj0EzH9_.js → packet-AYTQ26CC-DeYOUNLB.js} +1 -1
  67. package/web/assets/{pegDiagram-XKGWAZYB-CKbgKsPZ.js → pegDiagram-XKGWAZYB-BDVqO1lR.js} +1 -1
  68. package/web/assets/{pie-WAS4IAKB-DVr7PuUh.js → pie-WAS4IAKB-rSl6g03L.js} +1 -1
  69. package/web/assets/{pieDiagram-E7YTZNPT-CK_U235v.js → pieDiagram-E7YTZNPT-RE_qyV86.js} +1 -1
  70. package/web/assets/{quadrantDiagram-AXDQQJYC-DlRY8SNG.js → quadrantDiagram-AXDQQJYC-DvgsYDiT.js} +1 -1
  71. package/web/assets/{radar-RG4KPBEZ-C29uvfrD.js → radar-RG4KPBEZ-BluY7HPZ.js} +1 -1
  72. package/web/assets/{railroad-74A4TZTK-Bsl6sVlq.js → railroad-74A4TZTK-CI8HoIPX.js} +1 -1
  73. package/web/assets/railroad-abnf-HS5TGJTU-CW6o7azB.js +1 -0
  74. package/web/assets/railroad-ebnf-LZEXJU2U-iCDWKNcB.js +1 -0
  75. package/web/assets/railroad-peg-WCYAUIDC-B0qaz-kW.js +1 -0
  76. package/web/assets/{railroadDiagram-O6MQD6OU-2yZrB_cl.js → railroadDiagram-O6MQD6OU-BMjqzW-y.js} +1 -1
  77. package/web/assets/{requirementDiagram-BXWQKSXE-BGne8p4g.js → requirementDiagram-BXWQKSXE-Dw7cOOR8.js} +1 -1
  78. package/web/assets/{sankeyDiagram-P5KCCOFB-ChE-PJgx.js → sankeyDiagram-P5KCCOFB-pOTF6bRz.js} +1 -1
  79. package/web/assets/{sequenceDiagram-WJ2MYXX4-Dmr-MzeW.js → sequenceDiagram-WJ2MYXX4-ClQQJtjY.js} +1 -1
  80. package/web/assets/{settingsRoute-C5b_th3F.js → settingsRoute-STSKsBxR.js} +1 -1
  81. package/web/assets/{stateDiagram-D77RDMKH-EMQqubpF.js → stateDiagram-D77RDMKH-BLbq24-r.js} +1 -1
  82. package/web/assets/stateDiagram-v2-MP3YSRHH-QAOfSNO2.js +1 -0
  83. package/web/assets/{swimlanes-42K2YHIH-DshnggZ3.js → swimlanes-42K2YHIH-Bvyc9d-X.js} +1 -1
  84. package/web/assets/swimlanesDiagram-VR7AAH4N-D0Q3jk4u.js +8 -0
  85. package/web/assets/{timeline-definition-24CTP7MA-ZwyROueZ.js → timeline-definition-24CTP7MA-D19M8pqI.js} +1 -1
  86. package/web/assets/{treeView-Q6P3EWNA-Dcc62DZb.js → treeView-Q6P3EWNA-C0O23uvm.js} +1 -1
  87. package/web/assets/{treemap-WGGIJYW6-BklfTxbU.js → treemap-WGGIJYW6-Cqe8MA6f.js} +1 -1
  88. package/web/assets/{useStore-COKqKujw.js → useStore-7Pbk_2aC.js} +1 -1
  89. package/web/assets/{vennDiagram-4TSXK5OY-DbYhZINT.js → vennDiagram-4TSXK5OY-D40HW3dD.js} +1 -1
  90. package/web/assets/{wardley-WFR3VGLG-9C_Tmqe6.js → wardley-WFR3VGLG-D4IGg7gO.js} +1 -1
  91. package/web/assets/{wardleyDiagram-VM6X3IG4-DxjFuJhq.js → wardleyDiagram-VM6X3IG4-BRIm2egQ.js} +1 -1
  92. package/web/assets/{xychartDiagram-S5SC5T6Z-Cy3VPR-8.js → xychartDiagram-S5SC5T6Z-xMbNSxWK.js} +1 -1
  93. package/web/index.html +2 -2
  94. package/web/sw.js +1 -1
  95. package/web/assets/AutomationsView-vRpxGraI.js +0 -51
  96. package/web/assets/LibraryView-B-X0m4v6.js +0 -2
  97. package/web/assets/Settings-BJPZ_q9L.js +0 -10
  98. package/web/assets/architecture-7GRP2DOG-Kx7T2LBQ.js +0 -1
  99. package/web/assets/channel-hv9z71s4.js +0 -1
  100. package/web/assets/classDiagram-ZZMXUADV-kuXKTzrq.js +0 -1
  101. package/web/assets/classDiagram-v2-VYDZK3BY-kuXKTzrq.js +0 -1
  102. package/web/assets/eventmodeling-NTZA5JFV-D2DyVOaq.js +0 -1
  103. package/web/assets/flowDiagram-HODETNUW-B_K3Zdwc.js +0 -1
  104. package/web/assets/index-DPD-O0uT.css +0 -1
  105. package/web/assets/mount-CRyC02I5.js +0 -27
  106. package/web/assets/railroad-abnf-HS5TGJTU-D2U1pfm4.js +0 -1
  107. package/web/assets/railroad-ebnf-LZEXJU2U-Dvnc21w0.js +0 -1
  108. package/web/assets/railroad-peg-WCYAUIDC-CFx-oLDW.js +0 -1
  109. package/web/assets/stateDiagram-v2-MP3YSRHH-DAbT5gGC.js +0 -1
  110. package/web/assets/swimlanesDiagram-VR7AAH4N-B1V7grs-.js +0 -8
package/bin/bivy.mjs CHANGED
@@ -2691,7 +2691,14 @@ blocked, or need the user to look at something. --json prints
2691
2691
  const body = await res.json().catch(() => ({}));
2692
2692
  if (!res.ok) return fail(sessionHttpError("Notify", res.status, body));
2693
2693
  if (json) { console.log(JSON.stringify(body)); return; }
2694
- const pushed = { sent: "and pushed to the user's devices", user_watching: "(no push: the user has the app open; add --urgent to push anyway)", rate_limited: "(no push: this session pushed less than a minute ago)", unavailable: "(no push: this machine isn't signed in to a Bivy account)" }[body.push] ?? "";
2694
+ const noPush = { user_watching: "the user has the app open; add --urgent to push anyway", rate_limited: "this session pushed less than a minute ago", unavailable: "this machine isn't signed in to a Bivy account" }[body.push];
2695
+ // A terminal run (`bivy run`) has no chat to hold a card: the push is the notice.
2696
+ if (body.posted === false) {
2697
+ if (body.push === "sent") console.log(c.green("Pushed to the user's devices."));
2698
+ else console.log(c.yellow(`Not delivered: a terminal run has no chat to post in, and no push was sent (${noPush ?? body.push}).`));
2699
+ return;
2700
+ }
2701
+ const pushed = body.push === "sent" ? "and pushed to the user's devices" : noPush ? `(no push: ${noPush})` : "";
2695
2702
  console.log(c.green(`Posted in the chat ${pushed}.`.replace(" .", ".")));
2696
2703
  }
2697
2704
 
@@ -284,6 +284,54 @@ input, textarea, select { font-family: inherit; font-size: 16px; }
284
284
  .focus-view-btn[aria-pressed="true"] { color: var(--accent); background: var(--accent-soft); }
285
285
  .focus-view-label { font-size: var(--text-xs); font-weight: var(--weight-semibold); }
286
286
 
287
+ /* ---- Side pane (wide screens): the session's changes, apps and artifacts
288
+ beside the chat instead of in a sheet over it. Same bodies as the sheets
289
+ (components/Panel.tsx), docked; below the breakpoint in App.tsx the pane
290
+ is never rendered and the sheets are used. */
291
+ .app.has-pane { grid-template-columns: minmax(300px, 22vw) minmax(0, 1fr) clamp(380px, 32vw, 600px); }
292
+ .side-pane {
293
+ display: flex; flex-direction: column; min-width: 0; min-height: 0; height: var(--app-h, 100dvh);
294
+ border-left: 1px solid var(--line); background: var(--surface);
295
+ }
296
+ .side-pane-head {
297
+ flex: none; display: flex; align-items: center; justify-content: space-between; gap: var(--space-2);
298
+ min-height: var(--topbar-h, 57px); padding: var(--space-2) var(--space-2) var(--space-2) var(--space-3);
299
+ border-bottom: 1px solid var(--line);
300
+ }
301
+ .side-pane-tabs { min-width: 0; overflow-x: auto; }
302
+ .side-pane-tab { display: inline-flex; align-items: center; gap: var(--space-1); }
303
+ .seg-count { color: var(--muted); font-variant-numeric: tabular-nums; font-weight: var(--weight-normal); }
304
+ .side-pane-close { flex: none; color: var(--muted); }
305
+ .side-pane-close:hover { color: var(--ink); }
306
+ .side-pane-body { flex: 1; min-height: 0; display: flex; flex-direction: column; }
307
+ /* A docked Panel: the sheet's title row becomes a quiet toolbar, its content
308
+ the pane's own scroller. */
309
+ .pane-panel { flex: 1; min-height: 0; display: flex; flex-direction: column; }
310
+ .pane-panel-head {
311
+ flex: none; display: flex; align-items: center; gap: var(--space-2);
312
+ min-height: 44px; padding: var(--space-1) var(--space-2) var(--space-1) var(--space-3);
313
+ border-bottom: 1px solid var(--line);
314
+ }
315
+ .pane-panel-title {
316
+ flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
317
+ font-size: var(--text-sm); font-weight: var(--weight-semibold);
318
+ }
319
+ .pane-panel-content { flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; padding: var(--space-3); overscroll-behavior: contain; }
320
+ .pane-panel[data-size="full"] .pane-panel-content { padding: 0; overflow: hidden; }
321
+ .pane-panel[data-size="full"] .preview-peek { border-radius: 0; }
322
+ /* The pane's tab already says Terminal: keep the head to the pane toolbar's
323
+ height, with the live status and the actions that matter beside a chat. */
324
+ .side-pane .term-head { min-height: 44px; padding: var(--space-1) var(--space-2) var(--space-1) var(--space-3); gap: var(--space-2); }
325
+ .side-pane .term-title-label, .side-pane .term-zoom { display: none; }
326
+ .side-pane .term-title { flex: none; }
327
+ .side-pane .term-status { margin-left: 0; }
328
+ /* Narrow screens: Changes in the session view switch covers the column's
329
+ view, full height. Everything else in the column stays mounted, hidden. */
330
+ .main.changes-section > :not(.topbar):not(.banner):not(.main-changes) { display: none; }
331
+ .main-changes { flex: 1; min-height: 0; display: flex; flex-direction: column; background: var(--surface); }
332
+ .main-changes .pane-panel-content { padding-bottom: max(var(--space-3), env(safe-area-inset-bottom)); }
333
+ .topbar-actions .side-pane-toggle[aria-pressed="true"] { color: var(--accent); background: var(--accent-soft); }
334
+
287
335
  /* ---- Header session actions (dot-menu: rename / open PR / delete) ---- */
288
336
  .session-actions-wrap { position: relative; flex: none; }
289
337
  .session-actions-btn { border: 0; background: transparent; color: var(--ink); font-size: 20px; line-height: 1; padding: 4px 8px; border-radius: var(--radius-sm); }
@@ -927,7 +975,8 @@ button.sheet-action:last-child { border-bottom: 0; }
927
975
  would occupy and centers a compact card; the buttons are the old lock
928
976
  banner's escape hatches (open the terminal / take over in chat) at a more
929
977
  deliberate, explanatory scale. */
930
- .tui-locked { flex: 1; min-height: 0; display: flex; align-items: center; justify-content: center; padding: 24px 16px; overflow-y: auto; }
978
+ .tui-locked { flex: 1; min-height: 0; display: flex; flex-direction: column; align-items: center; justify-content: safe center; gap: var(--space-4); padding: 24px 16px; overflow-y: auto; }
979
+ .tui-locked-attention { width: min(440px, 100%); }
931
980
  .tui-locked-card { width: min(440px, 100%); display: flex; flex-direction: column; align-items: center; gap: 10px; text-align: center; }
932
981
  .tui-locked-icon { display: inline-flex; align-items: center; justify-content: center; width: 52px; height: 52px; border-radius: var(--radius-md); background: color-mix(in srgb, var(--accent) 12%, var(--surface-2)); color: var(--accent); }
933
982
  .tui-locked-title { margin: 2px 0 0; font-size: 18px; font-weight: 700; }
@@ -2610,9 +2659,12 @@ textarea.picker-search { resize: vertical; font-family: ui-monospace, monospace;
2610
2659
  .term-head-actions {
2611
2660
  display: flex; align-items: center; gap: 8px;
2612
2661
  flex: 1 1 auto; min-width: 0; overflow-x: auto;
2613
- justify-content: flex-end;
2662
+ /* Right-aligned by an auto margin, not justify-content: flex-end, which
2663
+ would push overflow off the left edge where it can't be scrolled to. */
2664
+ justify-content: flex-start;
2614
2665
  scrollbar-width: none; -webkit-overflow-scrolling: touch;
2615
2666
  }
2667
+ .term-head-actions > :first-child { margin-left: auto; }
2616
2668
  .term-head-actions::-webkit-scrollbar { display: none; }
2617
2669
  .term-head-actions > * { flex: 0 0 auto; }
2618
2670
  .term-attach-wrap { position: relative; }
@@ -17,12 +17,10 @@ export function createSessionControlCommands(ports) {
17
17
  ctx.reply({ type: "session.resume.result", ok: true });
18
18
  },
19
19
  "session.question.answer"(input, ctx) {
20
- const session = ports.resolve(input.sessionId);
21
20
  const requestId = String(input.requestId ?? "");
22
- if (!session || !requestId) {
21
+ if (!requestId || !ports.answer(input.sessionId, requestId, input)) {
23
22
  return ctx.reply({ type: "session.question.answer.error", httpStatus: 404, error: "No matching session/question" });
24
23
  }
25
- ports.answer(session, requestId, input);
26
24
  ctx.reply({ type: "session.question.answer.result", ok: true, requestId });
27
25
  },
28
26
  };
package/dist/server.js CHANGED
@@ -476,7 +476,7 @@ questionManager.onRequest((request) => {
476
476
  sessionId: request.sessionId,
477
477
  attentionId: request.id,
478
478
  title: "Bivy needs your input",
479
- body: `${sessionNotifyLabel(resolveSession(request.sessionId))} is asking a question — tap to answer.`,
479
+ body: `${sessionNotifyLabel(resolveSession(request.sessionId), runTerms.liveRun(request.sessionId)?.name)} is asking a question — tap to answer.`,
480
480
  });
481
481
  });
482
482
  questionManager.onResolved((request) => {
@@ -2330,7 +2330,7 @@ function currentAccess() {
2330
2330
  });
2331
2331
  }
2332
2332
  const RELAY_COMMANDS = {
2333
- ...createAppCommands(appService, (id) => { const record = resolveSession(id); return record ? harnessDirFor(record) : undefined; }, (app) => {
2333
+ ...createAppCommands(appService, (id) => { const record = resolveSession(id); return record ? harnessDirFor(record) : runTerms.liveRun(id)?.workspace; }, (app) => {
2334
2334
  const record = resolveSession(app.sessionId);
2335
2335
  if (!record)
2336
2336
  return;
@@ -2389,7 +2389,7 @@ const RELAY_COMMANDS = {
2389
2389
  resolve: (sessionId) => resolveSession(sessionId),
2390
2390
  pause: pauseSession,
2391
2391
  resume: resumeSession,
2392
- answer: (record, requestId, input) => answerSessionQuestion(record, requestId, input),
2392
+ answer: (sessionId, requestId, input) => answerSessionQuestion(sessionId, requestId, input),
2393
2393
  }),
2394
2394
  // Session fork/move command cluster, extracted to controllers/fork-commands.ts
2395
2395
  // (server.ts decomposition). Late-bound singletons (forkStandUp/forkRetire/
@@ -6405,9 +6405,13 @@ function recordMcpEvent(sessionId, event) {
6405
6405
  * out, or aborted) — same tolerance as resolveApproval. The resolved broadcast
6406
6406
  * is emitted from questionManager.onResolved, not here, so it fires exactly
6407
6407
  * when the question actually settles (never for an already-discarded id). */
6408
- function answerSessionQuestion(_record, requestId, msg) {
6408
+ /** A question's session may be an open chat or a live `bivy run` (`bivy ask`). */
6409
+ function answerSessionQuestion(sessionId, requestId, msg) {
6410
+ if (!resolveSession(sessionId) && !runTerms.liveRun(typeof sessionId === "string" ? sessionId : ""))
6411
+ return false;
6409
6412
  const answers = msg.answers && typeof msg.answers === "object" ? msg.answers : {};
6410
6413
  questionManager.resolve(requestId, msg.cancelled ? { behavior: "cancelled" } : { behavior: "completed", answers });
6414
+ return true;
6411
6415
  }
6412
6416
  /** Agent id requested by a client command (`agent` or legacy `runtimeId`); empty = default. */
6413
6417
  function agentFrom(msg) {
@@ -9211,6 +9215,7 @@ const runTerms = createRunTerminals({
9211
9215
  sessionBusy: (record) => sessionBusy(record),
9212
9216
  sessionTerminalsRecord: (sessionId, val) => sessionTerminals.record(sessionId, val),
9213
9217
  sessionTerminalsForget: (sessionId) => sessionTerminals.forget(sessionId),
9218
+ replayPendingInteractions: (sessionId) => replayPendingInteractions(sessionId),
9214
9219
  upsertSessionMetadata: (patch) => {
9215
9220
  const input = patch;
9216
9221
  // A run continuing a saved session (`bivy resume`) only moves its status;
@@ -11663,11 +11668,9 @@ app.post("/api/session/:id/attach", (req, res) => {
11663
11668
  // `bivy context`: what an agent in this session needs to orient itself.
11664
11669
  app.get("/api/session/:id/context", (req, res) => {
11665
11670
  const record = openSessions.get(String(req.params.id));
11666
- if (!record)
11667
- return res.status(404).json({ error: "Session not found" });
11668
- const { apps, previewAvailable } = appService.list(record.id);
11669
- res.json(buildAgentContext({
11670
- session: {
11671
+ const run = record ? undefined : runTerms.liveRun(String(req.params.id));
11672
+ const session = record
11673
+ ? {
11671
11674
  id: record.id,
11672
11675
  name: record.session.getName() || undefined,
11673
11676
  agent: record.runtimeId,
@@ -11678,9 +11681,16 @@ app.get("/api/session/:id/context", (req, res) => {
11678
11681
  forkedFrom: record.forkedFrom,
11679
11682
  delegatedFrom: record.delegatedFrom?.sessionId,
11680
11683
  busy: sessionBusy(record),
11681
- },
11684
+ }
11685
+ // A `bivy run` terminal: the agent drives it from its own TUI, so it's live.
11686
+ : run && { id: run.sessionId, name: run.name, agent: run.agent ?? "run", workspace: run.workspace, busy: true };
11687
+ if (!session)
11688
+ return res.status(404).json({ error: "Session not found" });
11689
+ const { apps, previewAvailable } = appService.list(session.id);
11690
+ res.json(buildAgentContext({
11691
+ session,
11682
11692
  machine: { name: identity.name, platform: process.platform, arch: process.arch },
11683
- presence: presenceBook.get(record.id),
11693
+ presence: presenceBook.get(session.id),
11684
11694
  userConnected: clients.size > 0 || (relay?.clientCount ?? 0) > 0,
11685
11695
  apps,
11686
11696
  previewAvailable,
@@ -11688,44 +11698,49 @@ app.get("/api/session/:id/context", (req, res) => {
11688
11698
  });
11689
11699
  // `bivy notify "<message>"`: a card in the chat, and a push naming the session
11690
11700
  // (never the text) when nobody has the app open, or when it's urgent. At most
11691
- // one push a minute per session; later notices still post a card.
11701
+ // one push a minute per session; later notices still post a card. A `bivy run`
11702
+ // terminal has no chat to hold the card, so its notice is the push alone.
11692
11703
  const lastNotifyPush = new Map();
11693
11704
  app.post("/api/session/:id/notify", (req, res) => {
11694
11705
  const record = openSessions.get(String(req.params.id));
11695
- if (!record)
11706
+ const run = record ? undefined : runTerms.liveRun(String(req.params.id));
11707
+ const sessionId = record?.id ?? run?.sessionId;
11708
+ if (!sessionId)
11696
11709
  return res.status(404).json({ error: "Session not found" });
11697
11710
  const text = typeof req.body?.text === "string" ? req.body.text.trim() : "";
11698
11711
  const notice = { id: `notice-${randomBytes(8).toString("hex")}`, text, ...(req.body?.urgent === true ? { urgent: true } : {}) };
11699
11712
  if (!isAgentNotice(notice))
11700
11713
  return res.status(400).json({ error: `A notice needs text (up to ${MAX_NOTICE_TEXT} characters).` });
11701
- eventLog.appendNotice(record.id, { afterMessageCount: record.session.getMessages().length, notice });
11702
- broadcast(stampSessionEvent({ type: "session.event", sessionId: record.id, event: { type: "notice", id: notice.id, notice } }));
11703
- const watching = clients.size > 0 || (relay?.clientCount ?? 0) > 0;
11704
- const push = noticePush({ urgent: notice.urgent, userWatching: watching, lastPushAt: lastNotifyPush.get(record.id), now: Date.now(), pushConfigured: Boolean(sessionAdvertiseTarget) });
11714
+ if (record) {
11715
+ eventLog.appendNotice(record.id, { afterMessageCount: record.session.getMessages().length, notice });
11716
+ broadcast(stampSessionEvent({ type: "session.event", sessionId: record.id, event: { type: "notice", id: notice.id, notice } }));
11717
+ }
11718
+ const watching = Boolean(record) && (clients.size > 0 || (relay?.clientCount ?? 0) > 0);
11719
+ const push = noticePush({ urgent: notice.urgent, userWatching: watching, lastPushAt: lastNotifyPush.get(sessionId), now: Date.now(), pushConfigured: Boolean(sessionAdvertiseTarget) });
11705
11720
  if (push === "sent") {
11706
- lastNotifyPush.set(record.id, Date.now());
11721
+ lastNotifyPush.set(sessionId, Date.now());
11707
11722
  void sendNotificationHint({
11708
11723
  kind: "agent_notice",
11709
- sessionId: record.id,
11710
- targetSessionId: record.id,
11711
- title: sessionNotifyLabel(record),
11724
+ sessionId,
11725
+ targetSessionId: sessionId,
11726
+ title: sessionNotifyLabel(record, run?.name || "A terminal session"),
11712
11727
  body: "Has a message for you — tap to read it.",
11713
11728
  });
11714
11729
  }
11715
- res.json({ ok: true, id: notice.id, push, userWatching: watching });
11730
+ res.json({ ok: true, id: notice.id, posted: Boolean(record), push, userWatching: watching });
11716
11731
  });
11717
11732
  // `bivy ask`: the question card and "needs your input" push, for any agent.
11718
11733
  // Returns at once with an id; `/wait` blocks up to 240s per call (under common
11719
11734
  // HTTP client timeouts), so the CLI loops until the user answers.
11720
11735
  app.post("/api/session/:id/ask", (req, res) => {
11721
- const record = openSessions.get(String(req.params.id));
11722
- if (!record)
11736
+ const sessionId = openSessions.get(String(req.params.id))?.id ?? runTerms.liveRun(String(req.params.id))?.sessionId;
11737
+ if (!sessionId)
11723
11738
  return res.status(404).json({ error: "Session not found" });
11724
11739
  const questions = askQuestionsFrom(req.body?.questions);
11725
11740
  if (typeof questions === "string")
11726
11741
  return res.status(400).json({ error: questions });
11727
11742
  const timeoutSeconds = Number(req.body?.timeoutSeconds);
11728
- res.status(201).json(agentQuestions.ask(record.id, questions, Number.isFinite(timeoutSeconds) && timeoutSeconds > 0 ? timeoutSeconds * 1000 : 10 * 60 * 1000));
11743
+ res.status(201).json(agentQuestions.ask(sessionId, questions, Number.isFinite(timeoutSeconds) && timeoutSeconds > 0 ? timeoutSeconds * 1000 : 10 * 60 * 1000));
11729
11744
  });
11730
11745
  app.get("/api/session/:id/ask/:askId", (req, res) => {
11731
11746
  const result = agentQuestions.get(String(req.params.id), String(req.params.askId));
@@ -14,6 +14,7 @@
14
14
  // timer maps, the agent lookup tables) moves here. Credential projection follows
15
15
  // the unified agent-integration model (#433): only an integration that declares
16
16
  // bivy/mixed auth receives Bivy vault projections.
17
+ import { randomUUID } from "node:crypto";
17
18
  import { provisionAgentRun } from "../runtime/credential-provisioning.js";
18
19
  import { ingestAgentCredentials } from "../runtime/credential-ingest.js";
19
20
  import { discoverCodexSessionForCwd } from "../runtime/codex-sessions.js";
@@ -21,6 +22,7 @@ import { discoverGrokSessionForCwd } from "../runtime/grok-sessions.js";
21
22
  import { discoverGeminiFamilySessionForCwd } from "../runtime/gemini-sessions.js";
22
23
  import { discoverOpenCodeSessionForCwd } from "../runtime/opencode-sessions.js";
23
24
  import { discoverPiSessionForCwd } from "../runtime/pi-session-discovery.js";
25
+ import { bivySessionEnv } from "../runtime/session-env.js";
24
26
  import { listMultiplexerSessions, attachCommand } from "../multiplexer.js";
25
27
  // Agents whose pinned run-terminal session can be reopened as a governed chat.
26
28
  const TAKEOVER_RUNTIME_BY_AGENT = {
@@ -115,6 +117,13 @@ export function createRunTerminals(deps) {
115
117
  deps.broadcastTuiState(record.id, false);
116
118
  void deps.refreshRecordAfterTui(record);
117
119
  }
120
+ function liveRun(ref) {
121
+ if (!ref)
122
+ return undefined;
123
+ const run = terminals.list((m) => m.kind === "run" && !m.mux)
124
+ .find((t) => runTerminals.has(t.id) && (t.id === ref || t.meta.sessionId === ref));
125
+ return run && { termId: run.id, sessionId: run.meta.sessionId ?? run.id, workspace: run.workspace, name: run.meta.name, agent: run.meta.agent };
126
+ }
118
127
  function adoptLiveRun(record) {
119
128
  if (record.tuiTermId)
120
129
  return;
@@ -287,12 +296,19 @@ export function createRunTerminals(deps) {
287
296
  const name = explicitName || (spec.mux ? undefined : defaultRunName(spec.agent, commandLine, workspace));
288
297
  const notifiable = !spec.mux;
289
298
  const createdAt = Date.now();
299
+ // The agent inside gets the same session env a chat session's agent does, so
300
+ // `bivy notify`/`ask`/`app` reach this run: under its pinned session id, else
301
+ // under the terminal's own id (see liveRun). A mux attach joins a shell that
302
+ // already has its environment.
303
+ const termId = `term-${randomUUID()}`;
304
+ const sessionEnv = spec.mux ? {} : bivySessionEnv(spec.sessionId ?? termId);
290
305
  try {
291
306
  const id = terminals.open({
307
+ id: termId,
292
308
  workspace,
293
309
  command: spec.command,
294
310
  args: spec.args,
295
- env: { ...credentialEnv, ...spec.env },
311
+ env: { ...credentialEnv, ...sessionEnv, ...spec.env },
296
312
  cols: spec.cols,
297
313
  rows: spec.rows,
298
314
  clientId: spec.clientId,
@@ -466,7 +482,15 @@ export function createRunTerminals(deps) {
466
482
  function handleTerminalMessage(msg, emit, owned, clientId, viewRun) {
467
483
  switch (msg.kind) {
468
484
  case "terminal.list":
469
- void runTerminalList().then((listed) => emit({ type: "terminal.list", terminals: listed }));
485
+ // Every client asks on connect: also re-send what live runs are waiting
486
+ // on, as a chat's history request does for its own cards.
487
+ void runTerminalList().then((listed) => {
488
+ emit({ type: "terminal.list", terminals: listed });
489
+ for (const t of listed) {
490
+ if (!t.mux)
491
+ deps.replayPendingInteractions(t.sessionId ?? t.termId);
492
+ }
493
+ });
470
494
  return true;
471
495
  case "terminal.takeover": {
472
496
  const takeoverTermId = typeof msg.termId === "string" && msg.termId ? msg.termId : undefined;
@@ -655,5 +679,5 @@ export function createRunTerminals(deps) {
655
679
  return false;
656
680
  }
657
681
  }
658
- return { runTerminalList, openRunTerminal, takeoverRunTerminal, handleTerminalMessage, addRunViewer, dropRunViewer, hasRunTerminal, hasLiveRunForSession, adoptLiveRun };
682
+ return { runTerminalList, openRunTerminal, takeoverRunTerminal, handleTerminalMessage, addRunViewer, dropRunViewer, hasRunTerminal, hasLiveRunForSession, adoptLiveRun, liveRun };
659
683
  }
package/dist/terminal.js CHANGED
@@ -77,7 +77,7 @@ export class TerminalManager {
77
77
  return this.terminals.size;
78
78
  }
79
79
  open(options) {
80
- const id = `term-${randomUUID()}`;
80
+ const id = options.id ?? `term-${randomUUID()}`;
81
81
  const shell = options.command || defaultShell();
82
82
  const shellArgs = options.command ? (options.args ?? []) : [];
83
83
  // Ensure a UTF-8 locale so TUIs (e.g. Claude Code) render box-drawing and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bivy/bivy",
3
- "version": "0.20.6-staging.1",
3
+ "version": "0.20.6-staging.3",
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.",