@bivy/bivy 0.20.9-staging.9 → 0.21.0
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/README.md +1 -2
- package/dist/agent-instructions.js +14 -4
- package/dist/apps/remote-preview.js +7 -0
- package/dist/apps/service.js +6 -1
- package/dist/apps/styles.css +56 -70
- package/dist/automation-reseal.js +30 -0
- package/dist/config-cli.js +2 -0
- package/dist/controllers/credential-commands.js +3 -27
- package/dist/credentials/api.js +2 -43
- package/dist/credentials/document.js +6 -14
- package/dist/credentials/store.js +5 -6
- package/dist/device-registry.js +27 -0
- package/dist/harness/checkpoint.js +0 -0
- package/dist/node-config.js +4 -3
- package/dist/runtime/model-catalog.js +16 -0
- package/dist/runtime/tool-call-map.js +1 -1
- package/dist/runtime/tool-trace.js +1 -1
- package/dist/server.js +136 -244
- package/dist/session/event-log.js +136 -29
- package/dist/session/inline-image-fetch.js +24 -33
- package/dist/session/message-components.js +429 -0
- package/dist/session/run-terminal.js +9 -7
- package/dist/session/transcript-persistence.js +135 -10
- package/dist/work-instructions.js +50 -0
- package/package.json +2 -2
- package/web/assets/AutomationsView-HnUfeti_.js +51 -0
- package/web/assets/ChangesCard-Dnkguen5.js +4 -0
- package/web/assets/LibraryView-CSKKV8TI.js +2 -0
- package/web/assets/{ProviderConnect-BPwnaFGk.js → ProviderConnect-DNgxGmP9.js} +1 -1
- package/web/assets/SessionList-CYU9IGnd.js +1 -0
- package/web/assets/Settings-HQZtxiMV.js +10 -0
- package/web/assets/{Sheet-DOOJosQY.js → Sheet-DoPtOHBC.js} +1 -1
- package/web/assets/{StatusDot-dpbztK53.js → Spinner-CPdRfDcT.js} +1 -1
- package/web/assets/{Terminal-DVGXS6Yd.js → Terminal-DY6UB5U-.js} +1 -1
- package/web/assets/{VoiceSettings-DX-n0SFl.js → VoiceSettings-BoQ_Mhpm.js} +1 -1
- package/web/assets/{abnfDiagram-VCTEODGH-CnJ4astd.js → abnfDiagram-VCTEODGH-n-P11_L4.js} +1 -1
- package/web/assets/arc-BNLueWOY.js +1 -0
- package/web/assets/architecture-7GRP2DOG-DyG-wgaH.js +1 -0
- package/web/assets/{architectureDiagram-5GKGNRK7-B26-V_Xd.js → architectureDiagram-5GKGNRK7-C0b9IP5U.js} +1 -1
- package/web/assets/{blockDiagram-I7D4REHJ-DuoZkDHs.js → blockDiagram-I7D4REHJ-iVx1FQBD.js} +1 -1
- package/web/assets/{c4Diagram-7LVT6UL2-B81PZ-Vw.js → c4Diagram-7LVT6UL2-DIwEaAvJ.js} +1 -1
- package/web/assets/channel-HmCJmS4T.js +1 -0
- package/web/assets/{chunk-4HAMMTFA-DmSrqg9X.js → chunk-4HAMMTFA-DSsyukSz.js} +1 -1
- package/web/assets/chunk-75Z2AOVW-zwLYo-XL.js +2 -0
- package/web/assets/{chunk-DU6HZSFF-jMBIr1yK.js → chunk-DU6HZSFF-PGuLkqeP.js} +15 -15
- package/web/assets/chunk-F27PBJKO-CpkyeGC9.js +1 -0
- package/web/assets/{chunk-GMAD6QVW-C5YdBKWY.js → chunk-GMAD6QVW-CZSmK3Ms.js} +1 -1
- package/web/assets/{chunk-GVQU2GXP-DOOAmJLJ.js → chunk-GVQU2GXP-C2XMkY6v.js} +1 -1
- package/web/assets/{chunk-IMKFNOWR-CISXYXGE.js → chunk-IMKFNOWR-DAIVz3j5.js} +1 -1
- package/web/assets/{chunk-L3NEJ4N5-CvWtiEy-.js → chunk-L3NEJ4N5-C0geNTDK.js} +1 -1
- package/web/assets/{chunk-OSK3NFVY-yVAn5yky.js → chunk-OSK3NFVY-D-WN2fBr.js} +2 -2
- package/web/assets/{chunk-P2QGCYS3-D-VGoELb.js → chunk-P2QGCYS3-DYGhprKz.js} +1 -1
- package/web/assets/{chunk-POPQ4Y6H-0Mj4Bp-N.js → chunk-POPQ4Y6H-Dlsu2a2Q.js} +1 -1
- package/web/assets/{chunk-PWAF6VOD-Dvvdg6s1.js → chunk-PWAF6VOD-DK0QWYdc.js} +1 -1
- package/web/assets/{chunk-SHT3W25Y-DcllnbDq.js → chunk-SHT3W25Y-BpQHIdUi.js} +1 -1
- package/web/assets/{chunk-SVP7TREG-DnuOGeio.js → chunk-SVP7TREG-Cs5YWxpG.js} +1 -1
- package/web/assets/{chunk-TICWLB2K-Dr76NyiT.js → chunk-TICWLB2K-BRsWLPXA.js} +1 -1
- package/web/assets/chunk-XXDRQBXY-CjF14Z7v.js +1 -0
- package/web/assets/classDiagram-ZZMXUADV-BmdAoNdO.js +1 -0
- package/web/assets/classDiagram-v2-VYDZK3BY-BmdAoNdO.js +1 -0
- package/web/assets/colors-Cg5Vh3br.js +1 -0
- package/web/assets/{cose-bilkent-JH36ORCC-BIqBF2fY.js → cose-bilkent-JH36ORCC-CEUD3V3W.js} +1 -1
- package/web/assets/{cynefin-OW5HDTMX-DoAt_4E6.js → cynefin-OW5HDTMX-DoHWnu_-.js} +1 -1
- package/web/assets/cynefinDiagram-5FMLGOSQ-CgPeHmV5.js +62 -0
- package/web/assets/{dagre-GXQ25YYZ-DIAXxVMd.js → dagre-GXQ25YYZ-CrIlgwM7.js} +1 -1
- package/web/assets/{defaultLocale-BFoDCU3G.js → defaultLocale-CNPGWpMu.js} +1 -1
- package/web/assets/{diagram-S7CK7UJ4-DA78JGp6.js → diagram-S7CK7UJ4-BM_l51zV.js} +1 -1
- package/web/assets/diagram-UQ7AKVKN-CfkxqgPI.js +41 -0
- package/web/assets/{diagram-VSXAHHWV-DCkvR_pJ.js → diagram-VSXAHHWV-0y5vCI9g.js} +1 -1
- package/web/assets/diagram-VX7I27RA-BMkMy7jn.js +24 -0
- package/web/assets/diagram-Z3DM3KII--0fxfDqG.js +24 -0
- package/web/assets/dist-kYfyPZP9.js +1 -0
- package/web/assets/{ebnfDiagram-PWID7BFC-CSCD0PAi.js → ebnfDiagram-PWID7BFC-6tPNzbJl.js} +1 -1
- package/web/assets/embed-BXV41wg7.js +201 -0
- package/web/assets/{erDiagram-RLTQ6QDP-M512qtMu.js → erDiagram-RLTQ6QDP-Bjqm_o3R.js} +1 -1
- package/web/assets/eventmodeling-NTZA5JFV-CoCZxN1n.js +1 -0
- package/web/assets/flowDiagram-HODETNUW-CznfWzEq.js +1 -0
- package/web/assets/ganttDiagram-EL5Y4UJY-92X7KOOI.js +292 -0
- package/web/assets/{gitGraph-4MIJSDKK-CwjwFpZb.js → gitGraph-4MIJSDKK-B5F-u7Vq.js} +1 -1
- package/web/assets/gitGraphDiagram-WWUBYQGX-Ct2hVG0b.js +106 -0
- package/web/assets/index-BKSbod42.css +1 -0
- package/web/assets/{index-CNs5i7tU.js → index-CnjJQpgZ.js} +2 -2
- package/web/assets/{info-A6RAGUB7-D6nXu5V2.js → info-A6RAGUB7-D3xmYWyC.js} +1 -1
- package/web/assets/infoDiagram-27XIBGKW-lzqZ7xaA.js +2 -0
- package/web/assets/init-ZlXS9mIS.js +1 -0
- package/web/assets/{ishikawaDiagram-5VMMS53U-4bsiyG2x.js → ishikawaDiagram-5VMMS53U-CsVZHXR0.js} +1 -1
- package/web/assets/{journeyDiagram-3NMN7TZE-o3U3L2rt.js → journeyDiagram-3NMN7TZE-DN9AdyVy.js} +1 -1
- package/web/assets/{kanban-definition-UXKFOSKX-CQOPj0NZ.js → kanban-definition-UXKFOSKX-gvkBaOFe.js} +1 -1
- package/web/assets/linear-CeYXFQza.js +1 -0
- package/web/assets/{mermaid-parser.core-Ba2-DGvb.js → mermaid-parser.core-CPwOm7-a.js} +3 -3
- package/web/assets/{mermaid.core-BxdMVbm9.js → mermaid.core-F09Wr40d.js} +6 -6
- package/web/assets/{mindmap-definition-YA3MSWOX-1Eu4-zLM.js → mindmap-definition-YA3MSWOX-Qzc2J9Pm.js} +1 -1
- package/web/assets/mount-Bvl-yslT.js +36 -0
- package/web/assets/ordinal-BdBDtYTt.js +1 -0
- package/web/assets/{packet-AYTQ26CC-BTZ7CbVd.js → packet-AYTQ26CC-Unxh5mP2.js} +1 -1
- package/web/assets/{pegDiagram-XKGWAZYB-DNdK3axo.js → pegDiagram-XKGWAZYB-BIqcRp3m.js} +1 -1
- package/web/assets/{pie-WAS4IAKB-DidjfqgW.js → pie-WAS4IAKB-BipHZKbc.js} +1 -1
- package/web/assets/{pieDiagram-E7YTZNPT-Dme68TUA.js → pieDiagram-E7YTZNPT-gaTNuzPD.js} +2 -2
- package/web/assets/{quadrantDiagram-AXDQQJYC-z0xeSJSB.js → quadrantDiagram-AXDQQJYC-3BEmlbuE.js} +1 -1
- package/web/assets/{radar-RG4KPBEZ-BV50NO32.js → radar-RG4KPBEZ-Cm1_Q0XJ.js} +1 -1
- package/web/assets/{railroad-74A4TZTK-BOkd4wGS.js → railroad-74A4TZTK-CJ4K8tjT.js} +1 -1
- package/web/assets/railroad-abnf-HS5TGJTU-BVKI4cmM.js +1 -0
- package/web/assets/railroad-ebnf-LZEXJU2U-UMcDSelb.js +1 -0
- package/web/assets/railroad-peg-WCYAUIDC-C00zpjQU.js +1 -0
- package/web/assets/{railroadDiagram-O6MQD6OU-2eK1mPm_.js → railroadDiagram-O6MQD6OU-Nzyn7Xx6.js} +1 -1
- package/web/assets/range-EplFIxc7.js +1 -0
- package/web/assets/{requirementDiagram-BXWQKSXE-DvlVp433.js → requirementDiagram-BXWQKSXE-9zXuiai_.js} +1 -1
- package/web/assets/{sankeyDiagram-P5KCCOFB-C6CsEyCg.js → sankeyDiagram-P5KCCOFB-Cnw1rQun.js} +1 -1
- package/web/assets/{sequenceDiagram-WJ2MYXX4-1wwxxN6P.js → sequenceDiagram-WJ2MYXX4-BSLLRV-U.js} +3 -3
- package/web/assets/{settingsRoute-Dxrvnqih.js → settingsRoute-C2Up46o6.js} +1 -1
- package/web/assets/src-CFHfg5gg.js +1 -0
- package/web/assets/stateDiagram-D77RDMKH-D07b4cJB.js +1 -0
- package/web/assets/stateDiagram-v2-MP3YSRHH-DaG9Yzhk.js +1 -0
- package/web/assets/step-BDqHAzds.js +1 -0
- package/web/assets/{swimlanes-42K2YHIH-XLThGmTf.js → swimlanes-42K2YHIH-RpDl0CG9.js} +1 -1
- package/web/assets/swimlanesDiagram-VR7AAH4N-7003ldWX.js +8 -0
- package/web/assets/time-Dzw-YSCF.js +1 -0
- package/web/assets/{timeline-definition-24CTP7MA-BEOV0lDT.js → timeline-definition-24CTP7MA-BgQOP12r.js} +1 -1
- package/web/assets/{treeView-Q6P3EWNA-Dm4FCZVW.js → treeView-Q6P3EWNA-BiSBsrlo.js} +1 -1
- package/web/assets/treemap-BfjIY-m6.js +1 -0
- package/web/assets/{treemap-WGGIJYW6-DEZGmkE8.js → treemap-WGGIJYW6-CNdjKqUG.js} +1 -1
- package/web/assets/useStore-CxjHbFPA.js +16 -0
- package/web/assets/{vennDiagram-4TSXK5OY-CDRUEJ_c.js → vennDiagram-4TSXK5OY-DLzOr3Yv.js} +1 -1
- package/web/assets/{wardley-WFR3VGLG-CjZ5JazT.js → wardley-WFR3VGLG-LCC8FWVb.js} +1 -1
- package/web/assets/{wardleyDiagram-VM6X3IG4-COsxLnU6.js → wardleyDiagram-VM6X3IG4-WrwzM3YW.js} +1 -1
- package/web/assets/{xychartDiagram-S5SC5T6Z-BfwzNIP-.js → xychartDiagram-S5SC5T6Z-B_HuoAnl.js} +3 -3
- package/web/index.html +2 -2
- package/web/sw.js +1 -1
- package/dist/ephemeral-exec.js +0 -108
- package/dist/hosted-custody.js +0 -30
- package/web/assets/AutomationsView-Dk1aHviY.js +0 -51
- package/web/assets/ChangesCard-Bn37dRew.js +0 -4
- package/web/assets/LibraryView-CGkpNOgR.js +0 -2
- package/web/assets/Settings-DJjEVwmu.js +0 -10
- package/web/assets/arc-Pry-7G1_.js +0 -1
- package/web/assets/architecture-7GRP2DOG-kbFZ_7eU.js +0 -1
- package/web/assets/array-BifhSqXX.js +0 -1
- package/web/assets/channel-CpJ3OYta.js +0 -1
- package/web/assets/chunk-75Z2AOVW-CGlA2AB-.js +0 -2
- package/web/assets/chunk-F27PBJKO-Cto7X88Z.js +0 -1
- package/web/assets/chunk-XXDRQBXY-DqAYeljN.js +0 -1
- package/web/assets/classDiagram-ZZMXUADV-BSqiC-6r.js +0 -1
- package/web/assets/classDiagram-v2-VYDZK3BY-BSqiC-6r.js +0 -1
- package/web/assets/cynefinDiagram-5FMLGOSQ-B2jZZ_es.js +0 -62
- package/web/assets/diagram-UQ7AKVKN-D7BIW4QU.js +0 -41
- package/web/assets/diagram-VX7I27RA-qmkf9rFQ.js +0 -24
- package/web/assets/diagram-Z3DM3KII-BZf2h-T8.js +0 -24
- package/web/assets/dist-v5Q1xZ2K.js +0 -1
- package/web/assets/eventmodeling-NTZA5JFV-DHYpZdDY.js +0 -1
- package/web/assets/flowDiagram-HODETNUW-BdNS-1c7.js +0 -1
- package/web/assets/ganttDiagram-EL5Y4UJY-DGMQzJ8U.js +0 -292
- package/web/assets/gitGraphDiagram-WWUBYQGX-DNfbdhPa.js +0 -106
- package/web/assets/index-kWXUrhG8.css +0 -1
- package/web/assets/infoDiagram-27XIBGKW-QcpEFiaQ.js +0 -2
- package/web/assets/init-C-OQMol4.js +0 -1
- package/web/assets/line-Cq9nN3X2.js +0 -1
- package/web/assets/linear-SzVmGSw4.js +0 -1
- package/web/assets/mount-C9EzYXqA.js +0 -31
- package/web/assets/ordinal-BDEzSJ7C.js +0 -1
- package/web/assets/path-fybaL0A-.js +0 -1
- package/web/assets/railroad-abnf-HS5TGJTU-DiQLjR_J.js +0 -1
- package/web/assets/railroad-ebnf-LZEXJU2U-D4bSTzXb.js +0 -1
- package/web/assets/railroad-peg-WCYAUIDC-DDhl4AMh.js +0 -1
- package/web/assets/src-B6xuSHsQ.js +0 -1
- package/web/assets/stateDiagram-D77RDMKH-_fkXrLZe.js +0 -1
- package/web/assets/stateDiagram-v2-MP3YSRHH-Cuo-j5c1.js +0 -1
- package/web/assets/swimlanesDiagram-VR7AAH4N-DOpCt5KW.js +0 -8
- package/web/assets/useStore-C1E8iwAQ.js +0 -29
|
@@ -17,10 +17,12 @@
|
|
|
17
17
|
// text). The base is a SNAPSHOT that is overwritten each turn and SHRINKS on
|
|
18
18
|
// compaction, so it cannot be appended naively. Instead each snapshot is stored
|
|
19
19
|
// as a bounded DELTA against the previous one: a `reset:false` record carrying
|
|
20
|
-
// only the new tail when the snapshot merely extends the last (the common case)
|
|
21
|
-
// or a `
|
|
22
|
-
//
|
|
23
|
-
//
|
|
20
|
+
// only the new tail when the snapshot merely extends the last (the common case)
|
|
21
|
+
// or changes after a shared prefix (`keep` = how much of the prefix survives;
|
|
22
|
+
// a retried turn replacing its trailing error), or a `reset:true` record
|
|
23
|
+
// carrying the full snapshot when its first message changed (compaction).
|
|
24
|
+
// `baseReplay` folds these back — reset replaces, non-reset truncates to `keep`
|
|
25
|
+
// and extends — reproducing the last snapshot
|
|
24
26
|
// exactly, so the fold is behaviour-preserving while keeping the file bounded
|
|
25
27
|
// (~O(total messages)) instead of O(turns × transcript size).
|
|
26
28
|
//
|
|
@@ -234,7 +236,46 @@ export function mergeBases(logged, runtime) {
|
|
|
234
236
|
if (add(m))
|
|
235
237
|
sequence.push({ fresh: m }); // not in the logged base
|
|
236
238
|
}
|
|
237
|
-
return placeFresh(merged, sequence);
|
|
239
|
+
return placeLoggedOnly(placeFresh(merged, sequence), sequence);
|
|
240
|
+
}
|
|
241
|
+
function timeOf(m) {
|
|
242
|
+
const t = m.timestamp;
|
|
243
|
+
return typeof t === "number" && Number.isFinite(t) ? t : undefined;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Put runs of messages only the log has back where they happened. A reload can
|
|
247
|
+
* drop messages the log stored: Claude's reloaded transcript has no subagent
|
|
248
|
+
* sidechain. Such a run kept its spot after its last shared message, so every
|
|
249
|
+
* later turn went in ahead of it and the old subagent work sat below the newest
|
|
250
|
+
* reply. A run that is older than the shared message before it moves to just
|
|
251
|
+
* before the first shared message that came later.
|
|
252
|
+
*/
|
|
253
|
+
function placeLoggedOnly(list, sequence) {
|
|
254
|
+
const shared = new Set(sequence.map((item) => ("anchor" in item ? item.anchor : item.fresh)));
|
|
255
|
+
const out = [];
|
|
256
|
+
let previous; // last shared message already in `out`
|
|
257
|
+
for (let i = 0; i < list.length;) {
|
|
258
|
+
if (shared.has(list[i])) {
|
|
259
|
+
previous = list[i];
|
|
260
|
+
out.push(previous);
|
|
261
|
+
i++;
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
let end = i;
|
|
265
|
+
while (end < list.length && !shared.has(list[end]))
|
|
266
|
+
end++;
|
|
267
|
+
const run = list.slice(i, end);
|
|
268
|
+
i = end;
|
|
269
|
+
const t = timeOf(run[0]);
|
|
270
|
+
const before = previous && timeOf(previous);
|
|
271
|
+
if (t === undefined || before === undefined || before <= t) {
|
|
272
|
+
out.push(...run);
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
const at = out.findIndex((m) => shared.has(m) && (timeOf(m) ?? -Infinity) > t);
|
|
276
|
+
out.splice(at < 0 ? out.length : at, 0, ...run);
|
|
277
|
+
}
|
|
278
|
+
return out;
|
|
238
279
|
}
|
|
239
280
|
/**
|
|
240
281
|
* Put messages only the runtime had where they happened, not at the end. A
|
|
@@ -249,10 +290,6 @@ function placeFresh(merged, sequence) {
|
|
|
249
290
|
const freshSet = new Set(sequence.flatMap((item) => ("fresh" in item ? [item.fresh] : [])));
|
|
250
291
|
if (!freshSet.size)
|
|
251
292
|
return merged;
|
|
252
|
-
const timeOf = (m) => {
|
|
253
|
-
const t = m.timestamp;
|
|
254
|
-
return typeof t === "number" && Number.isFinite(t) ? t : undefined;
|
|
255
|
-
};
|
|
256
293
|
const placed = merged.filter((m) => !freshSet.has(m));
|
|
257
294
|
let previous; // last anchor seen, or last fresh placed after it
|
|
258
295
|
for (let i = 0; i < sequence.length; i++) {
|
|
@@ -291,7 +328,9 @@ function isBase(value) {
|
|
|
291
328
|
if (!value || typeof value !== "object")
|
|
292
329
|
return false;
|
|
293
330
|
const record = value;
|
|
294
|
-
|
|
331
|
+
const keep = value.keep;
|
|
332
|
+
return record.bivyKind === "base" && Array.isArray(record.messages) && typeof record.reset === "boolean"
|
|
333
|
+
&& (keep === undefined || (Number.isInteger(keep) && keep >= 0));
|
|
295
334
|
}
|
|
296
335
|
function isForkDisplay(value) {
|
|
297
336
|
if (!value || typeof value !== "object")
|
|
@@ -544,34 +583,70 @@ export function baseReplay(entries) {
|
|
|
544
583
|
if (entry.reset)
|
|
545
584
|
base = [...entry.messages];
|
|
546
585
|
else
|
|
547
|
-
base = base.concat(entry.messages);
|
|
586
|
+
base = (entry.keep === undefined ? base : base.slice(0, entry.keep)).concat(entry.messages);
|
|
548
587
|
}
|
|
549
588
|
return base;
|
|
550
589
|
}
|
|
551
|
-
function parseLogDetailed(
|
|
552
|
-
|
|
590
|
+
function parseLogDetailed(lines, keep = () => true) {
|
|
591
|
+
let records = [];
|
|
553
592
|
let malformedLines = 0;
|
|
554
|
-
|
|
593
|
+
let superseded = 0;
|
|
594
|
+
for (const line of lines) {
|
|
555
595
|
const trimmed = line.trim();
|
|
556
596
|
if (!trimmed || !keep(trimmed))
|
|
557
597
|
continue;
|
|
558
598
|
try {
|
|
559
599
|
const value = JSON.parse(trimmed);
|
|
560
|
-
if (isRecord(value))
|
|
561
|
-
records.push(value);
|
|
562
|
-
else
|
|
600
|
+
if (!isRecord(value)) {
|
|
563
601
|
malformedLines += 1;
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
// A full reset replaces every earlier base record (baseReplay discards
|
|
605
|
+
// them), so don't hold old snapshots and their images in memory.
|
|
606
|
+
if (isBase(value) && value.reset) {
|
|
607
|
+
const before = records.length;
|
|
608
|
+
records = records.filter((record) => record.bivyKind !== "base");
|
|
609
|
+
superseded += before - records.length;
|
|
610
|
+
}
|
|
611
|
+
records.push(value);
|
|
564
612
|
}
|
|
565
613
|
catch {
|
|
566
614
|
malformedLines += 1;
|
|
567
615
|
}
|
|
568
616
|
}
|
|
569
|
-
return { records, malformedLines };
|
|
617
|
+
return { records, malformedLines, superseded };
|
|
618
|
+
}
|
|
619
|
+
/** A log this large that carries replaced base snapshots is rewritten without
|
|
620
|
+
* them when it's next read. */
|
|
621
|
+
const COMPACT_LOG_BYTES = 32 * 1024 * 1024;
|
|
622
|
+
/** A file's lines, read in chunks: a log can outgrow the longest string V8
|
|
623
|
+
* allows (~512 MB), which made the whole session unreadable. */
|
|
624
|
+
function* fileLines(file) {
|
|
625
|
+
const fd = fs.openSync(file, "r");
|
|
626
|
+
try {
|
|
627
|
+
const chunk = Buffer.allocUnsafe(4 * 1024 * 1024);
|
|
628
|
+
let partial = [];
|
|
629
|
+
for (let n = fs.readSync(fd, chunk); n > 0; n = fs.readSync(fd, chunk)) {
|
|
630
|
+
let start = 0;
|
|
631
|
+
for (let nl = chunk.indexOf(10, start); nl !== -1 && nl < n; nl = chunk.indexOf(10, start)) {
|
|
632
|
+
yield Buffer.concat([...partial, chunk.subarray(start, nl)]).toString("utf8");
|
|
633
|
+
partial = [];
|
|
634
|
+
start = nl + 1;
|
|
635
|
+
}
|
|
636
|
+
if (start < n)
|
|
637
|
+
partial.push(Buffer.from(chunk.subarray(start, n)));
|
|
638
|
+
}
|
|
639
|
+
if (partial.length)
|
|
640
|
+
yield Buffer.concat(partial).toString("utf8");
|
|
641
|
+
}
|
|
642
|
+
finally {
|
|
643
|
+
fs.closeSync(fd);
|
|
644
|
+
}
|
|
570
645
|
}
|
|
571
646
|
/** Parse a JSONL log body into valid records. Callers that need corruption
|
|
572
647
|
* diagnostics use EventLog.load, which reports malformed lines via onIssue. */
|
|
573
648
|
export function parseLog(body) {
|
|
574
|
-
return parseLogDetailed(body).records;
|
|
649
|
+
return parseLogDetailed(body.split("\n")).records;
|
|
575
650
|
}
|
|
576
651
|
/**
|
|
577
652
|
* Append-only, coalesced, throttled JSONL log store — the write+read companion to
|
|
@@ -655,11 +730,14 @@ export class EventLog {
|
|
|
655
730
|
/** Read + parse a session's log file (lines passing `keep`), reporting problems. */
|
|
656
731
|
readLog(id, keep) {
|
|
657
732
|
try {
|
|
658
|
-
const
|
|
659
|
-
const
|
|
733
|
+
const file = this.pathFor(id);
|
|
734
|
+
const bytes = fs.statSync(file).size;
|
|
735
|
+
const parsed = parseLogDetailed(fileLines(file), keep);
|
|
660
736
|
if (parsed.malformedLines > 0)
|
|
661
737
|
this.report(id, "parse", new Error(`${parsed.malformedLines} malformed record(s); valid history was recovered`));
|
|
662
|
-
|
|
738
|
+
if (!keep && parsed.superseded > 0 && bytes > COMPACT_LOG_BYTES)
|
|
739
|
+
return { records: parsed.records, bytes: this.compact(id, parsed.records) ?? bytes };
|
|
740
|
+
return { records: parsed.records, bytes };
|
|
663
741
|
}
|
|
664
742
|
catch (error) {
|
|
665
743
|
if (error?.code !== "ENOENT")
|
|
@@ -667,6 +745,31 @@ export class EventLog {
|
|
|
667
745
|
return { records: [], bytes: 0 };
|
|
668
746
|
}
|
|
669
747
|
}
|
|
748
|
+
/** Rewrite a log without the base snapshots a later reset replaced (logs
|
|
749
|
+
* written before truncate-and-extend records repeated the whole transcript on
|
|
750
|
+
* every retry). Atomic; returns the new size, or undefined if it failed. */
|
|
751
|
+
compact(id, records) {
|
|
752
|
+
const file = this.pathFor(id);
|
|
753
|
+
const tmp = `${file}.compact-${process.pid}`;
|
|
754
|
+
try {
|
|
755
|
+
const fd = fs.openSync(tmp, "w");
|
|
756
|
+
let bytes = 0;
|
|
757
|
+
try {
|
|
758
|
+
for (const record of records)
|
|
759
|
+
bytes += fs.writeSync(fd, `${JSON.stringify(record)}\n`);
|
|
760
|
+
}
|
|
761
|
+
finally {
|
|
762
|
+
fs.closeSync(fd);
|
|
763
|
+
}
|
|
764
|
+
fs.renameSync(tmp, file);
|
|
765
|
+
return bytes;
|
|
766
|
+
}
|
|
767
|
+
catch (error) {
|
|
768
|
+
fs.rmSync(tmp, { force: true });
|
|
769
|
+
this.report(id, "rewrite", error);
|
|
770
|
+
return undefined;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
670
773
|
load(id) {
|
|
671
774
|
const cached = this.disk.get(id);
|
|
672
775
|
if (cached) {
|
|
@@ -762,15 +865,19 @@ export class EventLog {
|
|
|
762
865
|
// Identical snapshot → nothing to record.
|
|
763
866
|
if (nextKeys.length === prevKeys.length && prevKeys.every((k, i) => k === nextKeys[i]))
|
|
764
867
|
return;
|
|
765
|
-
//
|
|
766
|
-
//
|
|
767
|
-
//
|
|
868
|
+
// Record only what follows the prefix both snapshots share: a plain extend, or
|
|
869
|
+
// a truncate-and-extend (`keep`) when a later message changed — a retried turn
|
|
870
|
+
// replacing its trailing error rewrote the whole transcript, images and all,
|
|
871
|
+
// on every retry. A changed first message (compaction) or the first snapshot
|
|
768
872
|
// appends a full reset. Rebuild the recorded messages from the serialized form:
|
|
769
873
|
// a free deep copy that also matches exactly what will land on disk.
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
874
|
+
let shared = 0;
|
|
875
|
+
while (shared < prevKeys.length && shared < nextKeys.length && prevKeys[shared] === nextKeys[shared])
|
|
876
|
+
shared++;
|
|
877
|
+
const tail = nextKeys.slice(shared).map((s) => JSON.parse(s));
|
|
878
|
+
const record = shared === 0
|
|
879
|
+
? { bivyKind: "base", reset: true, createdAt: Date.now(), messages: tail }
|
|
880
|
+
: { bivyKind: "base", reset: false, createdAt: Date.now(), messages: tail, ...(shared < prevKeys.length ? { keep: shared } : {}) };
|
|
774
881
|
this.baseKeys.set(id, nextKeys);
|
|
775
882
|
this.enqueue(id, this.syntheticKey(id), record);
|
|
776
883
|
}
|
|
@@ -10,27 +10,21 @@
|
|
|
10
10
|
// otherwise get the *viewer's* browser/IP to hit an arbitrary URL by embedding
|
|
11
11
|
// it in a reply).
|
|
12
12
|
//
|
|
13
|
-
// This is the PURE-ish, testable half —
|
|
14
|
-
//
|
|
15
|
-
//
|
|
13
|
+
// This is the PURE-ish, testable half — host/SSRF validation and the guarded
|
|
14
|
+
// fetch itself — all dependency-injectable so tests never hit the real network
|
|
15
|
+
// or DNS. Which references a message makes at all is the renderer's and this
|
|
16
|
+
// module's shared concern, so it lives in ./message-components.js (the same file
|
|
17
|
+
// @bivy/core compiles). The server half (src/server.ts) owns the AttachmentStore
|
|
16
18
|
// write, the durable event-log ref, and the live broadcast; see resolveInlineImages.
|
|
17
19
|
import dns from "node:dns/promises";
|
|
18
20
|
import { hostnameIsLocal } from "../auth.js";
|
|
19
21
|
import { sanitizeAttachmentName, sniffMime } from "./attach-to-chat.js";
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
|
|
26
|
-
* src/ephemeral-exec.ts for its cross-copy host allowlist. If you change one,
|
|
27
|
-
* change the other.
|
|
28
|
-
*/
|
|
29
|
-
const INLINE_IMAGE_MD_RE = /!\[[^\]]*\]\((https:\/\/[^)\s]+)\)/g;
|
|
30
|
-
/** Bound how many distinct remote images a single message can trigger a fetch
|
|
31
|
-
* for — a pathological/malicious message can't fan out into an unbounded
|
|
32
|
-
* number of outbound requests. */
|
|
33
|
-
export const MAX_INLINE_IMAGES_PER_MESSAGE = 6;
|
|
22
|
+
import { extractImageReferences, extractMessageReferences, MAX_COMPONENTS_PER_MESSAGE } from "./message-components.js";
|
|
23
|
+
/** Bound how many distinct images a single message can trigger a resolve for —
|
|
24
|
+
* a pathological/malicious message can't fan out into an unbounded number of
|
|
25
|
+
* outbound requests or file reads. Re-exported from the shared grammar, which
|
|
26
|
+
* the renderer applies the same cap from. */
|
|
27
|
+
export const MAX_INLINE_IMAGES_PER_MESSAGE = MAX_COMPONENTS_PER_MESSAGE;
|
|
34
28
|
/** Ceiling for a single fetched inline image. Smaller than
|
|
35
29
|
* MAX_AGENT_ATTACHMENT_BYTES (attach-to-chat.ts) because these bytes come from
|
|
36
30
|
* an arbitrary, untrusted remote origin rather than the local workspace —
|
|
@@ -40,23 +34,20 @@ export const MAX_INLINE_IMAGE_BYTES = 8 * 1024 * 1024;
|
|
|
40
34
|
const FETCH_TIMEOUT_MS = 10_000;
|
|
41
35
|
/** Hard cap on redirect hops, mirroring execEphemeralRequest's guard. */
|
|
42
36
|
const MAX_REDIRECTS = 5;
|
|
43
|
-
/**
|
|
44
|
-
* ``, in first-seen order, capped at MAX_INLINE_IMAGES_PER_MESSAGE.
|
|
37
|
+
/** The distinct `https://` URLs a message's raw markdown references via
|
|
38
|
+
* ``, in first-seen order, capped at MAX_INLINE_IMAGES_PER_MESSAGE.
|
|
39
|
+
* Workspace-relative references are a different resolver's job (they are read,
|
|
40
|
+
* not fetched) — see resolveWorkspaceImageRefs in transcript-persistence.ts. */
|
|
45
41
|
export function extractInlineImageUrls(text) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
out.push(url);
|
|
56
|
-
if (out.length >= MAX_INLINE_IMAGES_PER_MESSAGE)
|
|
57
|
-
break;
|
|
58
|
-
}
|
|
59
|
-
return out;
|
|
42
|
+
return extractImageReferences(text).filter((i) => i.origin === "remote").map((i) => i.ref);
|
|
43
|
+
}
|
|
44
|
+
/** The distinct workspace references a message makes — from image syntax or a
|
|
45
|
+
* `::view{src=…}` directive — in first-seen order, under the same per-message
|
|
46
|
+
* cap. `syntax` tells the resolver whether non-image bytes are acceptable. */
|
|
47
|
+
export function extractWorkspaceRefs(text) {
|
|
48
|
+
return extractMessageReferences(text)
|
|
49
|
+
.filter((r) => r.origin === "workspace")
|
|
50
|
+
.map(({ ref, syntax }) => ({ ref, syntax }));
|
|
60
51
|
}
|
|
61
52
|
/** Best-effort plain text for an assistant RuntimeMessage's `content`, which is
|
|
62
53
|
* either a plain string or an array of typed blocks (`{type:"text", text}`
|