@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.
- package/dist/agent-instructions.js +4 -1
- package/dist/apps/styles.css +10 -0
- package/dist/session/message-components.js +97 -5
- package/dist/session/transcript-persistence.js +35 -5
- package/package.json +1 -1
- package/web/assets/AutomationsView-D6XMg64V.js +51 -0
- package/web/assets/{ChangesCard-CF4Ggj8i.js → ChangesCard-BF_zqNMW.js} +1 -1
- package/web/assets/{LibraryView-BPA8XNBE.js → LibraryView-CyKhQRWl.js} +1 -1
- package/web/assets/{ProviderConnect-CnobppzO.js → ProviderConnect-xUpGkrXQ.js} +1 -1
- package/web/assets/{SessionList-BTKFzdDa.js → SessionList-CDAaRqDd.js} +1 -1
- package/web/assets/{Settings-C7DbOXb4.js → Settings-C6CBay8K.js} +3 -3
- package/web/assets/{Sheet-Dx_m8Blv.js → Sheet-CQ3kJfG8.js} +1 -1
- package/web/assets/{Terminal-pZiTEW8e.js → Terminal-bWYB7yEP.js} +1 -1
- package/web/assets/{VoiceSettings-WZM9C7g7.js → VoiceSettings-CpRseGnZ.js} +1 -1
- package/web/assets/{abnfDiagram-VCTEODGH-WRpHjP0B.js → abnfDiagram-VCTEODGH-BnqSMbD2.js} +1 -1
- package/web/assets/architecture-7GRP2DOG-DtnBeuIM.js +1 -0
- package/web/assets/{architectureDiagram-5GKGNRK7-CdjlBExx.js → architectureDiagram-5GKGNRK7-BBLHVvrE.js} +1 -1
- package/web/assets/{blockDiagram-I7D4REHJ-D77krZd2.js → blockDiagram-I7D4REHJ-D6ZAWARg.js} +1 -1
- package/web/assets/{c4Diagram-7LVT6UL2-CDhNs6GX.js → c4Diagram-7LVT6UL2-CvHhWLkr.js} +1 -1
- package/web/assets/channel-Ce44as-F.js +1 -0
- package/web/assets/{chunk-4HAMMTFA-B4yDhw9w.js → chunk-4HAMMTFA-Vr_2EPUd.js} +1 -1
- package/web/assets/{chunk-75Z2AOVW-DlBs-44Q.js → chunk-75Z2AOVW-DjA6dwOv.js} +1 -1
- package/web/assets/{chunk-DU6HZSFF-CJ_HANui.js → chunk-DU6HZSFF-Bk-tQdlE.js} +1 -1
- package/web/assets/{chunk-F27PBJKO-Cu5CSjiN.js → chunk-F27PBJKO-CiElOFE2.js} +1 -1
- package/web/assets/{chunk-GMAD6QVW-DyA4dvEU.js → chunk-GMAD6QVW-WQgMtbn0.js} +1 -1
- package/web/assets/{chunk-GVQU2GXP-BQMSnk4C.js → chunk-GVQU2GXP-WEZjgQrA.js} +1 -1
- package/web/assets/{chunk-IMKFNOWR-CS_PemS5.js → chunk-IMKFNOWR-DC-ow2MX.js} +1 -1
- package/web/assets/{chunk-L3NEJ4N5-DxvNkxIH.js → chunk-L3NEJ4N5-BhSiGYPv.js} +1 -1
- package/web/assets/{chunk-OSK3NFVY-CCohDz0n.js → chunk-OSK3NFVY-DoSnNPZl.js} +1 -1
- package/web/assets/{chunk-P2QGCYS3-D-B6wTHD.js → chunk-P2QGCYS3-BHE6l8Ay.js} +1 -1
- package/web/assets/{chunk-POPQ4Y6H-BOvoop5q.js → chunk-POPQ4Y6H-B5T_cREs.js} +1 -1
- package/web/assets/{chunk-PWAF6VOD-CLeIUWUo.js → chunk-PWAF6VOD-C_Bmr9L8.js} +1 -1
- package/web/assets/{chunk-SHT3W25Y-DrmMp-0y.js → chunk-SHT3W25Y-CS6JdVlE.js} +1 -1
- package/web/assets/{chunk-SVP7TREG-us7eU0Ur.js → chunk-SVP7TREG-Za1aku1N.js} +1 -1
- package/web/assets/{chunk-TICWLB2K-iDXvmJ81.js → chunk-TICWLB2K-DSoPwE0f.js} +1 -1
- package/web/assets/classDiagram-ZZMXUADV-YaBJg7rT.js +1 -0
- package/web/assets/classDiagram-v2-VYDZK3BY-YaBJg7rT.js +1 -0
- package/web/assets/{cynefin-OW5HDTMX-CIdE0drB.js → cynefin-OW5HDTMX-DMqMVe7D.js} +1 -1
- package/web/assets/{cynefinDiagram-5FMLGOSQ-DoE-XOWg.js → cynefinDiagram-5FMLGOSQ-C-oGZ90j.js} +1 -1
- package/web/assets/{dagre-GXQ25YYZ-BWmLtVVG.js → dagre-GXQ25YYZ-CmCz8osb.js} +1 -1
- package/web/assets/{diagram-S7CK7UJ4-BXsoBMqT.js → diagram-S7CK7UJ4-B2C_VWJT.js} +1 -1
- package/web/assets/{diagram-UQ7AKVKN-D6ZwCAqS.js → diagram-UQ7AKVKN-K71hxWUE.js} +1 -1
- package/web/assets/{diagram-VSXAHHWV-CahS4Ehx.js → diagram-VSXAHHWV-0j_oCpe3.js} +1 -1
- package/web/assets/{diagram-VX7I27RA-8IpuDz9Q.js → diagram-VX7I27RA-C0ZpEmR9.js} +1 -1
- package/web/assets/{diagram-Z3DM3KII-BNNzb7O9.js → diagram-Z3DM3KII-B3_eLE11.js} +1 -1
- package/web/assets/{ebnfDiagram-PWID7BFC-D9q0WjFG.js → ebnfDiagram-PWID7BFC-BAw1S_yz.js} +1 -1
- package/web/assets/{erDiagram-RLTQ6QDP-CEaH33R9.js → erDiagram-RLTQ6QDP-LnFzfvme.js} +1 -1
- package/web/assets/eventmodeling-NTZA5JFV-DaUDYV6w.js +1 -0
- package/web/assets/flowDiagram-HODETNUW-DpkRRFUz.js +1 -0
- package/web/assets/{ganttDiagram-EL5Y4UJY-BaZJssC1.js → ganttDiagram-EL5Y4UJY-BD1YyxBG.js} +1 -1
- package/web/assets/{gitGraph-4MIJSDKK-BqtDQboB.js → gitGraph-4MIJSDKK-BI2J6VIQ.js} +1 -1
- package/web/assets/{gitGraphDiagram-WWUBYQGX-DSR4hPrg.js → gitGraphDiagram-WWUBYQGX-t3PDpqN5.js} +1 -1
- package/web/assets/{index-C2MrCQ49.js → index-ByQAWMNy.js} +2 -2
- package/web/assets/{index-D6p6NJlp.css → index-DtEJd4mp.css} +1 -1
- package/web/assets/{info-A6RAGUB7-BcRqmoAK.js → info-A6RAGUB7-CSVZrTZB.js} +1 -1
- package/web/assets/{infoDiagram-27XIBGKW-DWrIU55Z.js → infoDiagram-27XIBGKW-W9HHsf-U.js} +1 -1
- package/web/assets/{ishikawaDiagram-5VMMS53U-Bydrg8Aa.js → ishikawaDiagram-5VMMS53U-BV6eCBR-.js} +1 -1
- package/web/assets/{journeyDiagram-3NMN7TZE-Bs9PQ9td.js → journeyDiagram-3NMN7TZE-BLAoYdlB.js} +1 -1
- package/web/assets/{kanban-definition-UXKFOSKX-CRDsBHws.js → kanban-definition-UXKFOSKX-BjjqCqFc.js} +1 -1
- package/web/assets/{mermaid-parser.core-DGTHwG-V.js → mermaid-parser.core-BMChRbVo.js} +3 -3
- package/web/assets/{mermaid.core-Tftjkz0u.js → mermaid.core-3xU-aAau.js} +4 -4
- package/web/assets/{mindmap-definition-YA3MSWOX-Cby1m35H.js → mindmap-definition-YA3MSWOX-Z4hKHQ3u.js} +1 -1
- package/web/assets/mount-CUMK5BkZ.js +36 -0
- package/web/assets/{packet-AYTQ26CC-D7pp3ByC.js → packet-AYTQ26CC-lxuFr31S.js} +1 -1
- package/web/assets/{pegDiagram-XKGWAZYB-B9EB-hpz.js → pegDiagram-XKGWAZYB-FctpK8MP.js} +1 -1
- package/web/assets/{pie-WAS4IAKB-CBLaLmoX.js → pie-WAS4IAKB-D1XjwVNA.js} +1 -1
- package/web/assets/{pieDiagram-E7YTZNPT-B03jVQzh.js → pieDiagram-E7YTZNPT-BsQRYtAh.js} +1 -1
- package/web/assets/{quadrantDiagram-AXDQQJYC-DO7LdvI8.js → quadrantDiagram-AXDQQJYC-BvyrQ2jc.js} +1 -1
- package/web/assets/{radar-RG4KPBEZ-Yp7qqsWn.js → radar-RG4KPBEZ--UB7zpJK.js} +1 -1
- package/web/assets/{railroad-74A4TZTK-DCyaPcEg.js → railroad-74A4TZTK-Bipei93v.js} +1 -1
- package/web/assets/railroad-abnf-HS5TGJTU-DkDg_T0-.js +1 -0
- package/web/assets/railroad-ebnf-LZEXJU2U-C72SpzF6.js +1 -0
- package/web/assets/railroad-peg-WCYAUIDC-DffGUEzi.js +1 -0
- package/web/assets/{railroadDiagram-O6MQD6OU-BjVunKM_.js → railroadDiagram-O6MQD6OU-C4LVh6ZN.js} +1 -1
- package/web/assets/{requirementDiagram-BXWQKSXE-B2scbtpl.js → requirementDiagram-BXWQKSXE-BoJ08H7a.js} +1 -1
- package/web/assets/{sankeyDiagram-P5KCCOFB-C9WyXdqF.js → sankeyDiagram-P5KCCOFB-D-uAad_0.js} +1 -1
- package/web/assets/{sequenceDiagram-WJ2MYXX4-DDjf04-a.js → sequenceDiagram-WJ2MYXX4-CNbcPsiV.js} +1 -1
- package/web/assets/{settingsRoute-DMOkGZ0F.js → settingsRoute-_CRpVkP3.js} +1 -1
- package/web/assets/{stateDiagram-D77RDMKH-QtaNRMSA.js → stateDiagram-D77RDMKH-C_PgmXc4.js} +1 -1
- package/web/assets/stateDiagram-v2-MP3YSRHH-CA3td9Gb.js +1 -0
- package/web/assets/{swimlanes-42K2YHIH-B8i2q2ms.js → swimlanes-42K2YHIH-DKjHuS27.js} +1 -1
- package/web/assets/swimlanesDiagram-VR7AAH4N-XBH4W0Oj.js +8 -0
- package/web/assets/{timeline-definition-24CTP7MA-CBUkpsbO.js → timeline-definition-24CTP7MA-C5j5LSFy.js} +1 -1
- package/web/assets/{treeView-Q6P3EWNA-nflLeiBr.js → treeView-Q6P3EWNA-e8gFneDN.js} +1 -1
- package/web/assets/{treemap-WGGIJYW6-yD2jzvoa.js → treemap-WGGIJYW6-BHj8jZD5.js} +1 -1
- package/web/assets/useStore-Dku6B6Gn.js +23 -0
- package/web/assets/{vennDiagram-4TSXK5OY-B6E0p7OK.js → vennDiagram-4TSXK5OY-D1_1_r15.js} +1 -1
- package/web/assets/{wardley-WFR3VGLG-jjQJdlvj.js → wardley-WFR3VGLG-wO__yant.js} +1 -1
- package/web/assets/{wardleyDiagram-VM6X3IG4-B_RIiDHY.js → wardleyDiagram-VM6X3IG4-TQcBd6dx.js} +1 -1
- package/web/assets/{xychartDiagram-S5SC5T6Z-ULQo9pt_.js → xychartDiagram-S5SC5T6Z-Cse2UCo2.js} +1 -1
- package/web/index.html +2 -2
- package/web/sw.js +1 -1
- package/web/assets/AutomationsView-C91uHBUg.js +0 -51
- package/web/assets/architecture-7GRP2DOG-BriHxVmA.js +0 -1
- package/web/assets/channel-KG6NYshN.js +0 -1
- package/web/assets/classDiagram-ZZMXUADV-DHqO-Y2G.js +0 -1
- package/web/assets/classDiagram-v2-VYDZK3BY-DHqO-Y2G.js +0 -1
- package/web/assets/eventmodeling-NTZA5JFV-S9SQVVXj.js +0 -1
- package/web/assets/flowDiagram-HODETNUW-Btl9wWK-.js +0 -1
- package/web/assets/mount-adAIXSUv.js +0 -36
- package/web/assets/railroad-abnf-HS5TGJTU-FUIbQmAF.js +0 -1
- package/web/assets/railroad-ebnf-LZEXJU2U-C48FI13a.js +0 -1
- package/web/assets/railroad-peg-WCYAUIDC-DgSxsTCM.js +0 -1
- package/web/assets/stateDiagram-v2-MP3YSRHH-CdaiTthO.js +0 -1
- package/web/assets/swimlanesDiagram-VR7AAH4N-B4l2mjJD.js +0 -8
- 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
|
|
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 " +
|
package/dist/apps/styles.css
CHANGED
|
@@ -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
|
-
*
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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