@fgv/ts-agent-memory 5.1.0-39 → 5.1.0-41
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/.rush/temp/{b82cf6bdece20481260e6bab946179eeec9d7b46.tar.log → cbbdbe09515171b4eba8f2592be2dace1e4e8142.tar.log} +14 -2
- package/.rush/temp/chunked-rush-logs/ts-agent-memory.build.chunks.jsonl +2 -2
- package/.rush/temp/operation/build/all.log +2 -2
- package/.rush/temp/operation/build/log-chunks.jsonl +2 -2
- package/.rush/temp/operation/build/state.json +1 -1
- package/dist/packlets/converters/envelopeConverter.js +17 -3
- package/dist/packlets/converters/envelopeConverter.js.map +1 -1
- package/dist/packlets/index/memoryIndex.js +58 -10
- package/dist/packlets/index/memoryIndex.js.map +1 -1
- package/dist/packlets/ingest/cycleGuard.js +13 -6
- package/dist/packlets/ingest/cycleGuard.js.map +1 -1
- package/dist/packlets/ingest/hostStages.js.map +1 -1
- package/dist/packlets/ingest/model.js.map +1 -1
- package/dist/packlets/ingest/orchestrator.js +94 -49
- package/dist/packlets/ingest/orchestrator.js.map +1 -1
- package/dist/packlets/retrieve/hybridRetriever.js +13 -2
- package/dist/packlets/retrieve/hybridRetriever.js.map +1 -1
- package/dist/packlets/retrieve/linkTraversalRetriever.js +46 -57
- package/dist/packlets/retrieve/linkTraversalRetriever.js.map +1 -1
- package/dist/packlets/retrieve/recencyRetriever.js +3 -3
- package/dist/packlets/retrieve/recencyRetriever.js.map +1 -1
- package/dist/packlets/retrieve/retriever.js +50 -7
- package/dist/packlets/retrieve/retriever.js.map +1 -1
- package/dist/packlets/retrieve/semanticRetriever.js +9 -3
- package/dist/packlets/retrieve/semanticRetriever.js.map +1 -1
- package/dist/packlets/retrieve/structuredFilterRetriever.js +3 -3
- package/dist/packlets/retrieve/structuredFilterRetriever.js.map +1 -1
- package/dist/packlets/retrieve/tagRetriever.js +3 -3
- package/dist/packlets/retrieve/tagRetriever.js.map +1 -1
- package/dist/packlets/retrieve/temporalRetrievers.js +3 -3
- package/dist/packlets/retrieve/temporalRetrievers.js.map +1 -1
- package/dist/packlets/store/fileTreeMemoryStore.js +72 -16
- package/dist/packlets/store/fileTreeMemoryStore.js.map +1 -1
- package/dist/packlets/tools/memoryTools.js +104 -21
- package/dist/packlets/tools/memoryTools.js.map +1 -1
- package/dist/packlets/types/envelope.js +13 -1
- package/dist/packlets/types/envelope.js.map +1 -1
- package/dist/packlets/vector/inMemoryCosineIndex.js +22 -17
- package/dist/packlets/vector/inMemoryCosineIndex.js.map +1 -1
- package/dist/packlets/vector/vectorIndex.js.map +1 -1
- package/dist/test/unit/converters/antagonistRoundTrip.test.js +3 -3
- package/dist/test/unit/converters/antagonistRoundTrip.test.js.map +1 -1
- package/dist/test/unit/converters/envelopeConverter.test.js +125 -8
- package/dist/test/unit/converters/envelopeConverter.test.js.map +1 -1
- package/dist/test/unit/index/memoryIndex.test.js +87 -25
- package/dist/test/unit/index/memoryIndex.test.js.map +1 -1
- package/dist/test/unit/ingest/antagonistCycleAndParity.test.js +20 -16
- package/dist/test/unit/ingest/antagonistCycleAndParity.test.js.map +1 -1
- package/dist/test/unit/ingest/cycleGuard.test.js +28 -1
- package/dist/test/unit/ingest/cycleGuard.test.js.map +1 -1
- package/dist/test/unit/ingest/orchestrator.test.js +187 -45
- package/dist/test/unit/ingest/orchestrator.test.js.map +1 -1
- package/dist/test/unit/retrieve/linkTraversalRetriever.test.js +106 -31
- package/dist/test/unit/retrieve/linkTraversalRetriever.test.js.map +1 -1
- package/dist/test/unit/retrieve/retrievers.test.js +299 -37
- package/dist/test/unit/retrieve/retrievers.test.js.map +1 -1
- package/dist/test/unit/store/embedOnWrite.test.js +69 -12
- package/dist/test/unit/store/embedOnWrite.test.js.map +1 -1
- package/dist/test/unit/store/listScoped.test.js +109 -0
- package/dist/test/unit/store/listScoped.test.js.map +1 -0
- package/dist/test/unit/store/rankAxis.test.js +254 -0
- package/dist/test/unit/store/rankAxis.test.js.map +1 -0
- package/dist/test/unit/tools/memoryTools.test.js +280 -11
- package/dist/test/unit/tools/memoryTools.test.js.map +1 -1
- package/dist/test/unit/types/writePolicy.test.js +9 -2
- package/dist/test/unit/types/writePolicy.test.js.map +1 -1
- package/dist/test/unit/vector/inMemoryCosineIndex.test.js +95 -35
- package/dist/test/unit/vector/inMemoryCosineIndex.test.js.map +1 -1
- package/dist/test/unit/vector/vectorIndex.test.js +24 -15
- package/dist/test/unit/vector/vectorIndex.test.js.map +1 -1
- package/dist/ts-agent-memory.d.ts +384 -97
- package/etc/ts-agent-memory.api.md +64 -22
- package/lib/packlets/converters/envelopeConverter.d.ts +8 -1
- package/lib/packlets/converters/envelopeConverter.d.ts.map +1 -1
- package/lib/packlets/converters/envelopeConverter.js +18 -4
- package/lib/packlets/converters/envelopeConverter.js.map +1 -1
- package/lib/packlets/index/memoryIndex.d.ts +42 -10
- package/lib/packlets/index/memoryIndex.d.ts.map +1 -1
- package/lib/packlets/index/memoryIndex.js +58 -10
- package/lib/packlets/index/memoryIndex.js.map +1 -1
- package/lib/packlets/ingest/cycleGuard.d.ts +5 -5
- package/lib/packlets/ingest/cycleGuard.d.ts.map +1 -1
- package/lib/packlets/ingest/cycleGuard.js +13 -6
- package/lib/packlets/ingest/cycleGuard.js.map +1 -1
- package/lib/packlets/ingest/hostStages.d.ts +3 -3
- package/lib/packlets/ingest/hostStages.d.ts.map +1 -1
- package/lib/packlets/ingest/hostStages.js.map +1 -1
- package/lib/packlets/ingest/model.d.ts +25 -14
- package/lib/packlets/ingest/model.d.ts.map +1 -1
- package/lib/packlets/ingest/model.js.map +1 -1
- package/lib/packlets/ingest/orchestrator.d.ts +18 -4
- package/lib/packlets/ingest/orchestrator.d.ts.map +1 -1
- package/lib/packlets/ingest/orchestrator.js +93 -48
- package/lib/packlets/ingest/orchestrator.js.map +1 -1
- package/lib/packlets/retrieve/hybridRetriever.d.ts.map +1 -1
- package/lib/packlets/retrieve/hybridRetriever.js +12 -1
- package/lib/packlets/retrieve/hybridRetriever.js.map +1 -1
- package/lib/packlets/retrieve/linkTraversalRetriever.d.ts +18 -23
- package/lib/packlets/retrieve/linkTraversalRetriever.d.ts.map +1 -1
- package/lib/packlets/retrieve/linkTraversalRetriever.js +45 -56
- package/lib/packlets/retrieve/linkTraversalRetriever.js.map +1 -1
- package/lib/packlets/retrieve/recencyRetriever.js +2 -2
- package/lib/packlets/retrieve/recencyRetriever.js.map +1 -1
- package/lib/packlets/retrieve/retriever.d.ts +70 -11
- package/lib/packlets/retrieve/retriever.d.ts.map +1 -1
- package/lib/packlets/retrieve/retriever.js +52 -7
- package/lib/packlets/retrieve/retriever.js.map +1 -1
- package/lib/packlets/retrieve/semanticRetriever.d.ts.map +1 -1
- package/lib/packlets/retrieve/semanticRetriever.js +9 -3
- package/lib/packlets/retrieve/semanticRetriever.js.map +1 -1
- package/lib/packlets/retrieve/structuredFilterRetriever.js +2 -2
- package/lib/packlets/retrieve/structuredFilterRetriever.js.map +1 -1
- package/lib/packlets/retrieve/tagRetriever.js +2 -2
- package/lib/packlets/retrieve/tagRetriever.js.map +1 -1
- package/lib/packlets/retrieve/temporalRetrievers.js +3 -3
- package/lib/packlets/retrieve/temporalRetrievers.js.map +1 -1
- package/lib/packlets/store/fileTreeMemoryStore.d.ts +57 -3
- package/lib/packlets/store/fileTreeMemoryStore.d.ts.map +1 -1
- package/lib/packlets/store/fileTreeMemoryStore.js +72 -16
- package/lib/packlets/store/fileTreeMemoryStore.js.map +1 -1
- package/lib/packlets/tools/memoryTools.d.ts +24 -0
- package/lib/packlets/tools/memoryTools.d.ts.map +1 -1
- package/lib/packlets/tools/memoryTools.js +104 -21
- package/lib/packlets/tools/memoryTools.js.map +1 -1
- package/lib/packlets/types/envelope.d.ts +61 -8
- package/lib/packlets/types/envelope.d.ts.map +1 -1
- package/lib/packlets/types/envelope.js +14 -0
- package/lib/packlets/types/envelope.js.map +1 -1
- package/lib/packlets/vector/inMemoryCosineIndex.d.ts +9 -5
- package/lib/packlets/vector/inMemoryCosineIndex.d.ts.map +1 -1
- package/lib/packlets/vector/inMemoryCosineIndex.js +22 -17
- package/lib/packlets/vector/inMemoryCosineIndex.js.map +1 -1
- package/lib/packlets/vector/vectorIndex.d.ts +46 -20
- package/lib/packlets/vector/vectorIndex.d.ts.map +1 -1
- package/lib/packlets/vector/vectorIndex.js.map +1 -1
- package/lib/test/unit/converters/antagonistRoundTrip.test.js +3 -3
- package/lib/test/unit/converters/antagonistRoundTrip.test.js.map +1 -1
- package/lib/test/unit/converters/envelopeConverter.test.js +124 -7
- package/lib/test/unit/converters/envelopeConverter.test.js.map +1 -1
- package/lib/test/unit/index/memoryIndex.test.js +86 -24
- package/lib/test/unit/index/memoryIndex.test.js.map +1 -1
- package/lib/test/unit/ingest/antagonistCycleAndParity.test.js +20 -16
- package/lib/test/unit/ingest/antagonistCycleAndParity.test.js.map +1 -1
- package/lib/test/unit/ingest/cycleGuard.test.js +28 -1
- package/lib/test/unit/ingest/cycleGuard.test.js.map +1 -1
- package/lib/test/unit/ingest/orchestrator.test.js +186 -44
- package/lib/test/unit/ingest/orchestrator.test.js.map +1 -1
- package/lib/test/unit/retrieve/linkTraversalRetriever.test.js +106 -31
- package/lib/test/unit/retrieve/linkTraversalRetriever.test.js.map +1 -1
- package/lib/test/unit/retrieve/retrievers.test.js +298 -36
- package/lib/test/unit/retrieve/retrievers.test.js.map +1 -1
- package/lib/test/unit/store/embedOnWrite.test.js +68 -11
- package/lib/test/unit/store/embedOnWrite.test.js.map +1 -1
- package/lib/test/unit/store/listScoped.test.d.ts +2 -0
- package/lib/test/unit/store/listScoped.test.d.ts.map +1 -0
- package/lib/test/unit/store/listScoped.test.js +111 -0
- package/lib/test/unit/store/listScoped.test.js.map +1 -0
- package/lib/test/unit/store/rankAxis.test.d.ts +2 -0
- package/lib/test/unit/store/rankAxis.test.d.ts.map +1 -0
- package/lib/test/unit/store/rankAxis.test.js +256 -0
- package/lib/test/unit/store/rankAxis.test.js.map +1 -0
- package/lib/test/unit/tools/memoryTools.test.js +280 -11
- package/lib/test/unit/tools/memoryTools.test.js.map +1 -1
- package/lib/test/unit/types/writePolicy.test.js +9 -2
- package/lib/test/unit/types/writePolicy.test.js.map +1 -1
- package/lib/test/unit/vector/inMemoryCosineIndex.test.js +95 -35
- package/lib/test/unit/vector/inMemoryCosineIndex.test.js.map +1 -1
- package/lib/test/unit/vector/vectorIndex.test.js +24 -15
- package/lib/test/unit/vector/vectorIndex.test.js.map +1 -1
- package/package.json +7 -7
- package/rush-logs/ts-agent-memory.build.cache.log +1 -1
- package/rush-logs/ts-agent-memory.build.log +2 -2
- package/src/packlets/converters/envelopeConverter.ts +27 -4
- package/src/packlets/index/memoryIndex.ts +86 -22
- package/src/packlets/ingest/cycleGuard.ts +22 -11
- package/src/packlets/ingest/hostStages.ts +3 -3
- package/src/packlets/ingest/model.ts +25 -14
- package/src/packlets/ingest/orchestrator.ts +143 -67
- package/src/packlets/retrieve/hybridRetriever.ts +14 -1
- package/src/packlets/retrieve/linkTraversalRetriever.ts +51 -62
- package/src/packlets/retrieve/recencyRetriever.ts +3 -3
- package/src/packlets/retrieve/retriever.ts +97 -13
- package/src/packlets/retrieve/semanticRetriever.ts +10 -5
- package/src/packlets/retrieve/structuredFilterRetriever.ts +3 -3
- package/src/packlets/retrieve/tagRetriever.ts +3 -3
- package/src/packlets/retrieve/temporalRetrievers.ts +3 -3
- package/src/packlets/store/fileTreeMemoryStore.ts +117 -12
- package/src/packlets/tools/memoryTools.ts +152 -25
- package/src/packlets/types/envelope.ts +66 -8
- package/src/packlets/vector/inMemoryCosineIndex.ts +45 -22
- package/src/packlets/vector/vectorIndex.ts +47 -20
- package/src/test/unit/converters/antagonistRoundTrip.test.ts +3 -3
- package/src/test/unit/converters/envelopeConverter.test.ts +168 -11
- package/src/test/unit/index/memoryIndex.test.ts +99 -14
- package/src/test/unit/ingest/antagonistCycleAndParity.test.ts +23 -18
- package/src/test/unit/ingest/cycleGuard.test.ts +44 -2
- package/src/test/unit/ingest/orchestrator.test.ts +234 -41
- package/src/test/unit/retrieve/linkTraversalRetriever.test.ts +134 -35
- package/src/test/unit/retrieve/retrievers.test.ts +381 -25
- package/src/test/unit/store/embedOnWrite.test.ts +83 -11
- package/src/test/unit/store/listScoped.test.ts +138 -0
- package/src/test/unit/store/rankAxis.test.ts +349 -0
- package/src/test/unit/tools/memoryTools.test.ts +362 -13
- package/src/test/unit/types/writePolicy.test.ts +11 -2
- package/src/test/unit/vector/inMemoryCosineIndex.test.ts +115 -39
- package/src/test/unit/vector/vectorIndex.test.ts +33 -17
- package/temp/build/lint/_eslint-5eVG3S6w.json +41 -33
- package/temp/build/typescript/ts_8nwakTlr.json +1 -1
- package/temp/ts-agent-memory.api.json +1080 -136
- package/temp/ts-agent-memory.api.md +64 -22
|
@@ -63,7 +63,13 @@ function makeEntry(spec) {
|
|
|
63
63
|
entityId: (_a = spec.entityId) !== null && _a !== void 0 ? _a : spec.id,
|
|
64
64
|
kind: (_b = spec.kind) !== null && _b !== void 0 ? _b : 'knowledge',
|
|
65
65
|
tags: (_c = spec.tags) !== null && _c !== void 0 ? _c : [],
|
|
66
|
-
links: ((_d = spec.links) !== null && _d !== void 0 ? _d : []).map((target) =>
|
|
66
|
+
links: ((_d = spec.links) !== null && _d !== void 0 ? _d : []).map((target) => {
|
|
67
|
+
var _a;
|
|
68
|
+
return ({
|
|
69
|
+
type: 'rel',
|
|
70
|
+
target: { scope: (_a = spec.scope) !== null && _a !== void 0 ? _a : 'knowledge', id: target }
|
|
71
|
+
});
|
|
72
|
+
}),
|
|
67
73
|
created: 0,
|
|
68
74
|
updated: (_e = spec.updated) !== null && _e !== void 0 ? _e : 0,
|
|
69
75
|
seq: 0,
|
|
@@ -207,9 +213,22 @@ describe('createMemoryTools', () => {
|
|
|
207
213
|
entityId: 'doc-1',
|
|
208
214
|
body: 'hello',
|
|
209
215
|
tags: ['a'],
|
|
210
|
-
links: [{ type: 'rel', target: 'doc-2', confidence: 0.5 }]
|
|
216
|
+
links: [{ type: 'rel', target: { id: 'doc-2' }, confidence: 0.5 }]
|
|
217
|
+
})).toSucceed();
|
|
218
|
+
expect(schema.convert({
|
|
219
|
+
kind: 'knowledge',
|
|
220
|
+
entityId: 'doc-1',
|
|
221
|
+
body: 'hello',
|
|
222
|
+
links: [{ type: 'rel', target: { scope: 'conversations/other', id: 'turn-1' } }]
|
|
211
223
|
})).toSucceed();
|
|
212
224
|
expect(schema.convert({ kind: 'knowledge', entityId: 'doc-1' })).toFail();
|
|
225
|
+
// A bare-string target (the pre-scoped format) is rejected at the schema.
|
|
226
|
+
expect(schema.convert({
|
|
227
|
+
kind: 'knowledge',
|
|
228
|
+
entityId: 'doc-1',
|
|
229
|
+
body: 'x',
|
|
230
|
+
links: [{ type: 'rel', target: 'doc-2' }]
|
|
231
|
+
})).toFail();
|
|
213
232
|
});
|
|
214
233
|
test('memory_read / memory_delete require kind and entityId', () => {
|
|
215
234
|
for (const name of ['memory_read', 'memory_delete']) {
|
|
@@ -224,10 +243,22 @@ describe('createMemoryTools', () => {
|
|
|
224
243
|
expect(schema.convert({ tag: 'x', limit: 3 })).toSucceed();
|
|
225
244
|
expect(schema.convert({ limit: 'three' })).toFail();
|
|
226
245
|
});
|
|
227
|
-
test('memory_context requires a from seed', () => {
|
|
246
|
+
test('memory_context requires a from seed with a REQUIRED scope (nested { id, scope })', () => {
|
|
228
247
|
const schema = toolByName(allTools(), 'memory_context').config.parametersSchema;
|
|
229
|
-
expect(schema.convert({ from: 'doc-1' })).toSucceed();
|
|
248
|
+
expect(schema.convert({ from: { id: 'doc-1', scope: 'knowledge' } })).toSucceed();
|
|
249
|
+
// scope is schema-REQUIRED (unlike a write link target) — the wire schema must
|
|
250
|
+
// not advertise an optionality the tool does not honor. Omitting it fails.
|
|
251
|
+
expect(schema.convert({ from: { id: 'doc-1' } })).toFail();
|
|
230
252
|
expect(schema.convert({ hops: 2 })).toFail();
|
|
253
|
+
// A bare-string seed (the pre-scoped format) is rejected at the schema.
|
|
254
|
+
expect(schema.convert({ from: 'doc-1' })).toFail();
|
|
255
|
+
});
|
|
256
|
+
test('the memory_context wire schema marks from.scope as required', () => {
|
|
257
|
+
// Guards the schema-accuracy contract at the wire level: an LLM reading the
|
|
258
|
+
// tool's `parameters` sees scope in the seed's `required` array.
|
|
259
|
+
const schema = toolByName(allTools(), 'memory_context').config.parametersSchema;
|
|
260
|
+
const wire = schema.toJson();
|
|
261
|
+
expect(wire.properties.from.required).toContain('scope');
|
|
231
262
|
});
|
|
232
263
|
});
|
|
233
264
|
describe('memory_write', () => {
|
|
@@ -271,11 +302,39 @@ describe('createMemoryTools', () => {
|
|
|
271
302
|
entityId: 'doc-1',
|
|
272
303
|
body: 'linked',
|
|
273
304
|
links: [
|
|
274
|
-
|
|
275
|
-
{ type: 'rel', target: 'doc-
|
|
305
|
+
// scope omitted → defaults to the writing record's own scope (knowledge)
|
|
306
|
+
{ type: 'rel', target: { id: 'doc-2' }, confidence: 0.9 },
|
|
307
|
+
{ type: 'rel', target: { id: 'doc-3' } }
|
|
276
308
|
]
|
|
277
309
|
})).toSucceedAndSatisfy((v) => expect(v.outcome).toBe('written'));
|
|
278
|
-
|
|
310
|
+
// The persisted links carry the writing record's scope on each target.
|
|
311
|
+
expect(await store.get(knowledgeKind, 'doc-1')).toSucceedAndSatisfy((rec) => {
|
|
312
|
+
expect(rec === null || rec === void 0 ? void 0 : rec.envelope.links).toEqual([
|
|
313
|
+
{ type: 'rel', target: { scope: 'knowledge', id: 'doc-2' }, confidence: 0.9 },
|
|
314
|
+
{ type: 'rel', target: { scope: 'knowledge', id: 'doc-3' } }
|
|
315
|
+
]);
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
test('an authored link with an EXPLICIT scope is honored verbatim (cross-scope edge)', async () => {
|
|
319
|
+
const store = makeStore();
|
|
320
|
+
const tools = (0, index_1.createMemoryTools)({
|
|
321
|
+
store,
|
|
322
|
+
retriever: makeRetriever([]),
|
|
323
|
+
registry: registryWith([{ kind: knowledgeKind }]),
|
|
324
|
+
codecs,
|
|
325
|
+
tools: ['memory_write']
|
|
326
|
+
});
|
|
327
|
+
expect(await toolByName(tools, 'memory_write').execute({
|
|
328
|
+
kind: 'knowledge',
|
|
329
|
+
entityId: 'doc-1',
|
|
330
|
+
body: 'cross',
|
|
331
|
+
links: [{ type: 'mtm-ref', target: { scope: 'conversations/conv-a', id: 'turn-3' } }]
|
|
332
|
+
})).toSucceed();
|
|
333
|
+
expect(await store.get(knowledgeKind, 'doc-1')).toSucceedAndSatisfy((rec) => {
|
|
334
|
+
expect(rec === null || rec === void 0 ? void 0 : rec.envelope.links).toEqual([
|
|
335
|
+
{ type: 'mtm-ref', target: { scope: 'conversations/conv-a', id: 'turn-3' } }
|
|
336
|
+
]);
|
|
337
|
+
});
|
|
279
338
|
});
|
|
280
339
|
test('an identical re-put of the same entity is a dedup no-op (outcome: deduped)', async () => {
|
|
281
340
|
const store = makeStore();
|
|
@@ -357,6 +416,8 @@ describe('createMemoryTools', () => {
|
|
|
357
416
|
get: async () => (0, ts_utils_1.fail)('disk fault'),
|
|
358
417
|
getById: async () => (0, ts_utils_1.fail)('unused'),
|
|
359
418
|
list: async () => (0, ts_utils_1.succeed)([]),
|
|
419
|
+
listScoped: async () => (0, ts_utils_1.succeed)([]),
|
|
420
|
+
asRecordSource: () => ({ list: async () => (0, ts_utils_1.succeed)([]) }),
|
|
360
421
|
put: async (record) => (0, ts_utils_1.succeed)(record),
|
|
361
422
|
delete: async () => (0, ts_utils_1.fail)('unused')
|
|
362
423
|
};
|
|
@@ -512,9 +573,9 @@ describe('createMemoryTools', () => {
|
|
|
512
573
|
retriever,
|
|
513
574
|
registry: registryWith([{ kind: knowledgeKind }])
|
|
514
575
|
}), 'memory_context');
|
|
515
|
-
expect(await tool.execute({ from: 'a' })).toSucceedAndSatisfy((value) => {
|
|
576
|
+
expect(await tool.execute({ from: { id: 'a', scope: 'knowledge' } })).toSucceedAndSatisfy((value) => {
|
|
516
577
|
const out = value;
|
|
517
|
-
expect(out.seed).
|
|
578
|
+
expect(out.seed).toEqual({ scope: 'knowledge', id: 'a' });
|
|
518
579
|
expect(out.results.map((r) => r.handle)).toEqual(['b', 'c']);
|
|
519
580
|
});
|
|
520
581
|
});
|
|
@@ -529,14 +590,46 @@ describe('createMemoryTools', () => {
|
|
|
529
590
|
retriever,
|
|
530
591
|
registry: registryWith([{ kind: knowledgeKind }])
|
|
531
592
|
}), 'memory_context');
|
|
532
|
-
expect(await tool.execute({
|
|
593
|
+
expect(await tool.execute({
|
|
594
|
+
from: { id: 'a', scope: 'knowledge' },
|
|
595
|
+
hops: 2,
|
|
596
|
+
tag: 'keep',
|
|
597
|
+
kind: 'knowledge',
|
|
598
|
+
limit: 5
|
|
599
|
+
})).toSucceedAndSatisfy((value) => {
|
|
533
600
|
const out = value;
|
|
534
601
|
expect(out.results.map((r) => r.handle).sort()).toEqual(['b', 'c']);
|
|
535
602
|
});
|
|
536
603
|
});
|
|
537
604
|
test('rejects an invalid seed id', async () => {
|
|
538
605
|
const tool = toolByName(allTools(), 'memory_context');
|
|
539
|
-
expect(await tool.execute({ from: 'a/b' })).toFail();
|
|
606
|
+
expect(await tool.execute({ from: { id: 'a/b', scope: 'knowledge' } })).toFail();
|
|
607
|
+
});
|
|
608
|
+
test('fails at the schema when the seed omits its required scope', async () => {
|
|
609
|
+
// scope is schema-required, so an omitted scope is rejected as an invalid
|
|
610
|
+
// argument (the wire schema and the runtime contract agree — no lie).
|
|
611
|
+
const tool = toolByName(allTools(), 'memory_context');
|
|
612
|
+
expect(await tool.execute({ from: { id: 'a' } })).toFailWith(/invalid arguments/i);
|
|
613
|
+
});
|
|
614
|
+
test('a scoped seed reaches ONLY the correctly-scoped graph', async () => {
|
|
615
|
+
// Two conversations reuse the stem `turn-0`; each links a same-scope `turn-1`.
|
|
616
|
+
// Seeding conv-a must reach conv-a's turn-1 and NEVER conv-b's.
|
|
617
|
+
const retriever = makeLinkRetriever([
|
|
618
|
+
{ id: 'turn-0', scope: 'conversations/conv-a', links: ['turn-1'] },
|
|
619
|
+
{ id: 'turn-1', scope: 'conversations/conv-a', tags: ['in-a'] },
|
|
620
|
+
{ id: 'turn-0', scope: 'conversations/conv-b', links: ['turn-1'] },
|
|
621
|
+
{ id: 'turn-1', scope: 'conversations/conv-b', tags: ['in-b'] }
|
|
622
|
+
]);
|
|
623
|
+
const tool = toolByName((0, index_1.createMemoryTools)({
|
|
624
|
+
store: makeStore(),
|
|
625
|
+
retriever,
|
|
626
|
+
registry: registryWith([{ kind: knowledgeKind }])
|
|
627
|
+
}), 'memory_context');
|
|
628
|
+
expect(await tool.execute({ from: { id: 'turn-0', scope: 'conversations/conv-a' } })).toSucceedAndSatisfy((value) => {
|
|
629
|
+
const out = value;
|
|
630
|
+
expect(out.results).toHaveLength(1);
|
|
631
|
+
expect(out.results[0].tags).toEqual(['in-a']);
|
|
632
|
+
});
|
|
540
633
|
});
|
|
541
634
|
test('rejects malformed arguments', async () => {
|
|
542
635
|
const tool = toolByName(allTools(), 'memory_context');
|
|
@@ -571,4 +664,180 @@ describe('createMemoryTools', () => {
|
|
|
571
664
|
});
|
|
572
665
|
});
|
|
573
666
|
});
|
|
667
|
+
describe('result projection (projectItem host projector + detail tier)', () => {
|
|
668
|
+
/**
|
|
669
|
+
* A host projector that bounds the `'gist'` body to a sentinel and passes the
|
|
670
|
+
* full body through only for `'full'` — the shape a real host uses to keep the
|
|
671
|
+
* default (gist) path bounded.
|
|
672
|
+
*/
|
|
673
|
+
const boundingProjector = (record, detail) => ({
|
|
674
|
+
handle: `h:${record.envelope.id}`,
|
|
675
|
+
kind: record.envelope.kind,
|
|
676
|
+
entityId: record.envelope.entityId,
|
|
677
|
+
tags: record.envelope.tags,
|
|
678
|
+
body: detail === 'full' ? record.body : '<<gist>>'
|
|
679
|
+
});
|
|
680
|
+
function searchToolWith(overrides) {
|
|
681
|
+
return toolByName((0, index_1.createMemoryTools)(Object.assign({ store: makeStore(), retriever: makeRetriever([{ id: 'doc-1', tags: ['topic'] }]), registry: registryWith([{ kind: knowledgeKind }]) }, overrides)), 'memory_search');
|
|
682
|
+
}
|
|
683
|
+
describe('schema surface', () => {
|
|
684
|
+
test('memory_search declares optional detail + offset properties', () => {
|
|
685
|
+
const props = schemaProperties(toolByName(allTools(), 'memory_search'));
|
|
686
|
+
expect(props).toContain('detail');
|
|
687
|
+
expect(props).toContain('offset');
|
|
688
|
+
});
|
|
689
|
+
test('memory_context declares an optional detail property', () => {
|
|
690
|
+
const props = schemaProperties(toolByName(allTools(), 'memory_context'));
|
|
691
|
+
expect(props).toContain('detail');
|
|
692
|
+
});
|
|
693
|
+
test('memory_read declares an optional detail property', () => {
|
|
694
|
+
const props = schemaProperties(toolByName(allTools(), 'memory_read'));
|
|
695
|
+
expect(props).toContain('detail');
|
|
696
|
+
});
|
|
697
|
+
test('memory_search accepts detail and offset, and rejects an out-of-enum detail', () => {
|
|
698
|
+
const schema = toolByName(allTools(), 'memory_search').config.parametersSchema;
|
|
699
|
+
expect(schema.convert({ detail: 'full', offset: 2 })).toSucceed();
|
|
700
|
+
expect(schema.convert({ detail: 'gist' })).toSucceed();
|
|
701
|
+
expect(schema.convert({ offset: 'nope' })).toFail();
|
|
702
|
+
// detail is now an enum ('gist' | 'full') at the wire-schema level.
|
|
703
|
+
expect(schema.convert({ detail: 'verbose' })).toFail();
|
|
704
|
+
});
|
|
705
|
+
});
|
|
706
|
+
describe('absent projector = byte-identical default projection', () => {
|
|
707
|
+
test('returns the full body and the default handle (raw id) when no projector is supplied', async () => {
|
|
708
|
+
const tool = searchToolWith({});
|
|
709
|
+
expect(await tool.execute({ tag: 'topic' })).toSucceedAndSatisfy((value) => {
|
|
710
|
+
const out = value;
|
|
711
|
+
expect(out.results[0].handle).toBe('doc-1');
|
|
712
|
+
expect(out.results[0].body).toBe('body-doc-1');
|
|
713
|
+
});
|
|
714
|
+
});
|
|
715
|
+
test('the detail tier is inert without a projector (gist and full both yield the full body)', async () => {
|
|
716
|
+
const tool = searchToolWith({});
|
|
717
|
+
for (const detail of ['gist', 'full']) {
|
|
718
|
+
expect(await tool.execute({ tag: 'topic', detail })).toSucceedAndSatisfy((value) => {
|
|
719
|
+
expect(value.results[0].body).toBe('body-doc-1');
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
});
|
|
723
|
+
});
|
|
724
|
+
describe('host projector + detail tier', () => {
|
|
725
|
+
test('defaults to the bounded gist projection when detail is absent', async () => {
|
|
726
|
+
const tool = searchToolWith({ projectItem: boundingProjector });
|
|
727
|
+
expect(await tool.execute({ tag: 'topic' })).toSucceedAndSatisfy((value) => {
|
|
728
|
+
const out = value;
|
|
729
|
+
expect(out.results[0].handle).toBe('h:doc-1');
|
|
730
|
+
expect(out.results[0].body).toBe('<<gist>>');
|
|
731
|
+
});
|
|
732
|
+
});
|
|
733
|
+
test('opts into the full body only when detail=full is requested', async () => {
|
|
734
|
+
const tool = searchToolWith({ projectItem: boundingProjector });
|
|
735
|
+
expect(await tool.execute({ tag: 'topic', detail: 'full' })).toSucceedAndSatisfy((value) => {
|
|
736
|
+
expect(value.results[0].body).toBe('body-doc-1');
|
|
737
|
+
});
|
|
738
|
+
});
|
|
739
|
+
test('an out-of-enum detail is rejected at the schema boundary', async () => {
|
|
740
|
+
// detail is a JsonSchema.enumOf(['gist','full']); the tool re-validates args
|
|
741
|
+
// on execute, so an out-of-enum value fails loudly rather than reaching the projector.
|
|
742
|
+
const tool = searchToolWith({ projectItem: boundingProjector });
|
|
743
|
+
expect(await tool.execute({ tag: 'topic', detail: 'verbose' })).toFailWith(/invalid arguments/i);
|
|
744
|
+
});
|
|
745
|
+
test('an explicit detail=gist stays bounded (the non-full branch)', async () => {
|
|
746
|
+
const tool = searchToolWith({ projectItem: boundingProjector });
|
|
747
|
+
expect(await tool.execute({ tag: 'topic', detail: 'gist' })).toSucceedAndSatisfy((value) => {
|
|
748
|
+
expect(value.results[0].body).toBe('<<gist>>');
|
|
749
|
+
});
|
|
750
|
+
});
|
|
751
|
+
test('memory_context routes its results through the host projector', async () => {
|
|
752
|
+
const tool = toolByName((0, index_1.createMemoryTools)({
|
|
753
|
+
store: makeStore(),
|
|
754
|
+
retriever: makeLinkRetriever([{ id: 'a', links: ['b'] }, { id: 'b' }]),
|
|
755
|
+
registry: registryWith([{ kind: knowledgeKind }]),
|
|
756
|
+
projectItem: boundingProjector
|
|
757
|
+
}), 'memory_context');
|
|
758
|
+
expect(await tool.execute({ from: { id: 'a', scope: 'knowledge' }, detail: 'full' })).toSucceedAndSatisfy((value) => {
|
|
759
|
+
const out = value;
|
|
760
|
+
expect(out.results.map((r) => r.handle)).toEqual(['h:b']);
|
|
761
|
+
expect(out.results[0].body).toBe('body-b');
|
|
762
|
+
});
|
|
763
|
+
});
|
|
764
|
+
test('a throwing host projector degrades to the default full-body projection (does not crash search)', async () => {
|
|
765
|
+
const tool = searchToolWith({
|
|
766
|
+
projectItem: () => {
|
|
767
|
+
throw new Error('host projector blew up');
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
expect(await tool.execute({ tag: 'topic' })).toSucceedAndSatisfy((value) => {
|
|
771
|
+
const out = value;
|
|
772
|
+
// Degraded to the built-in default: raw-id handle + full body.
|
|
773
|
+
expect(out.results[0].handle).toBe('doc-1');
|
|
774
|
+
expect(out.results[0].body).toBe('body-doc-1');
|
|
775
|
+
});
|
|
776
|
+
});
|
|
777
|
+
test('a throwing projector still honors the handleFor hook in the fallback path', async () => {
|
|
778
|
+
const tool = searchToolWith({
|
|
779
|
+
handleFor: (r) => `@${r.envelope.id}`,
|
|
780
|
+
projectItem: () => {
|
|
781
|
+
throw new Error('nope');
|
|
782
|
+
}
|
|
783
|
+
});
|
|
784
|
+
expect(await tool.execute({ tag: 'topic' })).toSucceedAndSatisfy((value) => {
|
|
785
|
+
expect(value.results[0].handle).toBe('@doc-1');
|
|
786
|
+
});
|
|
787
|
+
});
|
|
788
|
+
});
|
|
789
|
+
describe('offset threading (memory_search)', () => {
|
|
790
|
+
test('threads offset into the query to page the ordered result window', async () => {
|
|
791
|
+
const tool = toolByName((0, index_1.createMemoryTools)({
|
|
792
|
+
store: makeStore(),
|
|
793
|
+
retriever: makeRetriever([
|
|
794
|
+
{ id: 'doc-1', tags: ['topic'], updated: 30 },
|
|
795
|
+
{ id: 'doc-2', tags: ['topic'], updated: 20 },
|
|
796
|
+
{ id: 'doc-3', tags: ['topic'], updated: 10 }
|
|
797
|
+
]),
|
|
798
|
+
registry: registryWith([{ kind: knowledgeKind }])
|
|
799
|
+
}), 'memory_search');
|
|
800
|
+
// Recency-ordered [doc-1, doc-2, doc-3]; offset 1 + limit 1 → [doc-2].
|
|
801
|
+
expect(await tool.execute({ tag: 'topic', offset: 1, limit: 1 })).toSucceedAndSatisfy((value) => {
|
|
802
|
+
const out = value;
|
|
803
|
+
expect(out.count).toBe(1);
|
|
804
|
+
expect(out.results.map((r) => r.handle)).toEqual(['doc-2']);
|
|
805
|
+
});
|
|
806
|
+
});
|
|
807
|
+
});
|
|
808
|
+
describe('memory_read detail (explicit drill-in defaults to FULL)', () => {
|
|
809
|
+
async function readToolWith(projector) {
|
|
810
|
+
const tools = (0, index_1.createMemoryTools)(Object.assign({ store: makeStore(), retriever: makeRetriever([]), registry: registryWith([{ kind: knowledgeKind }]), codecs, tools: ['memory_write', 'memory_read'] }, (projector !== undefined ? { projectItem: projector } : {})));
|
|
811
|
+
await toolByName(tools, 'memory_write').execute({
|
|
812
|
+
kind: 'knowledge',
|
|
813
|
+
entityId: 'doc-1',
|
|
814
|
+
body: 'FULLBODY'
|
|
815
|
+
});
|
|
816
|
+
return toolByName(tools, 'memory_read');
|
|
817
|
+
}
|
|
818
|
+
test('defaults to the FULL body (drill-in) with a bounding projector', async () => {
|
|
819
|
+
const tool = await readToolWith(boundingProjector);
|
|
820
|
+
expect(await tool.execute({ kind: 'knowledge', entityId: 'doc-1' })).toSucceedAndSatisfy((value) => {
|
|
821
|
+
expect(value.item.body).toBe('FULLBODY');
|
|
822
|
+
});
|
|
823
|
+
});
|
|
824
|
+
test('opts down to gist only when detail=gist is requested', async () => {
|
|
825
|
+
const tool = await readToolWith(boundingProjector);
|
|
826
|
+
expect(await tool.execute({ kind: 'knowledge', entityId: 'doc-1', detail: 'gist' })).toSucceedAndSatisfy((value) => {
|
|
827
|
+
expect(value.item.body).toBe('<<gist>>');
|
|
828
|
+
});
|
|
829
|
+
});
|
|
830
|
+
test('is byte-identical for a no-projector consumer (full body regardless of detail)', async () => {
|
|
831
|
+
const tool = await readToolWith(undefined);
|
|
832
|
+
for (const args of [
|
|
833
|
+
{ kind: 'knowledge', entityId: 'doc-1' },
|
|
834
|
+
{ kind: 'knowledge', entityId: 'doc-1', detail: 'gist' }
|
|
835
|
+
]) {
|
|
836
|
+
expect(await tool.execute(args)).toSucceedAndSatisfy((value) => {
|
|
837
|
+
expect(value.item.body).toBe('FULLBODY');
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
});
|
|
841
|
+
});
|
|
842
|
+
});
|
|
574
843
|
//# sourceMappingURL=memoryTools.test.js.map
|