@fgv/ts-agent-memory 5.1.0-51 → 5.1.0-52
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/packlets/store/fileTreeMemoryStore.js +24 -100
- package/dist/packlets/store/fileTreeMemoryStore.js.map +1 -1
- package/dist/packlets/store/storeFileAccess.js +99 -0
- package/dist/packlets/store/storeFileAccess.js.map +1 -0
- package/dist/packlets/store/storeIdentity.js +26 -0
- package/dist/packlets/store/storeIdentity.js.map +1 -1
- package/dist/packlets/vector/inMemoryFragmentCosineIndex.js +0 -19
- package/dist/packlets/vector/inMemoryFragmentCosineIndex.js.map +1 -1
- package/dist/ts-agent-memory.d.ts +0 -36
- package/lib/packlets/store/fileTreeMemoryStore.d.ts +0 -17
- package/lib/packlets/store/fileTreeMemoryStore.d.ts.map +1 -1
- package/lib/packlets/store/fileTreeMemoryStore.js +23 -99
- package/lib/packlets/store/fileTreeMemoryStore.js.map +1 -1
- package/lib/packlets/store/storeFileAccess.d.ts +40 -0
- package/lib/packlets/store/storeFileAccess.d.ts.map +1 -0
- package/lib/packlets/store/storeFileAccess.js +105 -0
- package/lib/packlets/store/storeFileAccess.js.map +1 -0
- package/lib/packlets/store/storeIdentity.d.ts +25 -0
- package/lib/packlets/store/storeIdentity.d.ts.map +1 -1
- package/lib/packlets/store/storeIdentity.js +27 -0
- package/lib/packlets/store/storeIdentity.js.map +1 -1
- package/lib/packlets/vector/inMemoryFragmentCosineIndex.d.ts +0 -19
- package/lib/packlets/vector/inMemoryFragmentCosineIndex.d.ts.map +1 -1
- package/lib/packlets/vector/inMemoryFragmentCosineIndex.js +0 -19
- package/lib/packlets/vector/inMemoryFragmentCosineIndex.js.map +1 -1
- package/package.json +7 -7
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.FileTreeMemoryStore = void 0;
|
|
8
8
|
const ts_utils_1 = require("@fgv/ts-utils");
|
|
9
|
-
const ts_json_base_1 = require("@fgv/ts-json-base");
|
|
10
9
|
const types_1 = require("../types");
|
|
11
10
|
const converters_1 = require("../converters");
|
|
12
11
|
const vectorMaintenance_1 = require("./vectorMaintenance");
|
|
13
12
|
const storeCoverage_1 = require("./storeCoverage");
|
|
14
13
|
const storeIdentity_1 = require("./storeIdentity");
|
|
14
|
+
const storeFileAccess_1 = require("./storeFileAccess");
|
|
15
15
|
const storeReconcile_1 = require("./storeReconcile");
|
|
16
16
|
const index_1 = require("../index");
|
|
17
17
|
const scopeEncoding_1 = require("./scopeEncoding");
|
|
@@ -132,9 +132,9 @@ class FileTreeMemoryStore {
|
|
|
132
132
|
// version whose `invalid_at` is null/absent) from the entity subtree,
|
|
133
133
|
// read off the derived index. `asOf` resolution is via `list({ asOf })`
|
|
134
134
|
// and the temporal retrievers.
|
|
135
|
-
return this._readVersionedCurrent(addr.scope);
|
|
135
|
+
return this._readVersionedCurrent(addr.scope, kind);
|
|
136
136
|
}
|
|
137
|
-
return this._readRecord(addr.scope, addr.idStem);
|
|
137
|
+
return this._readRecord(addr.scope, addr.idStem, kind);
|
|
138
138
|
}));
|
|
139
139
|
await this._fireObservation('read', kind, entityId, {
|
|
140
140
|
outcome: result.isSuccess() ? 'success' : 'failure',
|
|
@@ -493,7 +493,7 @@ class FileTreeMemoryStore {
|
|
|
493
493
|
return (0, ts_utils_1.succeed)({ record: duplicate.value, evicted: [] });
|
|
494
494
|
}
|
|
495
495
|
}
|
|
496
|
-
return this._readRecord(scope, idStem).thenOnSuccess((existing) => {
|
|
496
|
+
return this._readRecord(scope, idStem, record.envelope.kind).thenOnSuccess((existing) => {
|
|
497
497
|
// Same-id re-put is a no-op ONLY when the content hash matches AND the
|
|
498
498
|
// mutable metadata is also unchanged. The content hash covers
|
|
499
499
|
// { kind, body, links }; a matching hash with revised tags/provenance is a
|
|
@@ -619,7 +619,7 @@ class FileTreeMemoryStore {
|
|
|
619
619
|
/** Serialize and write a fully-stamped record, then patch the index. */
|
|
620
620
|
_persist(record, scope, idStem) {
|
|
621
621
|
return (0, converters_1.serializeMemoryFile)(record.envelope, record.body)
|
|
622
|
-
.onSuccess((raw) => this.
|
|
622
|
+
.onSuccess((raw) => (0, storeFileAccess_1.writeRecordFile)(this._root, this._scopeEncoding, scope, idStem, raw))
|
|
623
623
|
.onSuccess(() => this._index.patch('put', { scope, record }))
|
|
624
624
|
.onSuccess(() => (0, ts_utils_1.succeed)(record));
|
|
625
625
|
}
|
|
@@ -629,9 +629,9 @@ class FileTreeMemoryStore {
|
|
|
629
629
|
if (!(0, types_1.isTemporalIdentityCodec)(codec)) {
|
|
630
630
|
return Promise.resolve((0, ts_utils_1.fail)(`memory delete '${entityId}': codec for versioned kind '${kind}' does not implement the temporal codec interface`));
|
|
631
631
|
}
|
|
632
|
-
return this._deleteVersioned(entityId, addr.scope);
|
|
632
|
+
return this._deleteVersioned(entityId, addr.scope, kind);
|
|
633
633
|
}
|
|
634
|
-
return this._deleteFlat(entityId, addr.scope, addr.idStem);
|
|
634
|
+
return this._deleteFlat(entityId, addr.scope, addr.idStem, kind);
|
|
635
635
|
}));
|
|
636
636
|
}
|
|
637
637
|
/**
|
|
@@ -639,15 +639,15 @@ class FileTreeMemoryStore {
|
|
|
639
639
|
* entry, then prune the vector best-effort. Structurally unchanged from the
|
|
640
640
|
* pre-temporal delete path.
|
|
641
641
|
*/
|
|
642
|
-
async _deleteFlat(entityId, scope, idStem) {
|
|
643
|
-
return this._readRecord(scope, idStem).thenOnSuccess((existing) => {
|
|
642
|
+
async _deleteFlat(entityId, scope, idStem, kind) {
|
|
643
|
+
return this._readRecord(scope, idStem, kind).thenOnSuccess((existing) => {
|
|
644
644
|
if (existing === undefined) {
|
|
645
645
|
return Promise.resolve((0, ts_utils_1.fail)(`memory delete '${entityId}': no record found`));
|
|
646
646
|
}
|
|
647
647
|
// Delete the record file + index entry (authoritative), then prune the
|
|
648
648
|
// vector best-effort: a committed delete must not fail because the
|
|
649
649
|
// derived index could not be pruned.
|
|
650
|
-
return this.
|
|
650
|
+
return (0, storeFileAccess_1.deleteRecordFile)(this._root, this._scopeEncoding, scope, idStem)
|
|
651
651
|
.onSuccess(() => this._index.patch('delete', { scope, record: existing }))
|
|
652
652
|
.thenOnSuccess(async () => {
|
|
653
653
|
await this._vectors.removeAll({ scope, id: existing.envelope.id });
|
|
@@ -661,7 +661,7 @@ class FileTreeMemoryStore {
|
|
|
661
661
|
* null/absent. `undefined` when the entity has no current version (never
|
|
662
662
|
* written, or fully invalidated / soft-deleted).
|
|
663
663
|
*/
|
|
664
|
-
_readVersionedCurrent(scope) {
|
|
664
|
+
_readVersionedCurrent(scope, expectedKind) {
|
|
665
665
|
// Select over ENVELOPES, then materialize the one winner — which is what
|
|
666
666
|
// `IMemoryStore.get`'s docstring promises and what `selectCurrentVersion`
|
|
667
667
|
// being generic over `IEnvelopeCarrier` exists for. Materializing every
|
|
@@ -671,21 +671,21 @@ class FileTreeMemoryStore {
|
|
|
671
671
|
if (current === undefined) {
|
|
672
672
|
return (0, ts_utils_1.succeed)(undefined);
|
|
673
673
|
}
|
|
674
|
-
return this._resolveRequired(current);
|
|
674
|
+
return this._resolveRequired(current).onSuccess((materialized) => (0, storeIdentity_1.verifyOccupantKind)(expectedKind, scope, materialized.envelope.id, materialized));
|
|
675
675
|
}
|
|
676
676
|
/**
|
|
677
677
|
* Every persisted version of the entity whose subtree is `scope`. All version
|
|
678
678
|
* files for one entity live under exactly that scope (which encodes the
|
|
679
679
|
* entityId), so a scope filter over the index isolates one entity's versions.
|
|
680
680
|
*/
|
|
681
|
-
_versionsForEntity(scope) {
|
|
681
|
+
_versionsForEntity(scope, expectedKind) {
|
|
682
682
|
// Selected on the envelope (scope), materialized after — so a versioned write
|
|
683
683
|
// reads only that entity's versions, never the vault. Bounded by the entity's
|
|
684
684
|
// version count.
|
|
685
685
|
return (0, ts_utils_1.mapResults)(this._index
|
|
686
686
|
.entries()
|
|
687
687
|
.filter((entry) => entry.scope === scope)
|
|
688
|
-
.map((entry) => this._resolveRequired(entry)));
|
|
688
|
+
.map((entry) => this._resolveRequired(entry).onSuccess((r) => (0, storeIdentity_1.verifyOccupantKind)(expectedKind, scope, r.envelope.id, r))));
|
|
689
689
|
}
|
|
690
690
|
/**
|
|
691
691
|
* Versioned write (invalidate-don't-delete). Builds the new version's content
|
|
@@ -707,7 +707,7 @@ class FileTreeMemoryStore {
|
|
|
707
707
|
// still-current version at snapshot time — normally one, but two-or-more if a
|
|
708
708
|
// prior invalidation partially failed; invalidating all of them lets the write
|
|
709
709
|
// self-heal a stuck state (P2-7).
|
|
710
|
-
const snapshot = this._versionsForEntity(scope);
|
|
710
|
+
const snapshot = this._versionsForEntity(scope, kind);
|
|
711
711
|
if (snapshot.isFailure()) {
|
|
712
712
|
return (0, ts_utils_1.fail)(snapshot.message);
|
|
713
713
|
}
|
|
@@ -836,8 +836,8 @@ class FileTreeMemoryStore {
|
|
|
836
836
|
* kinds exist to preserve the audit trail (and the L3 `contradicts` interlock
|
|
837
837
|
* builds on it), so a hard delete would defeat the purpose.
|
|
838
838
|
*/
|
|
839
|
-
async _deleteVersioned(entityId, scope) {
|
|
840
|
-
const snapshot = this._versionsForEntity(scope);
|
|
839
|
+
async _deleteVersioned(entityId, scope, kind) {
|
|
840
|
+
const snapshot = this._versionsForEntity(scope, kind);
|
|
841
841
|
if (snapshot.isFailure()) {
|
|
842
842
|
return (0, ts_utils_1.fail)(snapshot.message);
|
|
843
843
|
}
|
|
@@ -860,7 +860,7 @@ class FileTreeMemoryStore {
|
|
|
860
860
|
if (existing === undefined) {
|
|
861
861
|
return (0, ts_utils_1.fail)(`memory put: cannot evict '${id}' in scope '${scope}': not found`);
|
|
862
862
|
}
|
|
863
|
-
return this.
|
|
863
|
+
return (0, storeFileAccess_1.deleteRecordFile)(this._root, this._scopeEncoding, scope, id)
|
|
864
864
|
.onSuccess(() => this._index.patch('delete', { scope, record: existing }))
|
|
865
865
|
.onSuccess(() => (0, ts_utils_1.succeed)(id));
|
|
866
866
|
});
|
|
@@ -1010,7 +1010,7 @@ class FileTreeMemoryStore {
|
|
|
1010
1010
|
* already-consistent store touches no files.
|
|
1011
1011
|
*/
|
|
1012
1012
|
_rewriteEnvelope(scope, id, mutate) {
|
|
1013
|
-
return this.
|
|
1013
|
+
return (0, storeFileAccess_1.resolveScopeDir)(this._root, this._scopeEncoding, scope).onSuccess((scopeDir) => {
|
|
1014
1014
|
/* c8 ignore next 3 - defensive: the scope dir exists for any indexed record */
|
|
1015
1015
|
if (scopeDir === undefined) {
|
|
1016
1016
|
return (0, ts_utils_1.fail)(`'${id}': scope '${scope}' not found`);
|
|
@@ -1105,8 +1105,8 @@ class FileTreeMemoryStore {
|
|
|
1105
1105
|
* when the scope directory or file is absent. Verifies the on-disk id ↔
|
|
1106
1106
|
* filename round-trip on every load.
|
|
1107
1107
|
*/
|
|
1108
|
-
_readRecord(scope, idStem) {
|
|
1109
|
-
return this.
|
|
1108
|
+
_readRecord(scope, idStem, expectedKind) {
|
|
1109
|
+
return (0, storeFileAccess_1.resolveScopeDir)(this._root, this._scopeEncoding, scope).onSuccess((scopeDir) => {
|
|
1110
1110
|
if (scopeDir === undefined) {
|
|
1111
1111
|
return (0, ts_utils_1.succeed)(undefined);
|
|
1112
1112
|
}
|
|
@@ -1119,7 +1119,8 @@ class FileTreeMemoryStore {
|
|
|
1119
1119
|
return file
|
|
1120
1120
|
.getRawContents()
|
|
1121
1121
|
.onSuccess((raw) => (0, converters_1.parseMemoryFile)(raw, this._registry))
|
|
1122
|
-
.onSuccess((parsedRecord) => this._verifyLoaded(scope, file, parsedRecord))
|
|
1122
|
+
.onSuccess((parsedRecord) => this._verifyLoaded(scope, file, parsedRecord))
|
|
1123
|
+
.onSuccess((loaded) => (0, storeIdentity_1.verifyOccupantKind)(expectedKind, scope, idStem, loaded));
|
|
1123
1124
|
});
|
|
1124
1125
|
});
|
|
1125
1126
|
}
|
|
@@ -1135,83 +1136,6 @@ class FileTreeMemoryStore {
|
|
|
1135
1136
|
_verifyLoaded(scope, file, record) {
|
|
1136
1137
|
return (0, storeIdentity_1.verifyLoadedIdentity)(this._codec(record.envelope.kind), scope, file, record);
|
|
1137
1138
|
}
|
|
1138
|
-
/**
|
|
1139
|
-
* Resolve the directory for a scope, returning `undefined` when it does not
|
|
1140
|
-
* exist. Navigation only — does not create. Folds the path segments through
|
|
1141
|
-
* `getChildren` so an absent segment short-circuits to `undefined`.
|
|
1142
|
-
*/
|
|
1143
|
-
_resolveScopeDir(scope) {
|
|
1144
|
-
return this._scopeEncoding(scope).onSuccess((encoded) => {
|
|
1145
|
-
const segments = encoded.split('/').filter((s) => s.length > 0);
|
|
1146
|
-
return segments.reduce((acc, segment) => acc.onSuccess((current) => {
|
|
1147
|
-
if (current === undefined) {
|
|
1148
|
-
return (0, ts_utils_1.succeed)(undefined);
|
|
1149
|
-
}
|
|
1150
|
-
return current
|
|
1151
|
-
.getChildren()
|
|
1152
|
-
.onSuccess((children) => (0, ts_utils_1.succeed)(children.find((c) => c.type === 'directory' && c.name === segment)));
|
|
1153
|
-
}), (0, ts_utils_1.succeed)(this._root));
|
|
1154
|
-
});
|
|
1155
|
-
}
|
|
1156
|
-
/** Ensure the scope directory exists, creating segments as needed. */
|
|
1157
|
-
_ensureScopeDir(scope) {
|
|
1158
|
-
return this._scopeEncoding(scope).onSuccess((encoded) => {
|
|
1159
|
-
const segments = encoded.split('/').filter((s) => s.length > 0);
|
|
1160
|
-
return segments.reduce((acc, segment) => acc.onSuccess((current) => current.getChildren().onSuccess((children) => {
|
|
1161
|
-
const existing = children.find((c) => c.type === 'directory' && c.name === segment);
|
|
1162
|
-
if (existing === undefined) {
|
|
1163
|
-
return current.createChildDirectory(segment);
|
|
1164
|
-
}
|
|
1165
|
-
/* c8 ignore next 3 -- defensive: a child of a mutable in-memory/fs tree is itself mutable; the guard protects against a read-only adapter handed in as root */
|
|
1166
|
-
if (!ts_json_base_1.FileTree.isMutableDirectoryItem(existing)) {
|
|
1167
|
-
return (0, ts_utils_1.fail)(`${existing.absolutePath}: directory is not mutable`);
|
|
1168
|
-
}
|
|
1169
|
-
return (0, ts_utils_1.succeed)(existing);
|
|
1170
|
-
})), (0, ts_utils_1.succeed)(this._root));
|
|
1171
|
-
});
|
|
1172
|
-
}
|
|
1173
|
-
/** Write (create or overwrite) `<scope>/<idStem>.md` with `raw`. */
|
|
1174
|
-
_writeFile(scope, idStem, raw) {
|
|
1175
|
-
return this._ensureScopeDir(scope).onSuccess((scopeDir) => scopeDir.getChildren().onSuccess((children) => {
|
|
1176
|
-
const fileName = `${idStem}${MEMORY_FILE_EXTENSION}`;
|
|
1177
|
-
const existing = children.find((c) => c.type === 'file' && c.name === fileName);
|
|
1178
|
-
if (existing === undefined) {
|
|
1179
|
-
return scopeDir.createChildFile(fileName, raw).onSuccess(() => (0, ts_utils_1.succeed)(true));
|
|
1180
|
-
}
|
|
1181
|
-
/* c8 ignore next 3 -- defensive: a file in a mutable tree is mutable; guards a read-only adapter */
|
|
1182
|
-
if (!ts_json_base_1.FileTree.isMutableFileItem(existing)) {
|
|
1183
|
-
return (0, ts_utils_1.fail)(`${existing.absolutePath}: file is not mutable`);
|
|
1184
|
-
}
|
|
1185
|
-
return existing.setRawContents(raw).onSuccess(() => (0, ts_utils_1.succeed)(true));
|
|
1186
|
-
}));
|
|
1187
|
-
}
|
|
1188
|
-
/**
|
|
1189
|
-
* Physically delete `<scope>/<idStem>.md`. The scope-missing and file-missing
|
|
1190
|
-
* guards are unreachable through the callers (`delete` / `_evict` both read the
|
|
1191
|
-
* record first, so the directory and file exist) but are kept so a future
|
|
1192
|
-
* direct caller degrades loudly rather than silently.
|
|
1193
|
-
*/
|
|
1194
|
-
_deleteFile(scope, idStem) {
|
|
1195
|
-
return this._resolveScopeDir(scope).onSuccess((scopeDir) => {
|
|
1196
|
-
/* c8 ignore next 3 -- unreachable: callers read the record (hence the scope dir) first */
|
|
1197
|
-
if (scopeDir === undefined) {
|
|
1198
|
-
return (0, ts_utils_1.fail)(`memory delete: scope '${scope}' not found`);
|
|
1199
|
-
}
|
|
1200
|
-
const fileName = `${idStem}${MEMORY_FILE_EXTENSION}`;
|
|
1201
|
-
return scopeDir.getChildren().onSuccess((children) => {
|
|
1202
|
-
const file = children.find((c) => c.type === 'file' && c.name === fileName);
|
|
1203
|
-
/* c8 ignore next 3 -- unreachable: callers read the record (hence the file) first */
|
|
1204
|
-
if (file === undefined) {
|
|
1205
|
-
return (0, ts_utils_1.fail)(`memory delete: file '${fileName}' not found in scope '${scope}'`);
|
|
1206
|
-
}
|
|
1207
|
-
/* c8 ignore next 3 -- defensive: a file in a mutable tree is mutable; guards a read-only adapter */
|
|
1208
|
-
if (!ts_json_base_1.FileTree.isMutableFileItem(file)) {
|
|
1209
|
-
return (0, ts_utils_1.fail)(`${file.absolutePath}: file is not mutable`);
|
|
1210
|
-
}
|
|
1211
|
-
return file.delete().onSuccess(() => (0, ts_utils_1.succeed)(true));
|
|
1212
|
-
});
|
|
1213
|
-
});
|
|
1214
|
-
}
|
|
1215
1139
|
/**
|
|
1216
1140
|
* Walk the FileTree once and rebuild the index. Also resumes the `seq`
|
|
1217
1141
|
* counter past the highest persisted `seq` so new writes stay monotonic.
|