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

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.
@@ -198,7 +198,7 @@ export class AppRegistry extends EventEmitter {
198
198
  return structuredClone(app);
199
199
  }
200
200
  list(sessionId) {
201
- return structuredClone([...this.apps.values()].filter((app) => app.sessionId === sessionId));
201
+ return structuredClone([...this.apps.values()].filter((app) => sessionId === undefined || app.sessionId === sessionId));
202
202
  }
203
203
  getView(id) { return this.views.get(id); }
204
204
  /** An agent turn changed files: re-take changed static snapshots and bump
@@ -35,6 +35,7 @@ export class AppService {
35
35
  this.screenshots = { enabled: options.screenshots?.enabled ?? (() => false), take: options.screenshots?.take ?? takeShots };
36
36
  this.displays = options.displays ?? NO_DISPLAYS;
37
37
  }
38
+ /** A session's apps, or every app on this machine when no session is given. */
38
39
  list(sessionId) {
39
40
  const available = Boolean(this.gateway) && this.gateway?.available !== false;
40
41
  const apps = this.registry.list(sessionId);
@@ -588,6 +588,12 @@ input, textarea, select { font-family: inherit; font-size: 16px; }
588
588
  discoverability pattern as .row-menu-btn — and a touch-friendly baseline
589
589
  opacity on devices with no hover. */
590
590
  .assistant-row { align-self: stretch; width: 100%; max-width: 680px; }
591
+ /* The message "Show in chat" landed on (Artifacts / Apps pages). */
592
+ .is-jump-target { border-radius: var(--radius-md); animation: jump-target 2.4s var(--ease-standard); }
593
+ @keyframes jump-target {
594
+ 0%, 50% { background: color-mix(in srgb, var(--accent) 10%, transparent); box-shadow: 0 0 0 var(--space-2) color-mix(in srgb, var(--accent) 10%, transparent); }
595
+ 100% { background: transparent; box-shadow: 0 0 0 var(--space-2) transparent; }
596
+ }
591
597
  .msg-actions { display: flex; align-items: center; gap: 2px; margin-top: var(--space-1); margin-left: -6px; }
592
598
  .msg-copy-btn, .msg-speak-btn {
593
599
  border: 0; background: transparent; color: var(--muted);
@@ -1396,7 +1402,12 @@ button.sheet-action:last-child { border-bottom: 0; }
1396
1402
  font-weight: var(--weight-semibold); cursor: pointer; white-space: nowrap;
1397
1403
  }
1398
1404
  .seg-btn:hover { color: var(--ink); }
1399
- .seg-btn[aria-selected="true"] { background: var(--surface); color: var(--accent); box-shadow: var(--shadow-sm); }
1405
+ .seg-btn[aria-selected="true"], .seg-btn[aria-checked="true"] { background: var(--surface); color: var(--accent); box-shadow: var(--shadow-sm); }
1406
+ /* Chat | Terminal in the session topbar (SessionViewToggle). */
1407
+ .topbar-subline { display: flex; align-items: center; gap: var(--space-3); min-width: 0; margin-top: 2px; }
1408
+ .topbar-subline .node-switcher { flex: 0 1 auto; }
1409
+ .session-view-toggle { flex: none; padding: 2px; }
1410
+ .session-view-toggle .seg-btn { display: inline-flex; align-items: center; gap: var(--space-1); padding: 2px var(--space-2); line-height: 1.25; }
1400
1411
  .automations-tabs {
1401
1412
  flex: none; align-self: center; max-width: calc(100% - var(--space-6));
1402
1413
  margin: var(--space-2) auto 0; overflow-x: auto;
@@ -1412,6 +1423,62 @@ button.sheet-action:last-child { border-bottom: 0; }
1412
1423
  .automations-view-head-actions > .btn.primary { padding: 9px 18px; }
1413
1424
  .automations-view-body { padding: 8px 24px max(48px, env(safe-area-inset-bottom)); }
1414
1425
  }
1426
+ /* Artifacts / Apps pages (LibraryView.tsx) share the Automations page shell:
1427
+ a quiet image grid for files, a plain list for apps. */
1428
+ .library-view:focus { outline: none; }
1429
+ .library-view .automations-view-head { width: 100%; max-width: 1080px; margin: 0 auto; box-sizing: border-box; }
1430
+ .library-body { max-width: 1080px; }
1431
+ .library-toolbar { display: flex; margin: var(--space-2) 0 var(--space-4); }
1432
+ .library-loading { display: flex; justify-content: center; padding: var(--space-8) 0; }
1433
+ .library-body > .banner { margin: var(--space-2) 0; }
1434
+ .library-note { margin: var(--space-2) 0; font-size: var(--text-sm); color: var(--muted); }
1435
+ .library-grid {
1436
+ list-style: none; margin: var(--space-2) 0 0; padding: 0;
1437
+ display: grid; gap: var(--space-5) var(--space-3);
1438
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
1439
+ }
1440
+ .library-tile { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
1441
+ .library-tile-preview {
1442
+ width: 100%; aspect-ratio: 4 / 3; padding: 0; overflow: hidden;
1443
+ display: grid; place-items: center;
1444
+ border: 1px solid var(--line); border-radius: var(--radius-lg);
1445
+ background: var(--surface-2); color: var(--muted); font: inherit; cursor: pointer;
1446
+ transition: border-color var(--dur-fast) var(--ease-standard);
1447
+ }
1448
+ .library-tile-preview:hover:not(:disabled) { border-color: var(--line-strong); }
1449
+ .library-tile-preview:disabled { cursor: progress; }
1450
+ .library-tile-preview img { display: block; width: 100%; height: 100%; object-fit: cover; }
1451
+ .library-tile-file { display: flex; flex-direction: column; align-items: center; gap: var(--space-1); }
1452
+ .library-tile-ext { font-size: var(--text-xs); font-weight: var(--weight-semibold); letter-spacing: 0.04em; }
1453
+ .library-tile-caption { display: flex; flex-direction: column; gap: 2px; min-width: 0; padding: 0 2px; }
1454
+ .library-tile-name, .library-row-name {
1455
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
1456
+ font-size: var(--text-sm); font-weight: var(--weight-medium); color: var(--ink);
1457
+ }
1458
+ .library-tile-note { font-size: var(--text-xs); color: var(--danger); }
1459
+ .library-source {
1460
+ align-self: flex-start; display: inline-flex; align-items: center; gap: var(--space-1);
1461
+ max-width: 100%; min-width: 0; min-height: 28px; padding: 0; border: 0; background: transparent;
1462
+ color: var(--muted); font: inherit; font-size: var(--text-xs); text-align: left; cursor: pointer;
1463
+ }
1464
+ .library-source:hover { color: var(--ink); }
1465
+ .library-source svg, .library-source-when { flex: none; }
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
+ @media (min-width: 640px) {
1479
+ .library-grid { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: var(--space-6) var(--space-4); }
1480
+ }
1481
+
1415
1482
  .autom-section { margin-top: 26px; }
1416
1483
  .autom-section-head {
1417
1484
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
@@ -2446,6 +2513,12 @@ textarea.picker-search { resize: vertical; font-family: ui-monospace, monospace;
2446
2513
  height: var(--app-h, 100dvh); z-index: var(--z-sheet); display: flex; flex-direction: column;
2447
2514
  background: var(--bg); padding-top: env(safe-area-inset-top);
2448
2515
  }
2516
+ /* The same terminal inside the session pane (Chat | Terminal toggle): in the
2517
+ main column's flow under the topbar, not a fixed full-screen layer. */
2518
+ .term-overlay.term-embedded {
2519
+ position: relative; top: auto; left: auto; width: auto; height: auto;
2520
+ flex: 1 1 auto; min-height: 0; z-index: auto; padding-top: 0;
2521
+ }
2449
2522
  .term-head { display: flex; align-items: center; gap: 10px; padding: 10px 14px; border-bottom: 1px solid var(--line); }
2450
2523
  .term-title { font-weight: 700; flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2451
2524
  .term-status { color: var(--muted); font-weight: 400; margin-left: 8px; font-size: 12px; }
@@ -2493,6 +2566,10 @@ textarea.picker-search { resize: vertical; font-family: ui-monospace, monospace;
2493
2566
  }
2494
2567
  .term-out .xterm { height: 100%; }
2495
2568
  .term-out .xterm-viewport { overflow-y: auto; overscroll-behavior: contain; }
2569
+ /* xterm.css paints the viewport #000; the GPU renderers cover it with the theme
2570
+ background, the DOM fallback does not — a light-theme sliver below the last
2571
+ row. Match readTheme()'s background. */
2572
+ .term-out .xterm .xterm-viewport { background-color: var(--bg); }
2496
2573
  /* Smooth momentum scrolling for the terminal viewport on touch devices. */
2497
2574
  .term-out .xterm-viewport { -webkit-overflow-scrolling: touch; scrollbar-width: thin; }
2498
2575
 
@@ -2853,6 +2930,9 @@ select.picker-search { appearance: auto; }
2853
2930
  .scrim { position: fixed; inset: 0; background: var(--overlay); z-index: var(--z-dropdown); }
2854
2931
  @media (max-width: 900px) {
2855
2932
  .app { grid-template-columns: 1fr; }
2933
+ /* Share the subtitle line with the machine name: icons only (the accessible
2934
+ name stays via aria-label). */
2935
+ .session-view-label { display: none; }
2856
2936
  .only-mobile { display: inline-flex; }
2857
2937
  .sidebar {
2858
2938
  position: fixed; top: var(--app-top, 0px); left: 0; height: var(--app-h, 100dvh);
@@ -6,7 +6,7 @@ export function createAppCommands(service, workspaceFor, published = () => { })
6
6
  return workspace;
7
7
  };
8
8
  const operations = {
9
- "apps.list": (msg) => service.list(String(msg.sessionId)),
9
+ "apps.list": (msg) => service.list(typeof msg.sessionId === "string" ? msg.sessionId : undefined),
10
10
  "apps.publish": (msg) => {
11
11
  const sessionId = String(msg.sessionId);
12
12
  const app = service.publish(sessionId, workspaceOf(sessionId), msg.manifest);
@@ -0,0 +1,42 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // Copyright (c) 2026 Petter André Sjulstad
3
+ //
4
+ // The machine-wide Artifacts index behind the sidebar's Artifacts page: every
5
+ // file an agent sent into a chat (`bivy attach` / `attach_to_chat`), across all
6
+ // of this machine's sessions. Built from the same event-log records attachment
7
+ // GC already sweeps, so there is no second index to keep in sync. Only what can
8
+ // still be opened is listed: the session exists and the blob is still stored.
9
+ /** Newest first, bounded like the per-session projection. */
10
+ export const MAX_MACHINE_ARTIFACTS = 500;
11
+ /** One entry per content hash — the newest send wins, so the same report sent
12
+ * to two sessions links to where it was sent last. */
13
+ export function listMachineArtifacts(deps) {
14
+ const byHash = new Map();
15
+ for (const sessionId of deps.sessionIds()) {
16
+ for (const record of deps.scan(sessionId)) {
17
+ if (record.bivyKind !== "outbound-attachment")
18
+ continue;
19
+ const { ref } = record;
20
+ const existing = byHash.get(ref.hash);
21
+ if (existing && existing.createdAt >= record.createdAt)
22
+ continue;
23
+ byHash.set(ref.hash, {
24
+ sessionId, hash: ref.hash, name: ref.name, mimeType: ref.mimeType, kind: ref.kind, size: ref.size,
25
+ ...(record.caption ? { caption: record.caption } : {}),
26
+ createdAt: record.createdAt,
27
+ artifact: Boolean(record.artifact || existing?.artifact),
28
+ });
29
+ }
30
+ }
31
+ return [...byHash.values()]
32
+ .filter((item) => deps.stored(item.hash))
33
+ .sort((a, b) => b.createdAt - a.createdAt)
34
+ .slice(0, MAX_MACHINE_ARTIFACTS);
35
+ }
36
+ export function createArtifactCommands(deps) {
37
+ return {
38
+ "artifacts.list"(msg, ctx) {
39
+ ctx.reply({ type: "artifacts.list.ok", requestId: msg.requestId, artifacts: listMachineArtifacts(deps) });
40
+ },
41
+ };
42
+ }
@@ -4,6 +4,7 @@
4
4
  * client command registry. Relay and HTTP now differ only in framing. */
5
5
  export const CLIENT_COMMAND_ROUTES = [
6
6
  ...["list", "publish", "offers", "adopt", "open", "logs", "shot", "clearNotes", "share", "revoke", "remove"].map((action) => ({ method: "post", path: `/api/apps/${action}`, kind: `apps.${action}` })),
7
+ { method: "post", path: "/api/artifacts/list", kind: "artifacts.list" },
7
8
  { method: "post", path: "/api/auth/credentials/native-preview", kind: "credentials.native.preview" },
8
9
  { method: "post", path: "/api/auth/credentials/native-import", kind: "credentials.native.import" },
9
10
  { method: "post", path: "/api/session/pause", kind: "session.pause" },
@@ -8,7 +8,8 @@ const optionalSession = { sessionId: Type.Optional(Type.String()) };
8
8
  * this table remains migration-compatible; adding validation is one data row. */
9
9
  export const CLIENT_COMMAND_SCHEMAS = {
10
10
  ping: Type.Object(request),
11
- "apps.list": Type.Object({ ...request, ...session }),
11
+ "apps.list": Type.Object({ ...request, ...optionalSession }),
12
+ "artifacts.list": Type.Object(request),
12
13
  "apps.publish": Type.Object({ ...request, ...session, manifest: Type.Unknown() }),
13
14
  "apps.offers": Type.Object({ ...request, ...session }),
14
15
  "apps.adopt": Type.Object({ ...request, ...session, port: Type.Integer({ minimum: 1024, maximum: 65535 }) }),
package/dist/server.js CHANGED
@@ -115,6 +115,7 @@ import { createCapabilitiesController } from "./controllers/capabilities.js";
115
115
  import { createAccessDeviceController, createLinkedDeviceController } from "./controllers/devices.js";
116
116
  import { createSessionControlCommands } from "./controllers/session-control.js";
117
117
  import { createPresenceCommands } from "./controllers/presence-commands.js";
118
+ import { createArtifactCommands } from "./controllers/artifact-commands.js";
118
119
  import { PresenceBook, deviceFrom } from "./session/presence.js";
119
120
  import { createForkCommands } from "./controllers/fork-commands.js";
120
121
  import { createGithubCommands } from "./controllers/github-commands.js";
@@ -2035,6 +2036,11 @@ const RELAY_COMMANDS = {
2035
2036
  const meta = attachmentStore.readMeta(hash);
2036
2037
  ctx.reply({ type: "attachment.data", requestId, hash, mimeType: meta?.mimeType ?? "application/octet-stream", name: meta?.name, data: bytes.toString("base64") });
2037
2038
  },
2039
+ ...createArtifactCommands({
2040
+ sessionIds: () => new Set([...metadata.listSessions().map((session) => session.id), ...[...openSessions.values()].map((record) => record.id)]),
2041
+ scan: (id) => eventLog.scan(id, ["outbound-attachment"]),
2042
+ stored: (hash) => attachmentStore.getPath(hash) !== null,
2043
+ }),
2038
2044
  ...createPresenceCommands(presenceBook, publishPresence),
2039
2045
  ...createSessionControlCommands({
2040
2046
  resolve: (sessionId) => resolveSession(sessionId),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bivy/bivy",
3
- "version": "0.17.3-staging.15",
3
+ "version": "0.17.3-staging.17",
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.",