memhouse 0.20.1 → 0.20.3
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/CHANGELOG.md +92 -0
- package/bin/memhouse.js +12 -4
- package/docs/guides/share.md +1 -0
- package/docs/releases/README.md +2 -0
- package/docs/releases/v0.20.2.md +134 -0
- package/docs/releases/v0.20.3.md +111 -0
- package/editors/normalise/agy.js +16 -10
- package/editors/normalise/copilot-cli.js +7 -6
- package/editors/normalise/cursor-agent.js +13 -7
- package/editors/normalise/cursor.js +2 -1
- package/editors/normalise/devin.js +2 -1
- package/editors/normalise/fsx.js +99 -0
- package/editors/normalise/gemini-cli.js +23 -13
- package/editors/normalise/opencode.js +10 -9
- package/editors/normalise/vscode.js +19 -13
- package/editors/normalise/zed.js +23 -16
- package/editors/sqlite.js +18 -8
- package/memhouse/house/q-cli.js +14 -1
- package/memhouse/house/records-to-profile.js +14 -3
- package/memhouse/house/schema-profile-views.sql.tpl +53 -0
- package/memhouse/house/schema-profile.js +46 -6
- package/memhouse/house/schema-profile.sql.tpl +8 -1
- package/memhouse/share.js +5 -3
- package/memhouse/shipper/capture.js +60 -10
- package/memhouse/shipper/log-adapter.js +18 -7
- package/memhouse/shipper/normalise-run.js +31 -6
- package/memhouse/shipper/passlog.js +22 -2
- package/memhouse/shipper/profile-capture.js +64 -21
- package/package.json +2 -2
- package/ui/package-lock.json +264 -318
- package/ui/package.json +5 -4
|
@@ -7,13 +7,16 @@
|
|
|
7
7
|
// THE PASS (each line is a decision; the rest is adapter-capture.js's)
|
|
8
8
|
// archive session_units: every unit verbatim (enc, sha256, redacted, orig_sha256), its body in
|
|
9
9
|
// 1 MiB chunks; and a read manifest (unit_key '', enc 'manifest') for every read that
|
|
10
|
-
// opens a generation or delivers units or gone keys (harness-sessions-sql §4.1).
|
|
11
|
-
// opens a generation
|
|
10
|
+
// opens a generation or delivers units or gone keys (harness-sessions-sql §4.1). A read
|
|
11
|
+
// opens a generation when it is the source's first, a reset or a snapshot (§4.1): a
|
|
12
|
+
// changed snapshot's one unit goes into a generation of its own.
|
|
12
13
|
// An archive write carries one captured_at, set by the capture and later than the
|
|
13
14
|
// session's last one (kept in the local state as archivedAt; on a refold, the
|
|
14
15
|
// archive's latest), so a capped read's steps stay apart and a clock that steps back
|
|
15
16
|
// between passes never reorders them; never left to the server's default, which a
|
|
16
|
-
// parallel parse of a big insert may evaluate per chunk: it is the replay's pass id.
|
|
17
|
+
// parallel parse of a big insert may evaluate per chunk: it is the replay's pass id. The one
|
|
18
|
+
// exception is the re-run of a pass that stopped before its cursor write: it writes under
|
|
19
|
+
// that pass's captured_at, so the two replay as one pass (header: crash). A write that fits one batch is one statement, manifests last;
|
|
17
20
|
// a bigger one writes its units first (~8 MiB a statement), then all its manifests in
|
|
18
21
|
// one statement however big: a manifest never lands before its units (a stop between
|
|
19
22
|
// the two leaves units no manifest names, which the fold ignores and the re-read
|
|
@@ -35,9 +38,13 @@
|
|
|
35
38
|
// statement holds a pass's manifests, header: archive). Each pass reads the unit
|
|
36
39
|
// versions it needs and does not hold, at most REPLAY_MAX_BYTES (32 MiB) and 1,000
|
|
37
40
|
// units a response, and keeps only what the fold holds, so a unit edited at every pass
|
|
38
|
-
// is not read or held in all its versions at once. A pass
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
+
// is not read or held in all its versions at once. A pass that stopped before its
|
|
42
|
+
// cursor write cannot be told from one that did not by the archive alone; its re-run,
|
|
43
|
+
// which sees the archive ahead of the cursor, writes under the stopped pass's
|
|
44
|
+
// captured_at (header: crash), so the stopped read and the re-run replay as one pass.
|
|
45
|
+
// Each source's input to a pass's derive is that pass's net change to its fold: with
|
|
46
|
+
// one manifest a source, as every other pass has, exactly that manifest's units and gone
|
|
47
|
+
// keys; across a stopped read and its re-run, a unit the re-run took back is no change
|
|
41
48
|
// re-derive an upgrade's refold (harness-sessions-sql §8: a new adapter_ver replayed from session_units
|
|
42
49
|
// alone) replays the archive the same way, under the new version, over the house's rows:
|
|
43
50
|
// every row it makes, current or a removed version, is written above the house's revs at
|
|
@@ -84,13 +91,22 @@
|
|
|
84
91
|
// crash the local state is dropped before the first write and saved after the last, so a
|
|
85
92
|
// pass that stops anywhere leaves none, and the next one refolds from the house. A
|
|
86
93
|
// refold reads the archive's manifests first: when a stopped pass left them ahead of the
|
|
87
|
-
// cursor, its generation, progress and delta fold continue from the archive (a reset
|
|
88
|
-
// opens the generation past the archive's), so no two manifests of a
|
|
89
|
-
// progress and the fold is what the archive says. A source re-read
|
|
90
|
-
// delivers exactly what the generation the stopped pass opened holds
|
|
91
|
-
// cursor yet, or a
|
|
92
|
-
// nothing: only its position moves. So does a stream re-read
|
|
93
|
-
// delivers only what the stopped pass appended to the cursor's
|
|
94
|
+
// cursor, its generation, progress and delta fold continue from the archive (a reset or
|
|
95
|
+
// a snapshot opens the generation past the archive's), so no two manifests of a
|
|
96
|
+
// generation share a progress and the fold is what the archive says. A source re-read
|
|
97
|
+
// from scratch that delivers exactly what the generation the stopped pass opened holds
|
|
98
|
+
// (its own read: no cursor yet, a reset or a snapshot; never a source_gone generation)
|
|
99
|
+
// stays in it and archives nothing: only its position moves. So does a stream re-read
|
|
100
|
+
// from the cursor that delivers only what the stopped pass appended to the cursor's
|
|
101
|
+
// generation, and a snapshot that delivers what a stopped pass archived in the cursor's
|
|
102
|
+
// own generation (an archive from before a snapshot opened a generation). The re-run
|
|
103
|
+
// writes its archive under the stopped pass's captured_at (archive), so a replay folds
|
|
104
|
+
// the stopped read and the re-run as one pass: a unit the stopped read delivered that the
|
|
105
|
+
// re-run no longer sees is never derived, as in a clean run of what was committed
|
|
106
|
+
// (harness-sessions-sql SPEC-QUESTIONS q20). A stop past the rows has already written the
|
|
107
|
+
// stopped read's records: the re-run keeps any it no longer sees as removed versions,
|
|
108
|
+
// which a replay does not make (nor does the reference engine's), and an upgrade's
|
|
109
|
+
// re-derive tombstones them
|
|
94
110
|
// edges after the rows, before the cursor (harness-sessions-sql §5 step 4): the engine edges the session
|
|
95
111
|
// is the fork or the origin of (profile-edges.js), so the rollup bills a fork's copied
|
|
96
112
|
// usage once. A fork whose edge moved and is not this session gets its sessions row
|
|
@@ -284,6 +300,9 @@ async function captureSession(ctx, ad, d, store, listed) {
|
|
|
284
300
|
...archOnly.filter((sk) => ours.has(sk) && !A.get(sk).gone).map((sk) => ({ source_key: sk, mode: '', role: 'main', omitted: true })),
|
|
285
301
|
];
|
|
286
302
|
const confirmed = new Set(); // omitted sources the adapter answered are not there
|
|
303
|
+
// The captured_at of a pass that stopped before its cursor write (an archive ahead of the cursor): this
|
|
304
|
+
// re-run writes under it (header: crash). null when no source is ahead.
|
|
305
|
+
let stoppedAt = null;
|
|
287
306
|
for (const src of [...listed.sources, ...omitted]) {
|
|
288
307
|
const sk = src.source_key;
|
|
289
308
|
const h = H.get(sk);
|
|
@@ -306,6 +325,7 @@ async function captureSession(ctx, ad, d, store, listed) {
|
|
|
306
325
|
// Only a generation the stopped pass opened (newer than the cursor's) and that holds a read
|
|
307
326
|
// (not a source_gone one) can be the one a re-read from scratch repeats.
|
|
308
327
|
resumed = a.gen > hGen && !a.gone ? a.fold : null;
|
|
328
|
+
if (a.at && (stoppedAt === null || String(a.at) > stoppedAt)) stoppedAt = String(a.at);
|
|
309
329
|
appendedTo = a.gen === hGen && !a.gone ? { fold: a.fold, progress: String(a.progress) } : null;
|
|
310
330
|
} else prior = h && !goneOf(h) ? { gen: Number(h.generation), progress: String(h.progress), position: parse(h.position, null), known: parse(h.known, {}) } : null;
|
|
311
331
|
}
|
|
@@ -339,7 +359,15 @@ async function captureSession(ctx, ad, d, store, listed) {
|
|
|
339
359
|
units = r.units; gone = []; knownNext = {}; full = true; opens = true;
|
|
340
360
|
break;
|
|
341
361
|
case 'snapshot':
|
|
342
|
-
|
|
362
|
+
// A changed snapshot opens the next generation (harness-sessions-sql §4.1), unless it is the
|
|
363
|
+
// stopped pass's own read again (header: crash): the unit a generation the stopped pass opened
|
|
364
|
+
// holds, or one it archived in the cursor's own generation (before a snapshot opened one).
|
|
365
|
+
if ((resumed && sameUnits(r.units, resumed)) || (appendedTo && sameUnits(r.units, appendedTo.fold))) {
|
|
366
|
+
reread = r.units; knownNext = Object.fromEntries(resumed || appendedTo.fold); full = true;
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
gen = prior ? gen + 1 : gen;
|
|
370
|
+
units = r.units; knownNext = {}; opens = true;
|
|
343
371
|
break;
|
|
344
372
|
case 'appended': case 'delta':
|
|
345
373
|
units = r.units;
|
|
@@ -368,7 +396,7 @@ async function captureSession(ctx, ad, d, store, listed) {
|
|
|
368
396
|
}
|
|
369
397
|
sources[sk] = { gen, progress, position: r.position ?? null, known: src.mode === 'stream' ? {} : knownNext, mode: src.mode || '', role: src.role || 'main' };
|
|
370
398
|
if (changed) fresh.push({ source_key: sk, generation: gen, complete: false, units, gone });
|
|
371
|
-
if (!prior || r.continuity === 'reset') {
|
|
399
|
+
if (!prior || r.continuity === 'reset' || r.continuity === 'snapshot') {
|
|
372
400
|
const byKey = new Map();
|
|
373
401
|
for (const u of reread || units) { const k = String(u.unit_key); byKey.delete(k); byKey.set(k, { ...u, unit_key: k }); }
|
|
374
402
|
opened.set(sk, { gen, units: [...byKey.values()] });
|
|
@@ -501,7 +529,9 @@ async function captureSession(ctx, ad, d, store, listed) {
|
|
|
501
529
|
last = known.length || local == null ? Math.max(0, ...known)
|
|
502
530
|
: (typeof ctx.house.archivedAt === 'function' ? await ctx.house.archivedAt(ref) : 0);
|
|
503
531
|
}
|
|
504
|
-
|
|
532
|
+
// The re-run of a stopped pass writes under that pass's captured_at (header: crash); every other
|
|
533
|
+
// write is later than the session's last one.
|
|
534
|
+
archivedAt = stoppedAt !== null ? Date.parse(`${stoppedAt.replace(' ', 'T')}Z`) : Math.max(Number(ctx.now()), last + 1);
|
|
505
535
|
for (const r of archive) r.captured_at = base.chTs(archivedAt);
|
|
506
536
|
const units = archive.filter((r) => r.enc !== 'manifest');
|
|
507
537
|
const manifests = archive.filter((r) => r.enc === 'manifest');
|
|
@@ -792,8 +822,9 @@ async function replayArchive(ctx, ad, d, key, ref, { adapter, adapterVer, order,
|
|
|
792
822
|
const byOrder = (a, b) => (rank.has(a) ? rank.get(a) : order.length) - (rank.has(b) ? rank.get(b) : order.length) || (a < b ? -1 : a > b ? 1 : 0);
|
|
793
823
|
const sources = new Map();
|
|
794
824
|
// The units a pass needs and does not hold yet are read for that pass (paged by pass), and only
|
|
795
|
-
// what the fold still holds is kept after it: a
|
|
796
|
-
//
|
|
825
|
+
// what the fold still holds is kept after it: a delta source keeps one generation and adds a
|
|
826
|
+
// version of a unit at every edit (so did a snapshot source, in an archive from before a snapshot
|
|
827
|
+
// opened a generation), so holding (or reading at once) every version a
|
|
797
828
|
// generation ever named grows with the session's history. A read is bounded by bytes, at most
|
|
798
829
|
// `maxBytes` of the units' archived text and 1,000 units a response (one unit larger than that
|
|
799
830
|
// alone); the sizes of every unit a generation's manifests name are read once, with that
|
|
@@ -863,12 +894,24 @@ async function replayArchive(ctx, ad, d, key, ref, { adapter, adapterVer, order,
|
|
|
863
894
|
let view = new Map();
|
|
864
895
|
for (const pass of passes) {
|
|
865
896
|
let full = state === null;
|
|
866
|
-
|
|
897
|
+
// Each source's input is the pass's net change to its fold (header: replay): with one manifest a
|
|
898
|
+
// source, that manifest's units and gone keys; across a stopped read and its re-run (one captured_at,
|
|
899
|
+
// header: crash), a unit the re-run took back is no change, and is never derived.
|
|
900
|
+
const before = new Map();
|
|
867
901
|
for (const m of pass) {
|
|
902
|
+
if (!before.has(m.source_key)) { const s = sources.get(m.source_key); before.set(m.source_key, s ? { generation: s.generation, gone: s.gone, units: new Map(s.units) } : null); }
|
|
868
903
|
foldManifest(sources, m);
|
|
869
904
|
if (m.source_gone ? sources.get(m.source_key).gone !== 'kept' : m.continuity === 'reset') full = true;
|
|
870
|
-
|
|
871
|
-
|
|
905
|
+
}
|
|
906
|
+
const fresh = [];
|
|
907
|
+
if (!full) {
|
|
908
|
+
for (const [sk, b] of before) {
|
|
909
|
+
const s = sources.get(sk);
|
|
910
|
+
if (s.gone) continue; // a kept vanish: its units stay as they were
|
|
911
|
+
const same = !!b && !b.gone && b.generation === s.generation;
|
|
912
|
+
const units = [...s.units].filter(([uk, sha]) => !same || b.units.get(uk) !== sha);
|
|
913
|
+
const gone = same ? [...b.units.keys()].filter((uk) => !s.units.has(uk)) : [];
|
|
914
|
+
if (units.length || gone.length) fresh.push({ source_key: sk, generation: s.generation, complete: false, units: await unitsAt(sk, s.generation, units), gone });
|
|
872
915
|
}
|
|
873
916
|
}
|
|
874
917
|
fresh.sort((a, b) => byOrder(a.source_key, b.source_key));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memhouse",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.3",
|
|
4
4
|
"description": "memhouse — agent conversation memory across 17 editors: parse-on-client shippers, a typed ClickHouse store with one set of rooms per member, and the agentlytics dashboard",
|
|
5
5
|
"main": "bin/memhouse.js",
|
|
6
6
|
"bin": {
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"email": "security@memhouse.io"
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
|
-
"@agent-realm/harness-session-adapters": "1.
|
|
69
|
+
"@agent-realm/harness-session-adapters": "1.1.0",
|
|
70
70
|
"@clickhouse/client": "^1.23.1",
|
|
71
71
|
"express": "^4.22.1"
|
|
72
72
|
}
|