@noopolis/mneme 0.1.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 +83 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +46 -0
- package/dist/contract/index.d.ts +3 -0
- package/dist/contract/index.d.ts.map +1 -0
- package/dist/contract/index.js +2 -0
- package/dist/contract/toolDescriptors.d.ts +14 -0
- package/dist/contract/toolDescriptors.d.ts.map +1 -0
- package/dist/contract/toolDescriptors.js +98 -0
- package/dist/contract/types.d.ts +262 -0
- package/dist/contract/types.d.ts.map +1 -0
- package/dist/contract/types.js +1 -0
- package/dist/identity/ids.d.ts +9 -0
- package/dist/identity/ids.d.ts.map +1 -0
- package/dist/identity/ids.js +25 -0
- package/dist/identity/index.d.ts +3 -0
- package/dist/identity/index.d.ts.map +1 -0
- package/dist/identity/index.js +2 -0
- package/dist/identity/scope.d.ts +24 -0
- package/dist/identity/scope.d.ts.map +1 -0
- package/dist/identity/scope.js +123 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/kernel/index.d.ts +3 -0
- package/dist/kernel/index.d.ts.map +1 -0
- package/dist/kernel/index.js +2 -0
- package/dist/kernel/kernel.d.ts +19 -0
- package/dist/kernel/kernel.d.ts.map +1 -0
- package/dist/kernel/kernel.js +318 -0
- package/dist/kernel/support.d.ts +66 -0
- package/dist/kernel/support.d.ts.map +1 -0
- package/dist/kernel/support.js +207 -0
- package/dist/mcp/config.d.ts +23 -0
- package/dist/mcp/config.d.ts.map +1 -0
- package/dist/mcp/config.js +76 -0
- package/dist/mcp/index.d.ts +4 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +3 -0
- package/dist/mcp/schemas.d.ts +150 -0
- package/dist/mcp/schemas.d.ts.map +1 -0
- package/dist/mcp/schemas.js +54 -0
- package/dist/mcp/server.d.ts +5 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +45 -0
- package/dist/policy/index.d.ts +2 -0
- package/dist/policy/index.d.ts.map +1 -0
- package/dist/policy/index.js +1 -0
- package/dist/policy/policy.d.ts +9 -0
- package/dist/policy/policy.d.ts.map +1 -0
- package/dist/policy/policy.js +123 -0
- package/dist/recall/index.d.ts +2 -0
- package/dist/recall/index.d.ts.map +1 -0
- package/dist/recall/index.js +1 -0
- package/dist/recall/recall.d.ts +28 -0
- package/dist/recall/recall.d.ts.map +1 -0
- package/dist/recall/recall.js +178 -0
- package/dist/runtime/index.d.ts +3 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/runtime.d.ts +19 -0
- package/dist/runtime/runtime.d.ts.map +1 -0
- package/dist/runtime/runtime.js +129 -0
- package/dist/runtime/support.d.ts +52 -0
- package/dist/runtime/support.d.ts.map +1 -0
- package/dist/runtime/support.js +229 -0
- package/dist/store/index.d.ts +3 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +2 -0
- package/dist/store/sqliteIndex.d.ts +38 -0
- package/dist/store/sqliteIndex.d.ts.map +1 -0
- package/dist/store/sqliteIndex.js +270 -0
- package/dist/store/store.d.ts +27 -0
- package/dist/store/store.d.ts.map +1 -0
- package/dist/store/store.js +114 -0
- package/package.json +53 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { memoryPolicy } from "../policy/policy.js";
|
|
2
|
+
import { runRecall } from "../recall/recall.js";
|
|
3
|
+
const defaultTokenBudget = 1200;
|
|
4
|
+
const defaultSourcePrefix = "daimon";
|
|
5
|
+
export const defaultSource = (agentId) => `${defaultSourcePrefix}/${agentId}`;
|
|
6
|
+
export const clampTokenBudget = (value) => {
|
|
7
|
+
if (!value || Number.isNaN(value) || value <= 0) {
|
|
8
|
+
return defaultTokenBudget;
|
|
9
|
+
}
|
|
10
|
+
return Math.max(128, Math.floor(value));
|
|
11
|
+
};
|
|
12
|
+
const trimText = (value, maxLength) => {
|
|
13
|
+
const normalized = value.trim();
|
|
14
|
+
return normalized.length <= maxLength ? normalized : normalized.slice(0, maxLength).trim();
|
|
15
|
+
};
|
|
16
|
+
export const normalizeScopeIds = (ids) => {
|
|
17
|
+
return [...new Set(ids.map((value) => value.trim().toLowerCase()).filter(Boolean))];
|
|
18
|
+
};
|
|
19
|
+
const extractTokens = (text) => {
|
|
20
|
+
const payload = `${text}`.toLowerCase();
|
|
21
|
+
return [...new Set(payload
|
|
22
|
+
.split(/[^a-z0-9]+/u)
|
|
23
|
+
.map((value) => value.trim())
|
|
24
|
+
.filter((value) => value.length > 3))].slice(0, 32);
|
|
25
|
+
};
|
|
26
|
+
export const readByScopes = async (store, scopeIds) => {
|
|
27
|
+
const queries = await Promise.all(scopeIds.map((scope) => store.read({ scope })));
|
|
28
|
+
const seen = new Set();
|
|
29
|
+
const events = [];
|
|
30
|
+
for (const event of queries.flat()) {
|
|
31
|
+
if (seen.has(event.id)) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
seen.add(event.id);
|
|
35
|
+
events.push(event);
|
|
36
|
+
}
|
|
37
|
+
return events;
|
|
38
|
+
};
|
|
39
|
+
export const recallableEvents = (events) => {
|
|
40
|
+
const forgotten = new Set(events
|
|
41
|
+
.filter((event) => event.type === "memory.forgotten")
|
|
42
|
+
.flatMap((event) => event.parentEventIds));
|
|
43
|
+
return events.filter((event) => event.type !== "memory.located" &&
|
|
44
|
+
event.type !== "memory.denied" &&
|
|
45
|
+
event.type !== "memory.forgotten" &&
|
|
46
|
+
!forgotten.has(event.id));
|
|
47
|
+
};
|
|
48
|
+
export const deniedMemoryEvents = (input) => input.events.flatMap((event) => {
|
|
49
|
+
if (input.selectedIds.has(event.id)) {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
const decision = memoryPolicy({
|
|
53
|
+
request: input.requester,
|
|
54
|
+
activeScope: input.activeScope,
|
|
55
|
+
candidate: event
|
|
56
|
+
});
|
|
57
|
+
return decision.decision === "deny" ? [{ event, reason: decision.reason }] : [];
|
|
58
|
+
});
|
|
59
|
+
export const buildRecallInput = (input) => {
|
|
60
|
+
return runRecall({
|
|
61
|
+
actor: input.actor,
|
|
62
|
+
scopeIds: input.scopeIds,
|
|
63
|
+
events: input.events,
|
|
64
|
+
query: input.text,
|
|
65
|
+
maxTokens: clampTokenBudget(input.maxTokens)
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
const toDecisionText = (decision) => {
|
|
69
|
+
return decision === "allow_raw" ? "used raw"
|
|
70
|
+
: decision === "allow_summary" ? "used summary"
|
|
71
|
+
: decision === "allow_redacted_summary" ? "used redacted summary"
|
|
72
|
+
: decision === "known_but_private" ? "used private memory via scope policy"
|
|
73
|
+
: decision === "locate_only" ? "used locate-only hint"
|
|
74
|
+
: decision === "unavailable" ? "memory unavailable"
|
|
75
|
+
: decision === "malformed_request" ? "malformed request"
|
|
76
|
+
: "denied by policy";
|
|
77
|
+
};
|
|
78
|
+
const memoryTagsFromRequest = (request, packet, result) => {
|
|
79
|
+
const context = request.context ?? {};
|
|
80
|
+
const tokens = [
|
|
81
|
+
...extractTokens(request.text),
|
|
82
|
+
...extractTokens(packet.principal.scope),
|
|
83
|
+
request.kind,
|
|
84
|
+
result.principal.scope,
|
|
85
|
+
...extractTokens(result.principal.qualifier ?? ""),
|
|
86
|
+
...context.networkId ? [context.networkId] : [],
|
|
87
|
+
...context.roomId ? [context.roomId] : []
|
|
88
|
+
];
|
|
89
|
+
return [...new Set(tokens)].slice(0, 64);
|
|
90
|
+
};
|
|
91
|
+
const baseEventInput = (input) => {
|
|
92
|
+
return {
|
|
93
|
+
principal: input.principal,
|
|
94
|
+
scope: input.scope,
|
|
95
|
+
visibility: input.visibility,
|
|
96
|
+
source: input.source,
|
|
97
|
+
parentEventIds: input.parentEventIds,
|
|
98
|
+
tags: input.tags,
|
|
99
|
+
entities: input.entities,
|
|
100
|
+
sensitivity: "normal"
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
const visibilityForPrincipal = (principal) => {
|
|
104
|
+
if (principal.scope === "global" ||
|
|
105
|
+
principal.scope === "team" ||
|
|
106
|
+
principal.scope === "room" ||
|
|
107
|
+
principal.scope === "pair") {
|
|
108
|
+
return principal.scope;
|
|
109
|
+
}
|
|
110
|
+
return "private";
|
|
111
|
+
};
|
|
112
|
+
export const buildDecisionEvents = (input) => {
|
|
113
|
+
const entities = [
|
|
114
|
+
...extractTokens(input.request.text),
|
|
115
|
+
...extractTokens(input.packet.principal.agentId),
|
|
116
|
+
...extractTokens(input.packet.principal.scope)
|
|
117
|
+
];
|
|
118
|
+
const tags = memoryTagsFromRequest(input.request, input.packet, {
|
|
119
|
+
principal: input.principal,
|
|
120
|
+
packet: input.packet,
|
|
121
|
+
promptText: input.outputText,
|
|
122
|
+
recall: input.recall
|
|
123
|
+
});
|
|
124
|
+
const base = baseEventInput({
|
|
125
|
+
principal: input.principal,
|
|
126
|
+
scope: input.scope,
|
|
127
|
+
source: input.source,
|
|
128
|
+
visibility: visibilityForPrincipal(input.principal),
|
|
129
|
+
tags,
|
|
130
|
+
entities,
|
|
131
|
+
parentEventIds: input.parentEventIds
|
|
132
|
+
});
|
|
133
|
+
const events = [
|
|
134
|
+
{
|
|
135
|
+
...base,
|
|
136
|
+
type: "memory.claimed",
|
|
137
|
+
content: {
|
|
138
|
+
kind: "text",
|
|
139
|
+
text: `Wake request ${input.request.eventId} from ${input.request.from ?? "operator"}: ${trimText(input.request.text, 480)}`
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
...base,
|
|
144
|
+
type: "memory.observed",
|
|
145
|
+
tags: [...tags, "output", input.result],
|
|
146
|
+
content: {
|
|
147
|
+
kind: "text",
|
|
148
|
+
text: `Agent output: ${trimText(input.outputText, 620)}`
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
];
|
|
152
|
+
for (const selected of input.recall.selected ?? []) {
|
|
153
|
+
events.push({
|
|
154
|
+
...base,
|
|
155
|
+
type: "memory.recalled",
|
|
156
|
+
tags: [...tags, "memory", selected.decision, selected.scope],
|
|
157
|
+
content: {
|
|
158
|
+
kind: "text",
|
|
159
|
+
text: `Recalled ${selected.eventId}: ${selected.representation}. Decision=${toDecisionText(selected.decision)} (${selected.scope})`
|
|
160
|
+
},
|
|
161
|
+
entities: [...entities, selected.eventId]
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
if (input.result === "failed" && input.error) {
|
|
165
|
+
events.push({
|
|
166
|
+
...base,
|
|
167
|
+
type: "memory.denied",
|
|
168
|
+
tags: [...tags, "failed"],
|
|
169
|
+
content: {
|
|
170
|
+
kind: "text",
|
|
171
|
+
text: `Turn failed: ${trimText(input.error, 480)}`
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
if (input.packet.sections.length > 0) {
|
|
176
|
+
for (const section of input.packet.sections) {
|
|
177
|
+
events.push({
|
|
178
|
+
...base,
|
|
179
|
+
type: "memory.observed",
|
|
180
|
+
tags: [...tags, "section", section.heading.toLowerCase()],
|
|
181
|
+
content: {
|
|
182
|
+
kind: "text",
|
|
183
|
+
text: `${section.heading}: ${trimText(section.text, 420)}`
|
|
184
|
+
},
|
|
185
|
+
entities: [...entities, ...extractTokens(section.heading), ...extractTokens(section.text)]
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return events;
|
|
190
|
+
};
|
|
191
|
+
export const selectToolSummary = (toolEvents) => {
|
|
192
|
+
if (!Array.isArray(toolEvents) || toolEvents.length === 0) {
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
principal: {
|
|
197
|
+
agentId: "daimon",
|
|
198
|
+
scope: "global"
|
|
199
|
+
},
|
|
200
|
+
type: "memory.summarized",
|
|
201
|
+
scope: "global",
|
|
202
|
+
visibility: "global",
|
|
203
|
+
source: "mneme/tool",
|
|
204
|
+
content: {
|
|
205
|
+
kind: "text",
|
|
206
|
+
text: `Observed ${toolEvents.length} tool event(s) during turn.`
|
|
207
|
+
},
|
|
208
|
+
tags: ["tool", "summary"],
|
|
209
|
+
entities: ["tool"],
|
|
210
|
+
parentEventIds: []
|
|
211
|
+
};
|
|
212
|
+
};
|
|
213
|
+
export const runMemorySelection = (input) => {
|
|
214
|
+
const selection = runRecall({
|
|
215
|
+
actor: input.requester,
|
|
216
|
+
scopeIds: input.scopeIds,
|
|
217
|
+
events: input.events,
|
|
218
|
+
query: input.query,
|
|
219
|
+
maxTokens: clampTokenBudget((input.maxResults ?? 1) * 160)
|
|
220
|
+
});
|
|
221
|
+
const limited = input.maxResults !== undefined ? selection.selected.slice(0, input.maxResults) : selection.selected;
|
|
222
|
+
return limited.map((entry) => ({
|
|
223
|
+
event: entry.event,
|
|
224
|
+
decision: entry.decision,
|
|
225
|
+
representation: entry.representation,
|
|
226
|
+
scope: entry.scope
|
|
227
|
+
}));
|
|
228
|
+
};
|
|
229
|
+
export { runRecall };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/store/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { MemoryEvent, MemoryEventType, MemoryPrincipalRef } from "../contract/types.js";
|
|
2
|
+
export interface MemoryIndexQuery {
|
|
3
|
+
allowedScopes?: string[];
|
|
4
|
+
query?: string;
|
|
5
|
+
tags?: string[];
|
|
6
|
+
entities?: string[];
|
|
7
|
+
types?: MemoryEventType[];
|
|
8
|
+
principalAgentId?: string;
|
|
9
|
+
principalScope?: MemoryPrincipalRef["scope"];
|
|
10
|
+
principalQualifier?: string;
|
|
11
|
+
limit?: number;
|
|
12
|
+
offset?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface MemoryIndexSearchResult {
|
|
15
|
+
event: MemoryEvent;
|
|
16
|
+
score: number;
|
|
17
|
+
}
|
|
18
|
+
export interface SQLiteIndexConfig {
|
|
19
|
+
runtimeHomePath: string;
|
|
20
|
+
indexFilePath?: string;
|
|
21
|
+
}
|
|
22
|
+
export declare class SQLiteMemoryIndex {
|
|
23
|
+
private readonly db;
|
|
24
|
+
private readonly dbPath;
|
|
25
|
+
private ftsEnabled;
|
|
26
|
+
constructor(options: SQLiteIndexConfig);
|
|
27
|
+
rebuildFromStore(): Promise<void>;
|
|
28
|
+
rebuildFromEvents(events: MemoryEvent[]): Promise<void>;
|
|
29
|
+
query(input: MemoryIndexQuery): Promise<MemoryIndexSearchResult[]>;
|
|
30
|
+
close(): void;
|
|
31
|
+
private useFtsFallback;
|
|
32
|
+
private detectFts;
|
|
33
|
+
private rebuildSchema;
|
|
34
|
+
private runQuery;
|
|
35
|
+
private tableExists;
|
|
36
|
+
}
|
|
37
|
+
export declare const createMemoryIndex: (options: SQLiteIndexConfig) => SQLiteMemoryIndex;
|
|
38
|
+
//# sourceMappingURL=sqliteIndex.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqliteIndex.d.ts","sourceRoot":"","sources":["../../src/store/sqliteIndex.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,WAAW,EACX,eAAe,EACf,kBAAkB,EACnB,MAAM,sBAAsB,CAAC;AAE9B,MAAM,WAAW,gBAAgB;IAC/B,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,KAAK,CAAC,EAAE,eAAe,EAAE,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,kBAAkB,CAAC,OAAO,CAAC,CAAC;IAC7C,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,WAAW,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AA0BD,MAAM,WAAW,iBAAiB;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAe;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,UAAU,CAAsB;gBAE5B,OAAO,EAAE,iBAAiB;IAOhC,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAKjC,iBAAiB,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvD,KAAK,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,uBAAuB,EAAE,CAAC;IAKxE,KAAK,IAAI,IAAI;IAIb,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,SAAS;IAUjB,OAAO,CAAC,aAAa;IAyIrB,OAAO,CAAC,QAAQ;IA2GhB,OAAO,CAAC,WAAW;CAMpB;AAED,eAAO,MAAM,iBAAiB,GAAI,SAAS,iBAAiB,KAAG,iBAC/B,CAAC"}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { DatabaseSync } from "node:sqlite";
|
|
2
|
+
import { mkdirSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { JsonlMemoryStore } from "./store.js";
|
|
5
|
+
import { canonicalScopeKey } from "../identity/ids.js";
|
|
6
|
+
const defaultIndexFileName = "index.sqlite";
|
|
7
|
+
const normalizeScope = (scope) => canonicalScopeKey(scope);
|
|
8
|
+
const uniqueSorted = (values) => [...new Set(values.map((value) => value.trim().toLowerCase()).filter(Boolean))].sort();
|
|
9
|
+
const splitQueryTokens = (query) => {
|
|
10
|
+
return uniqueSorted(query.split(/[^a-z0-9]+/u).filter((value) => value.length >= 2));
|
|
11
|
+
};
|
|
12
|
+
const escapeLikeTerm = (value) => value.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_");
|
|
13
|
+
const placeholders = (count) => new Array(count).fill("?").join(", ");
|
|
14
|
+
export class SQLiteMemoryIndex {
|
|
15
|
+
db;
|
|
16
|
+
dbPath;
|
|
17
|
+
ftsEnabled;
|
|
18
|
+
constructor(options) {
|
|
19
|
+
const memoryPath = path.join(options.runtimeHomePath, "memory");
|
|
20
|
+
mkdirSync(memoryPath, { recursive: true });
|
|
21
|
+
this.dbPath = options.indexFilePath ?? path.join(memoryPath, defaultIndexFileName);
|
|
22
|
+
this.db = new DatabaseSync(this.dbPath);
|
|
23
|
+
}
|
|
24
|
+
async rebuildFromStore() {
|
|
25
|
+
const events = await new JsonlMemoryStore(path.dirname(path.dirname(this.dbPath))).read();
|
|
26
|
+
await this.rebuildFromEvents(events);
|
|
27
|
+
}
|
|
28
|
+
async rebuildFromEvents(events) {
|
|
29
|
+
this.rebuildSchema(events);
|
|
30
|
+
}
|
|
31
|
+
async query(input) {
|
|
32
|
+
const results = this.runQuery(input);
|
|
33
|
+
return results;
|
|
34
|
+
}
|
|
35
|
+
close() {
|
|
36
|
+
this.db.close();
|
|
37
|
+
}
|
|
38
|
+
useFtsFallback() {
|
|
39
|
+
if (this.ftsEnabled === undefined) {
|
|
40
|
+
this.ftsEnabled = this.detectFts();
|
|
41
|
+
}
|
|
42
|
+
return this.ftsEnabled;
|
|
43
|
+
}
|
|
44
|
+
detectFts() {
|
|
45
|
+
try {
|
|
46
|
+
this.db.exec("CREATE VIRTUAL TABLE memory_events_fts_probe USING fts5(term)");
|
|
47
|
+
this.db.exec("DROP TABLE memory_events_fts_probe");
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
catch (_error) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
rebuildSchema(events) {
|
|
55
|
+
const normalized = [...events]
|
|
56
|
+
.map((event) => {
|
|
57
|
+
const scope = normalizeScope(event.scope);
|
|
58
|
+
const tags = uniqueSorted(event.tags ?? []);
|
|
59
|
+
const entities = uniqueSorted(event.entities ?? []);
|
|
60
|
+
const searchable = JSON.stringify({
|
|
61
|
+
id: event.id,
|
|
62
|
+
type: event.type,
|
|
63
|
+
scope,
|
|
64
|
+
principal: event.principal,
|
|
65
|
+
visibility: event.visibility,
|
|
66
|
+
sensitivity: event.sensitivity,
|
|
67
|
+
source: event.source,
|
|
68
|
+
tags,
|
|
69
|
+
entities,
|
|
70
|
+
content: event.content
|
|
71
|
+
}).toLowerCase();
|
|
72
|
+
return {
|
|
73
|
+
event,
|
|
74
|
+
eventId: event.id,
|
|
75
|
+
checksum: event.checksum,
|
|
76
|
+
createdAt: event.createdAt,
|
|
77
|
+
scope,
|
|
78
|
+
tags,
|
|
79
|
+
entities,
|
|
80
|
+
searchable,
|
|
81
|
+
};
|
|
82
|
+
})
|
|
83
|
+
.sort((left, right) => {
|
|
84
|
+
const createdAt = Date.parse(right.createdAt) - Date.parse(left.createdAt);
|
|
85
|
+
if (createdAt !== 0) {
|
|
86
|
+
return createdAt;
|
|
87
|
+
}
|
|
88
|
+
const scopeDelta = left.scope.localeCompare(right.scope);
|
|
89
|
+
if (scopeDelta !== 0) {
|
|
90
|
+
return scopeDelta;
|
|
91
|
+
}
|
|
92
|
+
const idDelta = left.eventId.localeCompare(right.eventId);
|
|
93
|
+
if (idDelta !== 0) {
|
|
94
|
+
return idDelta;
|
|
95
|
+
}
|
|
96
|
+
return left.checksum.localeCompare(right.checksum);
|
|
97
|
+
});
|
|
98
|
+
const useFts = this.useFtsFallback();
|
|
99
|
+
this.db.exec("BEGIN");
|
|
100
|
+
try {
|
|
101
|
+
this.db.exec("DROP TABLE IF EXISTS memory_event_entities");
|
|
102
|
+
this.db.exec("DROP TABLE IF EXISTS memory_event_tags");
|
|
103
|
+
this.db.exec("DROP TABLE IF EXISTS memory_events_fts");
|
|
104
|
+
this.db.exec("DROP TABLE IF EXISTS memory_events");
|
|
105
|
+
this.db.exec(`CREATE TABLE memory_events (
|
|
106
|
+
event_id TEXT PRIMARY KEY,
|
|
107
|
+
created_at TEXT NOT NULL,
|
|
108
|
+
scope TEXT NOT NULL,
|
|
109
|
+
type TEXT NOT NULL,
|
|
110
|
+
principal_agent_id TEXT NOT NULL,
|
|
111
|
+
principal_scope TEXT NOT NULL,
|
|
112
|
+
principal_qualifier TEXT,
|
|
113
|
+
visibility TEXT NOT NULL,
|
|
114
|
+
sensitivity TEXT NOT NULL,
|
|
115
|
+
source TEXT NOT NULL,
|
|
116
|
+
checksum TEXT NOT NULL,
|
|
117
|
+
searchable_text TEXT NOT NULL,
|
|
118
|
+
event_json TEXT NOT NULL
|
|
119
|
+
)`);
|
|
120
|
+
this.db.exec("CREATE INDEX memory_events_scope_idx ON memory_events(scope)");
|
|
121
|
+
this.db.exec("CREATE INDEX memory_events_type_idx ON memory_events(type)");
|
|
122
|
+
this.db.exec("CREATE INDEX memory_events_principal_agent_idx ON memory_events(principal_agent_id)");
|
|
123
|
+
this.db.exec("CREATE INDEX memory_events_principal_scope_idx ON memory_events(principal_scope)");
|
|
124
|
+
this.db.exec("CREATE INDEX memory_events_principal_qualifier_idx ON memory_events(principal_qualifier)");
|
|
125
|
+
this.db.exec("CREATE TABLE memory_event_tags (event_id TEXT NOT NULL, tag TEXT NOT NULL)");
|
|
126
|
+
this.db.exec("CREATE TABLE memory_event_entities (event_id TEXT NOT NULL, entity TEXT NOT NULL)");
|
|
127
|
+
this.db.exec("CREATE INDEX memory_event_tags_event_idx ON memory_event_tags(event_id)");
|
|
128
|
+
this.db.exec("CREATE INDEX memory_event_tags_tag_idx ON memory_event_tags(tag)");
|
|
129
|
+
this.db.exec("CREATE INDEX memory_event_entities_event_idx ON memory_event_entities(event_id)");
|
|
130
|
+
this.db.exec("CREATE INDEX memory_event_entities_entity_idx ON memory_event_entities(entity)");
|
|
131
|
+
if (useFts) {
|
|
132
|
+
this.db.exec(`CREATE VIRTUAL TABLE memory_events_fts USING fts5(event_id UNINDEXED, text)`);
|
|
133
|
+
}
|
|
134
|
+
const insertEvent = this.db.prepare(`
|
|
135
|
+
INSERT INTO memory_events
|
|
136
|
+
(event_id, created_at, scope, type, principal_agent_id, principal_scope, principal_qualifier, visibility, sensitivity, source, checksum, searchable_text, event_json)
|
|
137
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
138
|
+
`);
|
|
139
|
+
const insertTag = this.db.prepare(`
|
|
140
|
+
INSERT INTO memory_event_tags (event_id, tag)
|
|
141
|
+
VALUES (?, ?)
|
|
142
|
+
`);
|
|
143
|
+
const insertEntity = this.db.prepare(`
|
|
144
|
+
INSERT INTO memory_event_entities (event_id, entity)
|
|
145
|
+
VALUES (?, ?)
|
|
146
|
+
`);
|
|
147
|
+
const insertFts = useFts ? this.db.prepare(`
|
|
148
|
+
INSERT INTO memory_events_fts (event_id, text)
|
|
149
|
+
VALUES (?, ?)
|
|
150
|
+
`) : undefined;
|
|
151
|
+
for (const event of normalized) {
|
|
152
|
+
const rawEvent = event.event;
|
|
153
|
+
insertEvent.run(rawEvent.id, rawEvent.createdAt, event.scope, rawEvent.type, rawEvent.principal.agentId, rawEvent.principal.scope, rawEvent.principal.qualifier ?? null, rawEvent.visibility, rawEvent.sensitivity, rawEvent.source, rawEvent.checksum, event.searchable, JSON.stringify(rawEvent));
|
|
154
|
+
for (const tag of event.tags) {
|
|
155
|
+
insertTag.run(rawEvent.id, tag);
|
|
156
|
+
}
|
|
157
|
+
for (const entity of event.entities) {
|
|
158
|
+
insertEntity.run(rawEvent.id, entity);
|
|
159
|
+
}
|
|
160
|
+
insertFts?.run(rawEvent.id, event.searchable);
|
|
161
|
+
}
|
|
162
|
+
this.db.exec("COMMIT");
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
this.db.exec("ROLLBACK");
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
runQuery(input) {
|
|
170
|
+
const where = [];
|
|
171
|
+
const whereParams = [];
|
|
172
|
+
const scoreParams = [];
|
|
173
|
+
const allowedScopes = uniqueSorted((input.allowedScopes ?? []).map(normalizeScope).filter(Boolean));
|
|
174
|
+
if (allowedScopes.length > 0) {
|
|
175
|
+
where.push(`e.scope IN (${placeholders(allowedScopes.length)})`);
|
|
176
|
+
whereParams.push(...allowedScopes);
|
|
177
|
+
}
|
|
178
|
+
const types = uniqueSorted(input.types ?? []);
|
|
179
|
+
if (types.length > 0) {
|
|
180
|
+
where.push(`e.type IN (${placeholders(types.length)})`);
|
|
181
|
+
whereParams.push(...types);
|
|
182
|
+
}
|
|
183
|
+
if (input.principalAgentId) {
|
|
184
|
+
where.push("e.principal_agent_id = ?");
|
|
185
|
+
whereParams.push(input.principalAgentId);
|
|
186
|
+
}
|
|
187
|
+
if (input.principalScope) {
|
|
188
|
+
where.push("e.principal_scope = ?");
|
|
189
|
+
whereParams.push(input.principalScope);
|
|
190
|
+
}
|
|
191
|
+
if (input.principalQualifier) {
|
|
192
|
+
where.push("e.principal_qualifier = ?");
|
|
193
|
+
whereParams.push(input.principalQualifier);
|
|
194
|
+
}
|
|
195
|
+
const tags = uniqueSorted(input.tags ?? []);
|
|
196
|
+
if (tags.length > 0) {
|
|
197
|
+
where.push(`
|
|
198
|
+
e.event_id IN (
|
|
199
|
+
SELECT event_id
|
|
200
|
+
FROM memory_event_tags
|
|
201
|
+
WHERE tag IN (${placeholders(tags.length)})
|
|
202
|
+
GROUP BY event_id
|
|
203
|
+
HAVING COUNT(DISTINCT tag) = ${tags.length}
|
|
204
|
+
)
|
|
205
|
+
`);
|
|
206
|
+
whereParams.push(...tags);
|
|
207
|
+
}
|
|
208
|
+
const entities = uniqueSorted(input.entities ?? []);
|
|
209
|
+
if (entities.length > 0) {
|
|
210
|
+
where.push(`
|
|
211
|
+
e.event_id IN (
|
|
212
|
+
SELECT event_id
|
|
213
|
+
FROM memory_event_entities
|
|
214
|
+
WHERE entity IN (${placeholders(entities.length)})
|
|
215
|
+
GROUP BY event_id
|
|
216
|
+
HAVING COUNT(DISTINCT entity) = ${entities.length}
|
|
217
|
+
)
|
|
218
|
+
`);
|
|
219
|
+
whereParams.push(...entities);
|
|
220
|
+
}
|
|
221
|
+
const queryTokens = splitQueryTokens(input.query ?? "");
|
|
222
|
+
let searchScore = "0";
|
|
223
|
+
if (queryTokens.length > 0) {
|
|
224
|
+
if (this.useFtsFallback() && this.tableExists("memory_events_fts")) {
|
|
225
|
+
const match = queryTokens.map((token) => `${token}*`).join(" ");
|
|
226
|
+
where.push(`e.event_id IN (SELECT event_id FROM memory_events_fts WHERE memory_events_fts MATCH ?)`);
|
|
227
|
+
whereParams.push(match);
|
|
228
|
+
searchScore = "1.0";
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
const scoreParts = [];
|
|
232
|
+
for (const token of queryTokens) {
|
|
233
|
+
const like = `%${escapeLikeTerm(token)}%`;
|
|
234
|
+
where.push(`LOWER(e.searchable_text) LIKE ? ESCAPE '\\'`);
|
|
235
|
+
whereParams.push(like);
|
|
236
|
+
scoreParts.push("CASE WHEN LOWER(e.searchable_text) LIKE ? ESCAPE '\\' THEN 1 ELSE 0 END");
|
|
237
|
+
scoreParams.push(like);
|
|
238
|
+
}
|
|
239
|
+
searchScore = `(${scoreParts.join(" + ")})`;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const clause = where.length > 0 ? `WHERE ${where.join(" AND ")}` : "";
|
|
243
|
+
const orderBy = "ORDER BY score DESC, e.created_at DESC, e.scope ASC, e.event_id ASC, e.checksum ASC";
|
|
244
|
+
const limit = input.limit ?? 100;
|
|
245
|
+
const safeLimit = Number.isFinite(limit) && limit > 0 ? limit : 100;
|
|
246
|
+
const offset = input.offset ?? 0;
|
|
247
|
+
const safeOffset = Number.isFinite(offset) && offset >= 0 ? offset : 0;
|
|
248
|
+
const statement = this.db.prepare(`
|
|
249
|
+
SELECT
|
|
250
|
+
e.event_json AS event_json,
|
|
251
|
+
${searchScore} AS score
|
|
252
|
+
FROM memory_events e
|
|
253
|
+
${clause}
|
|
254
|
+
GROUP BY e.event_id
|
|
255
|
+
${orderBy}
|
|
256
|
+
LIMIT ${safeLimit}
|
|
257
|
+
OFFSET ${safeOffset}
|
|
258
|
+
`);
|
|
259
|
+
const rows = statement.all(...scoreParams, ...whereParams);
|
|
260
|
+
return rows.map((row) => ({
|
|
261
|
+
event: JSON.parse(row.event_json),
|
|
262
|
+
score: Number(row.score)
|
|
263
|
+
}));
|
|
264
|
+
}
|
|
265
|
+
tableExists(tableName) {
|
|
266
|
+
const row = this.db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(tableName);
|
|
267
|
+
return Boolean(row?.name);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
export const createMemoryIndex = (options) => new SQLiteMemoryIndex(options);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { MemoryEvent, MemoryEventInput } from "../contract/types.js";
|
|
2
|
+
export interface MemoryStoreQuery {
|
|
3
|
+
scope?: string;
|
|
4
|
+
principalAgentId?: string;
|
|
5
|
+
principalScope?: string;
|
|
6
|
+
principalQualifier?: string;
|
|
7
|
+
types?: string[];
|
|
8
|
+
tags?: string[];
|
|
9
|
+
search?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface MemoryStore {
|
|
12
|
+
append(event: MemoryEventInput): Promise<MemoryEvent>;
|
|
13
|
+
appendBatch(events: MemoryEventInput[]): Promise<MemoryEvent[]>;
|
|
14
|
+
read(query?: MemoryStoreQuery): Promise<MemoryEvent[]>;
|
|
15
|
+
clear(): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
export declare class JsonlMemoryStore implements MemoryStore {
|
|
18
|
+
private readonly eventsPath;
|
|
19
|
+
private readonly dirPath;
|
|
20
|
+
constructor(runtimeHomePath: string);
|
|
21
|
+
private appendOne;
|
|
22
|
+
append(input: MemoryEventInput): Promise<MemoryEvent>;
|
|
23
|
+
appendBatch(inputs: MemoryEventInput[]): Promise<MemoryEvent[]>;
|
|
24
|
+
clear(): Promise<void>;
|
|
25
|
+
read(query?: MemoryStoreQuery): Promise<MemoryEvent[]>;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/store/store.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,WAAW,EACX,gBAAgB,EACjB,MAAM,sBAAsB,CAAC;AAE9B,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IACtD,WAAW,CAAC,MAAM,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IAChE,IAAI,CAAC,KAAK,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IACvD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,qBAAa,gBAAiB,YAAW,WAAW;IAClD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAErB,eAAe,EAAE,MAAM;YAKrB,SAAS;IA4BjB,MAAM,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,WAAW,CAAC;IAIrD,WAAW,CAAC,MAAM,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAa/D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAItB,IAAI,CAAC,KAAK,GAAE,gBAAqB,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;CAyEjE"}
|