@bivy/bivy 0.20.9-staging.25 → 0.20.9-staging.27

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 (106) hide show
  1. package/dist/agent-instructions.js +4 -1
  2. package/dist/apps/styles.css +10 -0
  3. package/dist/session/message-components.js +97 -5
  4. package/dist/session/transcript-persistence.js +35 -5
  5. package/package.json +1 -1
  6. package/web/assets/AutomationsView-D6XMg64V.js +51 -0
  7. package/web/assets/{ChangesCard-CF4Ggj8i.js → ChangesCard-BF_zqNMW.js} +1 -1
  8. package/web/assets/{LibraryView-BPA8XNBE.js → LibraryView-CyKhQRWl.js} +1 -1
  9. package/web/assets/{ProviderConnect-CnobppzO.js → ProviderConnect-xUpGkrXQ.js} +1 -1
  10. package/web/assets/{SessionList-BTKFzdDa.js → SessionList-CDAaRqDd.js} +1 -1
  11. package/web/assets/{Settings-C7DbOXb4.js → Settings-C6CBay8K.js} +3 -3
  12. package/web/assets/{Sheet-Dx_m8Blv.js → Sheet-CQ3kJfG8.js} +1 -1
  13. package/web/assets/{Terminal-pZiTEW8e.js → Terminal-bWYB7yEP.js} +1 -1
  14. package/web/assets/{VoiceSettings-WZM9C7g7.js → VoiceSettings-CpRseGnZ.js} +1 -1
  15. package/web/assets/{abnfDiagram-VCTEODGH-WRpHjP0B.js → abnfDiagram-VCTEODGH-BnqSMbD2.js} +1 -1
  16. package/web/assets/architecture-7GRP2DOG-DtnBeuIM.js +1 -0
  17. package/web/assets/{architectureDiagram-5GKGNRK7-CdjlBExx.js → architectureDiagram-5GKGNRK7-BBLHVvrE.js} +1 -1
  18. package/web/assets/{blockDiagram-I7D4REHJ-D77krZd2.js → blockDiagram-I7D4REHJ-D6ZAWARg.js} +1 -1
  19. package/web/assets/{c4Diagram-7LVT6UL2-CDhNs6GX.js → c4Diagram-7LVT6UL2-CvHhWLkr.js} +1 -1
  20. package/web/assets/channel-Ce44as-F.js +1 -0
  21. package/web/assets/{chunk-4HAMMTFA-B4yDhw9w.js → chunk-4HAMMTFA-Vr_2EPUd.js} +1 -1
  22. package/web/assets/{chunk-75Z2AOVW-DlBs-44Q.js → chunk-75Z2AOVW-DjA6dwOv.js} +1 -1
  23. package/web/assets/{chunk-DU6HZSFF-CJ_HANui.js → chunk-DU6HZSFF-Bk-tQdlE.js} +1 -1
  24. package/web/assets/{chunk-F27PBJKO-Cu5CSjiN.js → chunk-F27PBJKO-CiElOFE2.js} +1 -1
  25. package/web/assets/{chunk-GMAD6QVW-DyA4dvEU.js → chunk-GMAD6QVW-WQgMtbn0.js} +1 -1
  26. package/web/assets/{chunk-GVQU2GXP-BQMSnk4C.js → chunk-GVQU2GXP-WEZjgQrA.js} +1 -1
  27. package/web/assets/{chunk-IMKFNOWR-CS_PemS5.js → chunk-IMKFNOWR-DC-ow2MX.js} +1 -1
  28. package/web/assets/{chunk-L3NEJ4N5-DxvNkxIH.js → chunk-L3NEJ4N5-BhSiGYPv.js} +1 -1
  29. package/web/assets/{chunk-OSK3NFVY-CCohDz0n.js → chunk-OSK3NFVY-DoSnNPZl.js} +1 -1
  30. package/web/assets/{chunk-P2QGCYS3-D-B6wTHD.js → chunk-P2QGCYS3-BHE6l8Ay.js} +1 -1
  31. package/web/assets/{chunk-POPQ4Y6H-BOvoop5q.js → chunk-POPQ4Y6H-B5T_cREs.js} +1 -1
  32. package/web/assets/{chunk-PWAF6VOD-CLeIUWUo.js → chunk-PWAF6VOD-C_Bmr9L8.js} +1 -1
  33. package/web/assets/{chunk-SHT3W25Y-DrmMp-0y.js → chunk-SHT3W25Y-CS6JdVlE.js} +1 -1
  34. package/web/assets/{chunk-SVP7TREG-us7eU0Ur.js → chunk-SVP7TREG-Za1aku1N.js} +1 -1
  35. package/web/assets/{chunk-TICWLB2K-iDXvmJ81.js → chunk-TICWLB2K-DSoPwE0f.js} +1 -1
  36. package/web/assets/classDiagram-ZZMXUADV-YaBJg7rT.js +1 -0
  37. package/web/assets/classDiagram-v2-VYDZK3BY-YaBJg7rT.js +1 -0
  38. package/web/assets/{cynefin-OW5HDTMX-CIdE0drB.js → cynefin-OW5HDTMX-DMqMVe7D.js} +1 -1
  39. package/web/assets/{cynefinDiagram-5FMLGOSQ-DoE-XOWg.js → cynefinDiagram-5FMLGOSQ-C-oGZ90j.js} +1 -1
  40. package/web/assets/{dagre-GXQ25YYZ-BWmLtVVG.js → dagre-GXQ25YYZ-CmCz8osb.js} +1 -1
  41. package/web/assets/{diagram-S7CK7UJ4-BXsoBMqT.js → diagram-S7CK7UJ4-B2C_VWJT.js} +1 -1
  42. package/web/assets/{diagram-UQ7AKVKN-D6ZwCAqS.js → diagram-UQ7AKVKN-K71hxWUE.js} +1 -1
  43. package/web/assets/{diagram-VSXAHHWV-CahS4Ehx.js → diagram-VSXAHHWV-0j_oCpe3.js} +1 -1
  44. package/web/assets/{diagram-VX7I27RA-8IpuDz9Q.js → diagram-VX7I27RA-C0ZpEmR9.js} +1 -1
  45. package/web/assets/{diagram-Z3DM3KII-BNNzb7O9.js → diagram-Z3DM3KII-B3_eLE11.js} +1 -1
  46. package/web/assets/{ebnfDiagram-PWID7BFC-D9q0WjFG.js → ebnfDiagram-PWID7BFC-BAw1S_yz.js} +1 -1
  47. package/web/assets/{erDiagram-RLTQ6QDP-CEaH33R9.js → erDiagram-RLTQ6QDP-LnFzfvme.js} +1 -1
  48. package/web/assets/eventmodeling-NTZA5JFV-DaUDYV6w.js +1 -0
  49. package/web/assets/flowDiagram-HODETNUW-DpkRRFUz.js +1 -0
  50. package/web/assets/{ganttDiagram-EL5Y4UJY-BaZJssC1.js → ganttDiagram-EL5Y4UJY-BD1YyxBG.js} +1 -1
  51. package/web/assets/{gitGraph-4MIJSDKK-BqtDQboB.js → gitGraph-4MIJSDKK-BI2J6VIQ.js} +1 -1
  52. package/web/assets/{gitGraphDiagram-WWUBYQGX-DSR4hPrg.js → gitGraphDiagram-WWUBYQGX-t3PDpqN5.js} +1 -1
  53. package/web/assets/{index-C2MrCQ49.js → index-ByQAWMNy.js} +2 -2
  54. package/web/assets/{index-D6p6NJlp.css → index-DtEJd4mp.css} +1 -1
  55. package/web/assets/{info-A6RAGUB7-BcRqmoAK.js → info-A6RAGUB7-CSVZrTZB.js} +1 -1
  56. package/web/assets/{infoDiagram-27XIBGKW-DWrIU55Z.js → infoDiagram-27XIBGKW-W9HHsf-U.js} +1 -1
  57. package/web/assets/{ishikawaDiagram-5VMMS53U-Bydrg8Aa.js → ishikawaDiagram-5VMMS53U-BV6eCBR-.js} +1 -1
  58. package/web/assets/{journeyDiagram-3NMN7TZE-Bs9PQ9td.js → journeyDiagram-3NMN7TZE-BLAoYdlB.js} +1 -1
  59. package/web/assets/{kanban-definition-UXKFOSKX-CRDsBHws.js → kanban-definition-UXKFOSKX-BjjqCqFc.js} +1 -1
  60. package/web/assets/{mermaid-parser.core-DGTHwG-V.js → mermaid-parser.core-BMChRbVo.js} +3 -3
  61. package/web/assets/{mermaid.core-Tftjkz0u.js → mermaid.core-3xU-aAau.js} +4 -4
  62. package/web/assets/{mindmap-definition-YA3MSWOX-Cby1m35H.js → mindmap-definition-YA3MSWOX-Z4hKHQ3u.js} +1 -1
  63. package/web/assets/mount-CUMK5BkZ.js +36 -0
  64. package/web/assets/{packet-AYTQ26CC-D7pp3ByC.js → packet-AYTQ26CC-lxuFr31S.js} +1 -1
  65. package/web/assets/{pegDiagram-XKGWAZYB-B9EB-hpz.js → pegDiagram-XKGWAZYB-FctpK8MP.js} +1 -1
  66. package/web/assets/{pie-WAS4IAKB-CBLaLmoX.js → pie-WAS4IAKB-D1XjwVNA.js} +1 -1
  67. package/web/assets/{pieDiagram-E7YTZNPT-B03jVQzh.js → pieDiagram-E7YTZNPT-BsQRYtAh.js} +1 -1
  68. package/web/assets/{quadrantDiagram-AXDQQJYC-DO7LdvI8.js → quadrantDiagram-AXDQQJYC-BvyrQ2jc.js} +1 -1
  69. package/web/assets/{radar-RG4KPBEZ-Yp7qqsWn.js → radar-RG4KPBEZ--UB7zpJK.js} +1 -1
  70. package/web/assets/{railroad-74A4TZTK-DCyaPcEg.js → railroad-74A4TZTK-Bipei93v.js} +1 -1
  71. package/web/assets/railroad-abnf-HS5TGJTU-DkDg_T0-.js +1 -0
  72. package/web/assets/railroad-ebnf-LZEXJU2U-C72SpzF6.js +1 -0
  73. package/web/assets/railroad-peg-WCYAUIDC-DffGUEzi.js +1 -0
  74. package/web/assets/{railroadDiagram-O6MQD6OU-BjVunKM_.js → railroadDiagram-O6MQD6OU-C4LVh6ZN.js} +1 -1
  75. package/web/assets/{requirementDiagram-BXWQKSXE-B2scbtpl.js → requirementDiagram-BXWQKSXE-BoJ08H7a.js} +1 -1
  76. package/web/assets/{sankeyDiagram-P5KCCOFB-C9WyXdqF.js → sankeyDiagram-P5KCCOFB-D-uAad_0.js} +1 -1
  77. package/web/assets/{sequenceDiagram-WJ2MYXX4-DDjf04-a.js → sequenceDiagram-WJ2MYXX4-CNbcPsiV.js} +1 -1
  78. package/web/assets/{settingsRoute-DMOkGZ0F.js → settingsRoute-_CRpVkP3.js} +1 -1
  79. package/web/assets/{stateDiagram-D77RDMKH-QtaNRMSA.js → stateDiagram-D77RDMKH-C_PgmXc4.js} +1 -1
  80. package/web/assets/stateDiagram-v2-MP3YSRHH-CA3td9Gb.js +1 -0
  81. package/web/assets/{swimlanes-42K2YHIH-B8i2q2ms.js → swimlanes-42K2YHIH-DKjHuS27.js} +1 -1
  82. package/web/assets/swimlanesDiagram-VR7AAH4N-XBH4W0Oj.js +8 -0
  83. package/web/assets/{timeline-definition-24CTP7MA-CBUkpsbO.js → timeline-definition-24CTP7MA-C5j5LSFy.js} +1 -1
  84. package/web/assets/{treeView-Q6P3EWNA-nflLeiBr.js → treeView-Q6P3EWNA-e8gFneDN.js} +1 -1
  85. package/web/assets/{treemap-WGGIJYW6-yD2jzvoa.js → treemap-WGGIJYW6-BHj8jZD5.js} +1 -1
  86. package/web/assets/useStore-Dku6B6Gn.js +23 -0
  87. package/web/assets/{vennDiagram-4TSXK5OY-B6E0p7OK.js → vennDiagram-4TSXK5OY-D1_1_r15.js} +1 -1
  88. package/web/assets/{wardley-WFR3VGLG-jjQJdlvj.js → wardley-WFR3VGLG-wO__yant.js} +1 -1
  89. package/web/assets/{wardleyDiagram-VM6X3IG4-B_RIiDHY.js → wardleyDiagram-VM6X3IG4-TQcBd6dx.js} +1 -1
  90. package/web/assets/{xychartDiagram-S5SC5T6Z-ULQo9pt_.js → xychartDiagram-S5SC5T6Z-Cse2UCo2.js} +1 -1
  91. package/web/index.html +2 -2
  92. package/web/sw.js +1 -1
  93. package/web/assets/AutomationsView-C91uHBUg.js +0 -51
  94. package/web/assets/architecture-7GRP2DOG-BriHxVmA.js +0 -1
  95. package/web/assets/channel-KG6NYshN.js +0 -1
  96. package/web/assets/classDiagram-ZZMXUADV-DHqO-Y2G.js +0 -1
  97. package/web/assets/classDiagram-v2-VYDZK3BY-DHqO-Y2G.js +0 -1
  98. package/web/assets/eventmodeling-NTZA5JFV-S9SQVVXj.js +0 -1
  99. package/web/assets/flowDiagram-HODETNUW-Btl9wWK-.js +0 -1
  100. package/web/assets/mount-adAIXSUv.js +0 -36
  101. package/web/assets/railroad-abnf-HS5TGJTU-FUIbQmAF.js +0 -1
  102. package/web/assets/railroad-ebnf-LZEXJU2U-C48FI13a.js +0 -1
  103. package/web/assets/railroad-peg-WCYAUIDC-DgSxsTCM.js +0 -1
  104. package/web/assets/stateDiagram-v2-MP3YSRHH-CdaiTthO.js +0 -1
  105. package/web/assets/swimlanesDiagram-VR7AAH4N-B4l2mjJD.js +0 -8
  106. package/web/assets/useStore-D4YwrJWz.js +0 -23
@@ -38,9 +38,12 @@ export const BIVY_AGENT_NOTE = [
38
38
  "rule. A `.csv`/`.tsv` renders as a table; anything else arrives as a download.",
39
39
  "- Show a NUMBER or a CHART inside your reply: fence a spec as ```bivy. A number is " +
40
40
  '`{"type":"metric","value":"4 650 kWh","label":"per year"}`; a chart is a plain Vega-Lite spec with its data ' +
41
- "inline (`{\"mark\":\"bar\",\"data\":{\"values\":[…]},\"encoding\":{…}}`). A chart cannot fetch a URL — put the data in the spec.",
41
+ "inline, or pointing at a workspace data file with `\"data\":{\"url\":\"data/use.csv\"}` (.csv/.tsv/.json). " +
42
+ "A chart cannot fetch a remote URL.",
42
43
  "- Send a file WITHOUT writing a reply (or from a script): run " +
43
44
  '`bivy attach <path> [--caption "short note"]`, or call the `attach_to_chat` tool if you have it.',
45
+ "- Put a published app's preview INSIDE your reply: `::view{app=<app id>}` on its own line. It frames the app " +
46
+ "where you wrote it, and still opens full screen on demand.",
44
47
  "- Something with a UI: `bivy app publish <manifest.json>` (a web server's port, a static build, a terminal) or " +
45
48
  "`bivy app run -- <command>` (a desktop app) gives the user a live preview; `bivy app shot` screenshots it so you " +
46
49
  "can check your work; `bivy app present` tells the user a visible change is ready to look at, and " +
@@ -3642,3 +3642,13 @@ p.setup-note .btn.link { min-height: 44px; margin-block: calc((1lh - 44px) / 2);
3642
3642
  border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface);
3643
3643
  }
3644
3644
  .chart-component figcaption { margin-top: var(--space-2); }
3645
+ /* An app preview framed inside a message. Same bordered surface as a chart or
3646
+ a mermaid diagram — all three are a figure in the prose, and should not read
3647
+ as three different things. */
3648
+ .inline-app { margin: 0; }
3649
+ .inline-app-frame {
3650
+ display: block; width: 100%; height: 420px; border: 1px solid var(--line);
3651
+ border-radius: var(--radius-sm); background: var(--surface);
3652
+ }
3653
+ .inline-app figcaption { display: flex; gap: var(--space-2); align-items: baseline; justify-content: space-between; margin-top: var(--space-2); }
3654
+ .inline-app-open { flex: none; }
@@ -176,6 +176,10 @@ export const COMPONENT_FENCE_LANG = "bivy";
176
176
  export const MAX_COMPONENT_SPEC_CHARS = 16 * 1024;
177
177
  /** Ceiling for a directive's attribute list, bounding the parse below. */
178
178
  const MAX_ATTRS_CHARS = 512;
179
+ /** An app id as the apps registry mints them. Narrow on purpose: an id is the
180
+ * only thing about an app the chat ever holds, so anything that is not plainly
181
+ * one is not treated as an app reference. */
182
+ const APP_ID = /^[\w-]{1,64}$/;
179
183
  /** Leaf directives this grammar knows. `view` is the general one; a named leaf
180
184
  * can be added later without touching the parser. */
181
185
  const LEAF_NAMES = new Set(["view"]);
@@ -224,7 +228,15 @@ export function parseComponentDirective(line) {
224
228
  const m = /^::([a-z][\w-]{0,31})[ \t]{0,32}(\{[^}\n]{0,512}\})?$/i.exec(String(line || "").trim());
225
229
  if (!m || !LEAF_NAMES.has((m[1] ?? "").toLowerCase()))
226
230
  return null;
227
- const { src, ...attrs } = parseAttrs(m[2] ?? "");
231
+ const { src, app, ...attrs } = parseAttrs(m[2] ?? "");
232
+ // `app=<id>` names something the MACHINE already published, not a file, so it
233
+ // is not a workspace reference and the node resolves nothing for it: the id
234
+ // travels in the transcript exactly as `AppMessage` has always stored it, and
235
+ // the view layer turns it into a preview. Kept as a spec rather than a `ref`
236
+ // so nothing downstream mistakes it for a path to read.
237
+ if (typeof app === "string" && APP_ID.test(app)) {
238
+ return { ref: null, spec: { type: "app", appId: app }, attrs };
239
+ }
228
240
  const ref = typeof src === "string" ? unescapeComponentRef(src) : "";
229
241
  if (!ref || classifyImageTarget(ref) !== "workspace")
230
242
  return null;
@@ -292,11 +304,85 @@ export function componentKind(opts) {
292
304
  // show that yet".
293
305
  return "file";
294
306
  }
307
+ /** How deep into a spec to look for references. Bounded so a pathological
308
+ * nesting cannot turn one message into an unbounded walk. */
309
+ const MAX_SPEC_DEPTH = 8;
295
310
  /**
296
- * Every reference a message makes that Bivy has to resolve — from image syntax
297
- * or from a directive — de-duplicated, in first-seen order, under ONE shared
298
- * cap. One budget across both forms is deliberate: the cost being bounded is
299
- * file reads and persisted bytes, which do not care which syntax asked.
311
+ * Workspace files a SPEC references, by putting a relative path in a `url`
312
+ * field. That is Vega-Lite's `data.url`, which is how a chart points at a
313
+ * dataset rather than carrying every row inline — and the whole spec is walked
314
+ * because Vega-Lite allows one per layer, facet and lookup, not just at the top.
315
+ *
316
+ * Deliberately keyed on the field NAME, not on anything Vega-specific: this
317
+ * file must not learn one kind's schema, and "a `url` field holding a
318
+ * workspace path is a reference" is a rule any future kind can use.
319
+ *
320
+ * A `url` that is not a workspace path (an `https://` address, say) is not
321
+ * returned, and so is never resolved — the view layer refuses those outright
322
+ * rather than letting a chart make the reader's browser fetch it.
323
+ */
324
+ export function specReferences(spec, limit = MAX_COMPONENTS_PER_MESSAGE) {
325
+ const out = [];
326
+ const seen = new Set();
327
+ const walk = (value, depth) => {
328
+ if (out.length >= limit || depth > MAX_SPEC_DEPTH || !value || typeof value !== "object")
329
+ return;
330
+ if (Array.isArray(value)) {
331
+ for (const item of value)
332
+ walk(item, depth + 1);
333
+ return;
334
+ }
335
+ for (const [key, child] of Object.entries(value)) {
336
+ if (key === "url" && typeof child === "string") {
337
+ const ref = unescapeComponentRef(child);
338
+ if (classifyImageTarget(ref) === "workspace" && !seen.has(ref)) {
339
+ seen.add(ref);
340
+ out.push(ref);
341
+ if (out.length >= limit)
342
+ return;
343
+ }
344
+ continue;
345
+ }
346
+ walk(child, depth + 1);
347
+ }
348
+ };
349
+ walk(spec, 0);
350
+ return out;
351
+ }
352
+ /** Each ```bivy fence in `text`, as a parsed spec with its offset. Line-wise,
353
+ * because that is how a fence is defined; an unterminated one yields nothing,
354
+ * which is also how a half-streamed message behaves. */
355
+ function componentFences(text) {
356
+ const out = [];
357
+ const lines = String(text).split("\n");
358
+ let at = 0;
359
+ for (let i = 0; i < lines.length; i++) {
360
+ const line = lines[i] ?? "";
361
+ const start = at;
362
+ at += line.length + 1;
363
+ if (!isComponentFence(line.trim().replace(/^```+/, "")))
364
+ continue;
365
+ const body = [];
366
+ let j = i + 1;
367
+ for (; j < lines.length && !lines[j].trim().startsWith("```"); j++) {
368
+ body.push(lines[j]);
369
+ at += lines[j].length + 1;
370
+ }
371
+ if (j < lines.length)
372
+ at += lines[j].length + 1;
373
+ const spec = parseComponentSpec(body.join("\n"));
374
+ if (spec)
375
+ out.push({ spec, at: start });
376
+ i = j;
377
+ }
378
+ return out;
379
+ }
380
+ /**
381
+ * Every reference a message makes that Bivy has to resolve — from image syntax,
382
+ * from a directive, or from a `url` inside a spec — de-duplicated, in first-seen
383
+ * order, under ONE shared cap. One budget across all three forms is deliberate:
384
+ * the cost being bounded is file reads and persisted bytes, which do not care
385
+ * which syntax asked.
300
386
  */
301
387
  export function extractMessageReferences(text, limit = MAX_COMPONENTS_PER_MESSAGE) {
302
388
  if (!text)
@@ -322,6 +408,12 @@ export function extractMessageReferences(text, limit = MAX_COMPONENTS_PER_MESSAG
322
408
  found.push({ ref: directive.ref, origin: "workspace", syntax: "view", at });
323
409
  at += line.length + 1;
324
410
  }
411
+ // A spec can point at a dataset instead of carrying it (a chart's data.url).
412
+ for (const { spec, at: offset } of componentFences(source)) {
413
+ for (const ref of specReferences(spec, limit)) {
414
+ found.push({ ref, origin: "workspace", syntax: "spec", at: offset });
415
+ }
416
+ }
325
417
  found.sort((a, b) => a.at - b.at);
326
418
  const out = [];
327
419
  const seen = new Set();
@@ -127,13 +127,31 @@ export function createTranscriptPersistence(deps) {
127
127
  }
128
128
  return refs;
129
129
  }
130
+ /** References made by the NEWEST assistant message. These are re-read even
131
+ * when the log already has them, which is what lets an agent overwrite a
132
+ * file and reference it again; see resolveWorkspaceRefs. */
133
+ function refsInLatestMessage(record) {
134
+ const messages = record.session.getMessages();
135
+ for (let i = messages.length - 1; i >= 0; i--) {
136
+ const m = messages[i];
137
+ if (m.role !== "assistant")
138
+ continue;
139
+ return new Set(extractWorkspaceRefs(assistantTextForImageScan(m.content)).map(({ ref }) => ref));
140
+ }
141
+ return new Set();
142
+ }
130
143
  /** Store resolved bytes under `ref` and tell every client, durably and live.
131
144
  * One place, so the remote and workspace resolvers below cannot drift in how
132
145
  * they record what they found. `kind` carries whether the bytes are an image,
133
146
  * which is what lets the view layer pick a renderer for a `::view` that
134
147
  * points at something other than a picture. */
135
- function recordResolvedRef(sessionId, ref, bytes, meta) {
148
+ function recordResolvedRef(sessionId, ref, bytes, meta, previousHash) {
136
149
  const stored = attachmentStore.put(bytes, { name: meta.name, mimeType: meta.mimeType, kind: meta.kind ?? "image" });
150
+ // Content-addressed, so re-reading an unchanged file yields the hash
151
+ // already recorded. Appending that again would grow the log and wake every
152
+ // device for a picture none of them would redraw.
153
+ if (previousHash && stored.hash === previousHash)
154
+ return;
137
155
  eventLog.appendInlineImage(sessionId, { url: ref, ref: stored });
138
156
  eventLog.flush(sessionId);
139
157
  deps.broadcast(deps.stampSessionEvent({ type: "session.event", sessionId, event: { type: "inlineImage", url: ref, ref: stored } }));
@@ -166,12 +184,24 @@ export function createTranscriptPersistence(deps) {
166
184
  const refs = workspaceRefsInTranscript(record);
167
185
  if (!refs.size)
168
186
  return;
169
- const alreadyResolved = new Set(eventLog.readInlineImages(record.id).map(([ref]) => ref));
187
+ const resolved = new Map(eventLog.readInlineImages(record.id));
188
+ // References the agent just made are re-read even when the log already has
189
+ // them. An agent that regenerates out/chart.png and shows it again means
190
+ // the new one; without this it would say "here is the updated chart" above
191
+ // the old picture. Bounded to the newest message, so this costs at most the
192
+ // per-message reference budget in reads per turn rather than re-reading
193
+ // every file the whole session ever mentioned.
194
+ const fresh = refsInLatestMessage(record);
170
195
  for (const [rel, syntax] of refs) {
171
- if (alreadyResolved.has(rel))
196
+ const previous = resolved.get(rel);
197
+ const isFresh = fresh.has(rel);
198
+ if (previous && !isFresh)
172
199
  continue;
173
200
  const failedAt = inlineImageFailedAt.get(rel);
174
- if (failedAt !== undefined && Date.now() - failedAt < INLINE_IMAGE_RETRY_COOLDOWN_MS)
201
+ // A fresh reference ignores the cooldown: the likeliest reason it failed
202
+ // before is that the agent had not written the file yet, and waiting ten
203
+ // minutes to show a file that now exists is worse than one extra read.
204
+ if (!isFresh && failedAt !== undefined && Date.now() - failedAt < INLINE_IMAGE_RETRY_COOLDOWN_MS)
175
205
  continue;
176
206
  const plan = planAttachment({ workspaceDir, filePath: rel, maxBytes: MAX_WORKSPACE_REF_BYTES });
177
207
  if (isAttachPlanError(plan)) {
@@ -189,7 +219,7 @@ export function createTranscriptPersistence(deps) {
189
219
  continue;
190
220
  }
191
221
  try {
192
- recordResolvedRef(record.id, rel, plan.bytes, { name: plan.name, mimeType: plan.mimeType, kind: plan.kind });
222
+ recordResolvedRef(record.id, rel, plan.bytes, { name: plan.name, mimeType: plan.mimeType, kind: plan.kind }, previous?.hash);
193
223
  }
194
224
  catch (error) {
195
225
  console.warn(`[component] ${rel}:`, error instanceof Error ? error.message : String(error));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bivy/bivy",
3
- "version": "0.20.9-staging.25",
3
+ "version": "0.20.9-staging.27",
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.",