@bivy/bivy 0.17.3-staging.17 → 0.17.3-staging.19

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.
@@ -1451,7 +1451,7 @@ button.sheet-action:last-child { border-bottom: 0; }
1451
1451
  .library-tile-file { display: flex; flex-direction: column; align-items: center; gap: var(--space-1); }
1452
1452
  .library-tile-ext { font-size: var(--text-xs); font-weight: var(--weight-semibold); letter-spacing: 0.04em; }
1453
1453
  .library-tile-caption { display: flex; flex-direction: column; gap: 2px; min-width: 0; padding: 0 2px; }
1454
- .library-tile-name, .library-row-name {
1454
+ .library-tile-name {
1455
1455
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
1456
1456
  font-size: var(--text-sm); font-weight: var(--weight-medium); color: var(--ink);
1457
1457
  }
@@ -1464,17 +1464,6 @@ button.sheet-action:last-child { border-bottom: 0; }
1464
1464
  .library-source:hover { color: var(--ink); }
1465
1465
  .library-source svg, .library-source-when { flex: none; }
1466
1466
  .library-source-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1467
- .library-list { list-style: none; margin: 0; padding: 0; }
1468
- .library-row { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3) 0; border-bottom: 1px solid var(--line); }
1469
- .library-row:last-child { border-bottom: 0; }
1470
- .library-row-icon {
1471
- flex: none; width: 40px; height: 40px; display: grid; place-items: center;
1472
- border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface-2);
1473
- color: var(--ink); font-weight: var(--weight-semibold);
1474
- }
1475
- .library-row-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
1476
- .library-row-name { font-size: var(--text-base); }
1477
- .library-row-kind { color: var(--muted); font-weight: var(--weight-normal); }
1478
1467
  @media (min-width: 640px) {
1479
1468
  .library-grid { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: var(--space-6) var(--space-4); }
1480
1469
  }
@@ -3034,18 +3023,53 @@ select.picker-search { appearance: auto; }
3034
3023
 
3035
3024
  /* Artifacts sheet (components/ArtifactsSheet.tsx) — agent-sent attachments,
3036
3025
  user uploads, and resolved inline images grouped by kind. */
3037
- .app-views-heading { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); }
3038
- .app-views-heading .artifact-name, .app-message .artifact-name { white-space: normal; overflow-wrap: anywhere; }
3039
- .app-view-row { flex-wrap: wrap; }
3040
- .app-views-heading > .btn, .app-view-actions .btn { min-block-size: var(--space-7); flex-shrink: 0; }
3041
- .app-view-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--space-2); margin-inline-start: auto; }
3042
- .app-view-notice { display: flex; flex-direction: column; gap: var(--space-2); flex-basis: 100%; min-width: 0; }
3043
- .app-view-notes { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-1); font-size: var(--text-sm); overflow-wrap: anywhere; }
3026
+ /* An app, a view of it, or a server that could become one (AppRow.tsx): the
3027
+ same tile, type and trailing action in the chat card, the Apps page and the
3028
+ Apps sheet. Views nest inside their app's card as small rows. */
3029
+ .apps-card { border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); overflow-wrap: anywhere; }
3030
+ .apps-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
3031
+ .app-row { display: flex; align-items: center; gap: var(--space-3); min-width: 0; padding: var(--space-3); }
3032
+ .app-tile { flex: none; display: grid; place-items: center; inline-size: 40px; block-size: 40px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface-2); color: var(--ink); font-weight: var(--weight-semibold); }
3033
+ .app-row.small .app-tile { inline-size: 36px; block-size: 36px; border-radius: var(--radius-sm); color: var(--muted); }
3034
+ .app-tile-port { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--ok); }
3035
+ .app-row-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
3036
+ .app-row-name { font-size: var(--text-base); font-weight: var(--weight-semibold); }
3037
+ .app-row.small .app-row-name { font-size: var(--text-sm); }
3038
+ .app-row-meta { color: var(--muted); font-size: var(--text-xs); line-height: var(--lh-xs); }
3039
+ .app-row-action { flex: none; display: flex; align-items: center; gap: var(--space-1); }
3040
+ .app-message { margin-block: var(--space-2); }
3041
+
3042
+ /* Apps sheet (AppsSheet.tsx): one card per app, its views nested, one primary
3043
+ action per view; secondary tools as quiet icon buttons; rare or destructive
3044
+ actions behind ⋯ (MoreMenu, the canonical .menu). Status uses .banner. */
3045
+ .apps-sheet { display: flex; flex-direction: column; gap: var(--space-4); padding: var(--space-2) var(--space-2) var(--space-4); }
3046
+ .apps-state { display: flex; align-items: center; justify-content: center; gap: var(--space-2); padding: var(--space-6) 0; color: var(--muted); font-size: var(--text-sm); }
3047
+ .apps-group { display: flex; flex-direction: column; gap: var(--space-2); }
3048
+ .apps-group-title { margin: 0; padding: 0 var(--space-1); color: var(--muted); font-size: var(--text-xs); font-weight: var(--weight-semibold); letter-spacing: 0.04em; text-transform: uppercase; }
3049
+ .apps-view { display: flex; flex-direction: column; border-top: 1px solid var(--line); }
3050
+ .apps-cmd { display: block; margin: 0 var(--space-3) var(--space-3); padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm); background: var(--surface-2); color: var(--ink); font-family: var(--font-mono); font-size: var(--text-xs); white-space: pre-wrap; overflow-wrap: anywhere; }
3051
+ .apps-view-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 0; padding: 0 var(--space-1) var(--space-1); }
3052
+ .apps-view-tools .btn { gap: 6px; padding-inline: var(--space-2); color: var(--muted); }
3053
+ .apps-view-tools .btn:hover:not(:disabled) { color: var(--ink); }
3054
+ .apps-more { position: relative; }
3055
+ .apps-view-tools .apps-more { margin-inline-start: auto; }
3056
+ .apps-more-menu { position: absolute; right: 0; top: calc(100% + var(--space-1)); z-index: var(--z-dropdown); }
3057
+ .apps-notice { display: flex; align-items: flex-start; gap: var(--space-2); margin: 0 var(--space-3) var(--space-3); padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm); background: color-mix(in srgb, var(--ok) 12%, transparent); font-size: var(--text-sm); }
3058
+ .apps-notice > svg { flex: none; margin-top: 1px; color: var(--ok); }
3059
+ .apps-notes { display: flex; flex-direction: column; gap: var(--space-2); margin: 0 var(--space-3) var(--space-3); padding-top: var(--space-3); border-top: 1px dashed var(--line); }
3060
+ .apps-notes-head { display: flex; align-items: center; gap: var(--space-2); color: var(--muted); font-size: var(--text-xs); font-weight: var(--weight-semibold); letter-spacing: 0.04em; text-transform: uppercase; }
3061
+ .apps-count { min-inline-size: 18px; padding: 0 6px; border-radius: var(--radius-full); background: var(--surface-3); color: var(--ink); text-align: center; letter-spacing: 0; }
3062
+ .apps-notes ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
3063
+ .apps-note { display: flex; flex-direction: column; gap: 2px; padding-inline-start: var(--space-3); border-inline-start: 2px solid var(--line-strong); }
3064
+ .apps-note-text { color: var(--ink); font-size: var(--text-sm); }
3065
+ .apps-notes-actions { display: flex; gap: var(--space-2); }
3066
+ .apps-empty { display: flex; flex-direction: column; align-items: center; gap: var(--space-2); padding: var(--space-6) var(--space-4); color: var(--muted); font-size: var(--text-sm); text-align: center; }
3067
+ .apps-empty strong { color: var(--ink); font-size: var(--text-base); }
3068
+ .apps-empty p { margin: 0; max-inline-size: 34ch; }
3069
+ .apps-foot { margin: 0; padding: 0 var(--space-1); color: var(--muted); font-size: var(--text-xs); line-height: var(--lh-xs); }
3044
3070
  /* Peek: an app preview framed in a drawer over the chat (PreviewPeek.tsx). */
3045
3071
  .preview-peek { display: block; inline-size: 100%; block-size: 100%; border: 0; border-radius: var(--radius-md); background: var(--bg); }
3046
3072
  .app-terminal-status { padding: var(--space-2) var(--space-4); margin: 0; color: var(--muted); font-size: var(--text-sm); }
3047
- .app-view-command { overflow-wrap: anywhere; white-space: pre-wrap; font-size: var(--text-xs); color: var(--muted); }
3048
- .app-view-row .artifact-main { flex-basis: 60%; }
3049
3073
  .artifacts-group { display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px; }
3050
3074
  .artifacts-group:last-child { margin-bottom: 0; }
3051
3075
  .artifacts-group-title { font-size: 12px; font-weight: 600; opacity: 0.6; text-transform: uppercase; letter-spacing: 0.04em; }
package/dist/server.js CHANGED
@@ -1941,10 +1941,13 @@ const dedupeSessionNew = (requestId, create) => sessionNewDedupe.run(requestId,
1941
1941
  // generic key->promise cache as above, reused for a different key) — see
1942
1942
  // issue #154's queued follow-ups. A client that isn't sure whether a queued
1943
1943
  // item's send actually reached the node before the socket dropped (see
1944
- // AppController.retryStuckFollowups) resends it verbatim after reconnecting;
1944
+ // FollowupCoordinator.retrySending) resends it verbatim after reconnecting;
1945
1945
  // this makes that safe by collapsing a retried clientMessageId onto the
1946
- // original broadcast + turn instead of double-prompting the runtime.
1947
- const promptDedupe = createSessionNewDedupe();
1946
+ // original broadcast + turn instead of double-prompting the runtime. The key is
1947
+ // kept for a day, not session.new's 10 minutes: a phone backgrounded mid-send
1948
+ // can reconnect long after the turn ended, and an expired key turned its
1949
+ // retry into a fresh, duplicate turn at the bottom of the chat.
1950
+ const promptDedupe = createSessionNewDedupe({ ttlMs: 24 * 60 * 60_000 });
1948
1951
  const dedupePrompt = (clientMessageId, run) => promptDedupe.run(clientMessageId, run);
1949
1952
  // Preview traffic has isolated per-view origins and a private gateway. Relay
1950
1953
  // delivery needs no listener; direct ingress is an optional operator override.
@@ -2910,7 +2913,7 @@ const RELAY_COMMANDS = {
2910
2913
  relay?.sendEvent({ type: "integrations.updated", integrations: integrations.list() });
2911
2914
  }
2912
2915
  },
2913
- async prompt(msg) {
2916
+ async prompt(msg, ctx) {
2914
2917
  const text = String(msg.text ?? "").trim();
2915
2918
  const { images, imageNotes, imageRefs, files } = attachmentsFrom(msg.attachments);
2916
2919
  if (!text && !images.length && !files.length)
@@ -2963,6 +2966,14 @@ const RELAY_COMMANDS = {
2963
2966
  eventLog.appendAttachments(record.id, promptText, [...imageRefs, ...fileRefs]);
2964
2967
  const agentPrompt = promptForAgent(record, promptText);
2965
2968
  const cmid = typeof msg.clientMessageId === "string" && msg.clientMessageId ? msg.clientMessageId : undefined;
2969
+ // A retry of a prompt we already took: the caller missed our echo (that is
2970
+ // why it retried), so acknowledge it again — to the caller only, since
2971
+ // other clients already rendered the first one. Without this the client
2972
+ // keeps the message "sending" and resends it on every reconnect.
2973
+ if (promptDedupe.has(cmid)) {
2974
+ ctx.reply({ type: "session.user_message", sessionId: record.id, text: promptText, clientMessageId: cmid, retry: true });
2975
+ return;
2976
+ }
2966
2977
  void dedupePrompt(cmid, async () => {
2967
2978
  broadcast({ type: "session.user_message", sessionId: record.id, text: promptText, clientMessageId: msg.clientMessageId });
2968
2979
  void sessionNamer.maybeNameSession(record, promptText);
@@ -38,5 +38,5 @@ export function createSessionNewDedupe(options = {}) {
38
38
  });
39
39
  return p;
40
40
  }
41
- return { run, size: () => inflight.size };
41
+ return { run, has: (requestId) => Boolean(requestId && inflight.has(requestId)), size: () => inflight.size };
42
42
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bivy/bivy",
3
- "version": "0.17.3-staging.17",
3
+ "version": "0.17.3-staging.19",
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.",