fix(n8n): restore App ID, Includes and Excludes, add App ID filters
Reverts the removal in 2a61fe1a. That commit dropped app_id, includes and
excludes from Add because docs/openapi.json does not declare them on
POST /v3/memories/add/. The schema is incomplete: probing the live endpoint
shows all three are accepted and honoured.
- app_id is persisted and is queryable via /v3/memories/search/ and
/v3/memories/, so the entity-id guard accepts it again as a valid scope
- includes/excludes demonstrably steer extraction. The same message stored
three memories unfiltered and one under
includes: "only record food and diet preferences"
Search and Get Many gain an App ID filter alongside User/Agent/Run. Writing
an app-scoped memory was otherwise unreadable from this node, and app_id is
how the OpenCode and Pi Agent plugins express project scope.
buildEntityFilters now takes the ids as an object rather than four adjacent
positional strings, so a fifth entity is a one-key change.
This commit is contained in:
@@ -53,6 +53,44 @@ describe('Mem0 node (offline)', () => {
|
||||
expect(out[0][0].json.error).toMatch(/at least one of User ID/i);
|
||||
});
|
||||
|
||||
it('forwards app id, includes and excludes on Add', async () => {
|
||||
const ctx = makeCtx(
|
||||
'add',
|
||||
{
|
||||
'messages.message': [{ role: 'user', content: 'hi' }],
|
||||
addFields: {
|
||||
app_id: 'p1',
|
||||
includes: 'only food preferences',
|
||||
excludes: 'nothing about vehicles',
|
||||
},
|
||||
userId: 'u1',
|
||||
},
|
||||
async () => ({}),
|
||||
);
|
||||
await run(ctx);
|
||||
expect(ctx.requests[0].body).toMatchObject({
|
||||
app_id: 'p1',
|
||||
includes: 'only food preferences',
|
||||
excludes: 'nothing about vehicles',
|
||||
});
|
||||
});
|
||||
|
||||
it('accepts an app id alone as the entity scope on Add', async () => {
|
||||
const ctx = makeCtx(
|
||||
'add',
|
||||
{
|
||||
'messages.message': [{ role: 'user', content: 'hi' }],
|
||||
addFields: { app_id: 'p1' },
|
||||
userId: '',
|
||||
},
|
||||
async () => ({}),
|
||||
{ continueOnFail: true },
|
||||
);
|
||||
const out: any = await run(ctx);
|
||||
expect(out[0][0].json.error).toBeUndefined();
|
||||
expect(ctx.requests[0].body.app_id).toBe('p1');
|
||||
});
|
||||
|
||||
it('reports a clear error on invalid JSON in Custom Categories', async () => {
|
||||
const ctx = makeCtx(
|
||||
'add',
|
||||
@@ -103,15 +141,21 @@ describe('Mem0 node (offline)', () => {
|
||||
it('combines entity ids with OR, never AND (entities are stored separately, so AND matches nothing)', async () => {
|
||||
const ctx = makeCtx(
|
||||
'search',
|
||||
{ query: 'x', userId: 'u1', agentId: 'a1', runId: 'r1' },
|
||||
{ query: 'x', userId: 'u1', agentId: 'a1', appId: 'p1', runId: 'r1' },
|
||||
async () => ({ results: [] }),
|
||||
);
|
||||
await run(ctx);
|
||||
expect(ctx.requests[0].body.filters).toEqual({
|
||||
OR: [{ user_id: 'u1' }, { agent_id: 'a1' }, { run_id: 'r1' }],
|
||||
OR: [{ user_id: 'u1' }, { agent_id: 'a1' }, { app_id: 'p1' }, { run_id: 'r1' }],
|
||||
});
|
||||
});
|
||||
|
||||
it.each(['search', 'getAll'])('filters %s by app id alone', async (op) => {
|
||||
const ctx = makeCtx(op, { query: 'x', appId: 'p1' }, async () => ({ results: [] }));
|
||||
await run(ctx);
|
||||
expect(ctx.requests[0].body.filters).toEqual({ app_id: 'p1' });
|
||||
});
|
||||
|
||||
it('filters Get Many by agent id alone', async () => {
|
||||
const ctx = makeCtx('getAll', { agentId: 'a1' }, async () => ({ results: [] }));
|
||||
await run(ctx);
|
||||
|
||||
Reference in New Issue
Block a user