@nuzo/memory-core 0.1.0 → 0.1.1
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/policy.d.ts +22 -1
- package/dist/policy.js +51 -8
- package/dist/ports.d.ts +6 -3
- package/dist/secrets.js +5 -0
- package/dist/service.d.ts +2 -1
- package/dist/service.js +156 -35
- package/dist/sqlite/adapter.d.ts +3 -3
- package/dist/sqlite/adapter.js +54 -12
- package/dist/sqlite/schema.d.ts +1 -1
- package/dist/sqlite/schema.js +13 -1
- package/dist/types.d.ts +22 -0
- package/package.json +2 -2
package/dist/policy.d.ts
CHANGED
|
@@ -1,12 +1,33 @@
|
|
|
1
1
|
import type { PolicyEngine, SecretScanner } from "./ports.js";
|
|
2
|
+
import { type MemoryScope } from "./types.js";
|
|
2
3
|
import type { ListMemoriesInput, MemoryRecord, RecallMemoriesInput, RememberMemoryInput, UpdateMemoryInput } from "./types.js";
|
|
3
4
|
export declare const memoryScopePattern: RegExp;
|
|
4
5
|
export declare const memoryTagPattern: RegExp;
|
|
6
|
+
export declare const memoryLimits: {
|
|
7
|
+
readonly actorLength: 256;
|
|
8
|
+
readonly contentLength: 8000;
|
|
9
|
+
readonly dateLength: 64;
|
|
10
|
+
readonly identifierLength: 256;
|
|
11
|
+
readonly importItems: 1000;
|
|
12
|
+
readonly queryLength: 2000;
|
|
13
|
+
readonly reasonLength: 1000;
|
|
14
|
+
readonly scopeLength: 256;
|
|
15
|
+
readonly sourceLength: 256;
|
|
16
|
+
readonly tags: 32;
|
|
17
|
+
};
|
|
18
|
+
export interface DefaultPolicyEngineOptions {
|
|
19
|
+
allowedScopes?: readonly MemoryScope[];
|
|
20
|
+
}
|
|
5
21
|
export declare class DefaultPolicyEngine implements PolicyEngine {
|
|
6
22
|
private readonly secretScanner;
|
|
7
|
-
|
|
23
|
+
private readonly allowedScopes;
|
|
24
|
+
constructor(secretScanner: SecretScanner, options?: DefaultPolicyEngineOptions);
|
|
8
25
|
assertCanRemember(input: RememberMemoryInput): Promise<void>;
|
|
9
26
|
assertCanUpdate(input: UpdateMemoryInput, current: MemoryRecord): Promise<void>;
|
|
27
|
+
assertCanForget(_input: {
|
|
28
|
+
id: string;
|
|
29
|
+
}, current: MemoryRecord): Promise<void>;
|
|
10
30
|
assertCanRecall(input: RecallMemoriesInput): Promise<void>;
|
|
11
31
|
assertCanList(input: ListMemoriesInput): Promise<void>;
|
|
32
|
+
private assertScopeAllowed;
|
|
12
33
|
}
|
package/dist/policy.js
CHANGED
|
@@ -1,21 +1,41 @@
|
|
|
1
|
-
import { invariant } from "./errors.js";
|
|
1
|
+
import { invariant, NuzoMemoryError } from "./errors.js";
|
|
2
2
|
import { memoryKinds } from "./types.js";
|
|
3
3
|
export const memoryScopePattern = /^(user|project|agent|team):[A-Za-z0-9._~:/-]+$/;
|
|
4
4
|
export const memoryTagPattern = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
5
|
+
export const memoryLimits = {
|
|
6
|
+
actorLength: 256,
|
|
7
|
+
contentLength: 8000,
|
|
8
|
+
dateLength: 64,
|
|
9
|
+
identifierLength: 256,
|
|
10
|
+
importItems: 1000,
|
|
11
|
+
queryLength: 2000,
|
|
12
|
+
reasonLength: 1000,
|
|
13
|
+
scopeLength: 256,
|
|
14
|
+
sourceLength: 256,
|
|
15
|
+
tags: 32,
|
|
16
|
+
};
|
|
5
17
|
export class DefaultPolicyEngine {
|
|
6
18
|
secretScanner;
|
|
7
|
-
|
|
19
|
+
allowedScopes;
|
|
20
|
+
constructor(secretScanner, options = {}) {
|
|
8
21
|
this.secretScanner = secretScanner;
|
|
22
|
+
this.allowedScopes = options.allowedScopes === undefined
|
|
23
|
+
? null
|
|
24
|
+
: new Set(options.allowedScopes);
|
|
9
25
|
}
|
|
10
26
|
async assertCanRemember(input) {
|
|
11
27
|
invariant(input.content.trim().length > 0, "MEMORY_CONTENT_EMPTY", "Memory content cannot be empty.");
|
|
12
|
-
invariant(input.content.length <=
|
|
28
|
+
invariant(input.content.length <= memoryLimits.contentLength, "MEMORY_CONTENT_TOO_LONG", "Memory content is too long.", { maxLength: memoryLimits.contentLength });
|
|
13
29
|
invariant(memoryKinds.includes(input.kind), "MEMORY_KIND_UNSUPPORTED", "Memory kind is not supported.", { kind: input.kind });
|
|
14
30
|
assertScope(input.scope);
|
|
31
|
+
this.assertScopeAllowed(input.scope);
|
|
15
32
|
invariant(input.source.trim().length > 0, "MEMORY_SOURCE_EMPTY", "Memory source cannot be empty.");
|
|
33
|
+
invariant(input.source.length <= memoryLimits.sourceLength, "MEMORY_SOURCE_TOO_LONG", "Memory source is too long.", { maxLength: memoryLimits.sourceLength });
|
|
16
34
|
const confidence = input.confidence ?? 1;
|
|
17
35
|
invariant(confidence >= 0 && confidence <= 1, "MEMORY_CONFIDENCE_INVALID", "Memory confidence must be between 0 and 1.", { confidence });
|
|
18
|
-
|
|
36
|
+
const tags = input.tags ?? [];
|
|
37
|
+
invariant(tags.length <= memoryLimits.tags, "MEMORY_TAG_LIMIT_EXCEEDED", "Memory has too many tags.", { maxTags: memoryLimits.tags });
|
|
38
|
+
for (const tag of tags) {
|
|
19
39
|
assertTag(tag);
|
|
20
40
|
}
|
|
21
41
|
const secretScan = await this.secretScanner.scan(input.content);
|
|
@@ -24,6 +44,7 @@ export class DefaultPolicyEngine {
|
|
|
24
44
|
});
|
|
25
45
|
}
|
|
26
46
|
async assertCanUpdate(input, current) {
|
|
47
|
+
this.assertScopeAllowed(current.scope);
|
|
27
48
|
await this.assertCanRemember({
|
|
28
49
|
content: input.content ?? current.content,
|
|
29
50
|
kind: input.kind ?? current.kind,
|
|
@@ -34,9 +55,17 @@ export class DefaultPolicyEngine {
|
|
|
34
55
|
});
|
|
35
56
|
invariant(input.actor.trim().length > 0, "MEMORY_ACTOR_EMPTY", "Memory actor cannot be empty.");
|
|
36
57
|
}
|
|
58
|
+
async assertCanForget(_input, current) {
|
|
59
|
+
this.assertScopeAllowed(current.scope);
|
|
60
|
+
}
|
|
37
61
|
async assertCanRecall(input) {
|
|
38
62
|
invariant(input.query.trim().length > 0, "RECALL_QUERY_EMPTY", "Recall query cannot be empty.");
|
|
63
|
+
invariant(input.query.length <= memoryLimits.queryLength, "RECALL_QUERY_TOO_LONG", "Recall query is too long.", { maxLength: memoryLimits.queryLength });
|
|
39
64
|
assertScope(input.scope);
|
|
65
|
+
this.assertScopeAllowed(input.scope);
|
|
66
|
+
if (input.includeGlobal === true) {
|
|
67
|
+
this.assertScopeAllowed("user:default");
|
|
68
|
+
}
|
|
40
69
|
const limit = input.limit ?? 8;
|
|
41
70
|
invariant(limit > 0 && limit <= 50, "RECALL_LIMIT_INVALID", "Recall limit must be 1-50.", {
|
|
42
71
|
limit,
|
|
@@ -45,16 +74,30 @@ export class DefaultPolicyEngine {
|
|
|
45
74
|
async assertCanList(input) {
|
|
46
75
|
if (input.scope !== undefined) {
|
|
47
76
|
assertScope(input.scope);
|
|
77
|
+
this.assertScopeAllowed(input.scope);
|
|
48
78
|
}
|
|
49
|
-
|
|
79
|
+
else if (this.allowedScopes !== null) {
|
|
80
|
+
throw new NuzoMemoryError("MEMORY_SCOPE_REQUIRED", "A scope is required for this restricted memory session.");
|
|
81
|
+
}
|
|
82
|
+
const tags = input.tags ?? [];
|
|
83
|
+
invariant(tags.length <= memoryLimits.tags, "MEMORY_TAG_LIMIT_EXCEEDED", "Memory filter has too many tags.", { maxTags: memoryLimits.tags });
|
|
84
|
+
for (const tag of tags) {
|
|
50
85
|
assertTag(tag);
|
|
51
86
|
}
|
|
52
87
|
}
|
|
88
|
+
assertScopeAllowed(scope) {
|
|
89
|
+
if (this.allowedScopes === null) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (!this.allowedScopes.has(scope)) {
|
|
93
|
+
throw new NuzoMemoryError("MEMORY_SCOPE_FORBIDDEN", "Memory scope is not authorized.", {
|
|
94
|
+
scope,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
53
98
|
}
|
|
54
99
|
function assertScope(scope) {
|
|
55
|
-
invariant(memoryScopePattern.test(scope), "MEMORY_SCOPE_INVALID", "Memory scope is invalid.", {
|
|
56
|
-
scope,
|
|
57
|
-
});
|
|
100
|
+
invariant(scope.length <= memoryLimits.scopeLength && memoryScopePattern.test(scope), "MEMORY_SCOPE_INVALID", "Memory scope is invalid.", { scope, maxLength: memoryLimits.scopeLength });
|
|
58
101
|
}
|
|
59
102
|
function assertTag(tag) {
|
|
60
103
|
invariant(memoryTagPattern.test(tag), "MEMORY_TAG_INVALID", "Memory tag is invalid.", {
|
package/dist/ports.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { ListMemoriesInput, MemoryEvent, MemoryRecord, RecallMemoriesInput, RecallMemoryResult, RememberMemoryInput, UpdateMemoryInput } from "./types.js";
|
|
2
2
|
export interface MemoryStore {
|
|
3
3
|
create(memory: MemoryRecord): Promise<void>;
|
|
4
|
-
update(memory: MemoryRecord): Promise<
|
|
4
|
+
update(memory: MemoryRecord, expectedRevision?: number): Promise<boolean>;
|
|
5
5
|
findById(id: string): Promise<MemoryRecord | null>;
|
|
6
6
|
list(filter: ListMemoriesInput): Promise<MemoryRecord[]>;
|
|
7
|
-
archive(id: string, archivedAt: Date): Promise<
|
|
8
|
-
delete(id: string): Promise<
|
|
7
|
+
archive(id: string, archivedAt: Date, expectedRevision?: number): Promise<boolean>;
|
|
8
|
+
delete(id: string, expectedRevision?: number): Promise<boolean>;
|
|
9
9
|
}
|
|
10
10
|
export interface SearchIndex {
|
|
11
11
|
index(memory: MemoryRecord): Promise<void>;
|
|
@@ -40,6 +40,9 @@ export interface SecretFinding {
|
|
|
40
40
|
export interface PolicyEngine {
|
|
41
41
|
assertCanRemember(input: RememberMemoryInput): Promise<void>;
|
|
42
42
|
assertCanUpdate(input: UpdateMemoryInput, current: MemoryRecord): Promise<void>;
|
|
43
|
+
assertCanForget(input: {
|
|
44
|
+
id: string;
|
|
45
|
+
}, current: MemoryRecord): Promise<void>;
|
|
43
46
|
assertCanRecall(input: RecallMemoriesInput): Promise<void>;
|
|
44
47
|
assertCanList(input: ListMemoriesInput): Promise<void>;
|
|
45
48
|
}
|
package/dist/secrets.js
CHANGED
|
@@ -9,6 +9,11 @@ const patterns = [
|
|
|
9
9
|
regex: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{20,}\b/,
|
|
10
10
|
message: "GitHub tokens should not be stored as memory.",
|
|
11
11
|
},
|
|
12
|
+
{
|
|
13
|
+
kind: "npm_token",
|
|
14
|
+
regex: /\bnpm_[A-Za-z0-9]{20,}\b/,
|
|
15
|
+
message: "npm access tokens should not be stored as memory.",
|
|
16
|
+
},
|
|
12
17
|
{
|
|
13
18
|
kind: "provider_api_key",
|
|
14
19
|
regex: /\b(?:sk-(?:proj-|ant-[A-Za-z0-9-]+-)?[A-Za-z0-9_-]{20,}|sk_live_[A-Za-z0-9]{20,}|AIza[A-Za-z0-9_-]{30,})\b/,
|
package/dist/service.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AuditLog, Clock, IdGenerator, MemoryStore, PolicyEngine, SearchIndex, TransactionManager } from "./ports.js";
|
|
2
|
-
import type { ExportMemoriesInput, ForgetMemoryInput, ForgetMemoriesInput, ForgetMemoriesResult, ImportMemoriesInput, ImportMemoriesResult, ListMemoriesInput, MemoryExportDocument, MemoryEvent, MemoryRecord, RecallMemoriesInput, RecallMemoryResult, RememberMemoryInput, UpdateMemoryInput } from "./types.js";
|
|
2
|
+
import type { CaptureSuggestionResult, ExportMemoriesInput, ForgetMemoryInput, ForgetMemoriesInput, ForgetMemoriesResult, ImportMemoriesInput, ImportMemoriesResult, ListMemoriesInput, MemoryExportDocument, MemoryEvent, MemoryRecord, RecallMemoriesInput, RecallMemoryResult, RememberMemoryInput, SuggestCaptureInput, UpdateMemoryInput } from "./types.js";
|
|
3
3
|
export interface MemoryServiceDependencies {
|
|
4
4
|
store: MemoryStore;
|
|
5
5
|
searchIndex: SearchIndex;
|
|
@@ -10,6 +10,7 @@ export interface MemoryServiceDependencies {
|
|
|
10
10
|
transactions?: TransactionManager;
|
|
11
11
|
}
|
|
12
12
|
export interface MemoryService {
|
|
13
|
+
suggestCapture(input: SuggestCaptureInput): Promise<CaptureSuggestionResult>;
|
|
13
14
|
remember(input: RememberMemoryInput): Promise<MemoryRecord>;
|
|
14
15
|
recall(input: RecallMemoriesInput): Promise<RecallMemoryResult[]>;
|
|
15
16
|
list(input?: ListMemoriesInput): Promise<MemoryRecord[]>;
|
package/dist/service.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { NuzoMemoryError } from "./errors.js";
|
|
2
|
+
import { memoryLimits } from "./policy.js";
|
|
2
3
|
import { memoryKinds } from "./types.js";
|
|
3
4
|
export function createMemoryService(dependencies) {
|
|
4
5
|
const { auditLog, clock, ids, policy, searchIndex, store, transactions } = dependencies;
|
|
@@ -6,11 +7,15 @@ export function createMemoryService(dependencies) {
|
|
|
6
7
|
? (operation) => transactions.run(operation)
|
|
7
8
|
: (operation) => operation();
|
|
8
9
|
async function forgetMemory(input) {
|
|
10
|
+
assertMemoryId(input.id);
|
|
9
11
|
assertActor(input.actor);
|
|
12
|
+
assertReason(input.reason);
|
|
10
13
|
const memory = await store.findById(input.id);
|
|
11
14
|
if (!memory) {
|
|
12
15
|
throw new NuzoMemoryError("MEMORY_NOT_FOUND", "Memory was not found.", { id: input.id });
|
|
13
16
|
}
|
|
17
|
+
assertExpectedRevision(input.expectedRevision, memory);
|
|
18
|
+
await policy.assertCanForget(input, memory);
|
|
14
19
|
const mode = input.mode ?? "archive";
|
|
15
20
|
const now = clock.now();
|
|
16
21
|
if (mode === "delete") {
|
|
@@ -18,7 +23,8 @@ export function createMemoryService(dependencies) {
|
|
|
18
23
|
throw new NuzoMemoryError("MEMORY_DELETE_CONFIRMATION_REQUIRED", "Hard delete requires explicit confirmation.", { id: input.id });
|
|
19
24
|
}
|
|
20
25
|
await runTransaction(async () => {
|
|
21
|
-
await store.delete(input.id);
|
|
26
|
+
const deleted = await store.delete(input.id, memory.revision);
|
|
27
|
+
assertRevisionCommitted(deleted, input.id, memory.revision);
|
|
22
28
|
await searchIndex.remove(input.id);
|
|
23
29
|
await auditLog.append({
|
|
24
30
|
id: ids.eventId(),
|
|
@@ -32,7 +38,8 @@ export function createMemoryService(dependencies) {
|
|
|
32
38
|
return;
|
|
33
39
|
}
|
|
34
40
|
await runTransaction(async () => {
|
|
35
|
-
await store.archive(input.id, now);
|
|
41
|
+
const archived = await store.archive(input.id, now, memory.revision);
|
|
42
|
+
assertRevisionCommitted(archived, input.id, memory.revision);
|
|
36
43
|
await searchIndex.remove(input.id);
|
|
37
44
|
await auditLog.append({
|
|
38
45
|
id: ids.eventId(),
|
|
@@ -45,11 +52,36 @@ export function createMemoryService(dependencies) {
|
|
|
45
52
|
});
|
|
46
53
|
}
|
|
47
54
|
return {
|
|
55
|
+
async suggestCapture(input) {
|
|
56
|
+
assertCaptureReason(input.reason);
|
|
57
|
+
await policy.assertCanRemember(input);
|
|
58
|
+
const draft = {
|
|
59
|
+
content: input.content.trim(),
|
|
60
|
+
kind: input.kind,
|
|
61
|
+
scope: input.scope,
|
|
62
|
+
tags: [...new Set(input.tags ?? [])],
|
|
63
|
+
source: input.source,
|
|
64
|
+
confidence: input.confidence ?? 1,
|
|
65
|
+
reason: input.reason.trim(),
|
|
66
|
+
};
|
|
67
|
+
const duplicateKey = toCaptureDuplicateKey(draft.content);
|
|
68
|
+
const memories = await store.list({ scope: draft.scope });
|
|
69
|
+
const duplicate = memories.find((memory) => (memory.archivedAt === null &&
|
|
70
|
+
toCaptureDuplicateKey(memory.content) === duplicateKey)) ?? null;
|
|
71
|
+
return {
|
|
72
|
+
status: duplicate ? "duplicate" : "ready",
|
|
73
|
+
memoryWrites: false,
|
|
74
|
+
requiresConfirmation: true,
|
|
75
|
+
draft,
|
|
76
|
+
duplicate,
|
|
77
|
+
};
|
|
78
|
+
},
|
|
48
79
|
async remember(input) {
|
|
49
80
|
await policy.assertCanRemember(input);
|
|
50
81
|
const now = clock.now();
|
|
51
82
|
const memory = {
|
|
52
83
|
id: ids.memoryId(),
|
|
84
|
+
revision: 1,
|
|
53
85
|
scope: input.scope,
|
|
54
86
|
kind: input.kind,
|
|
55
87
|
content: input.content.trim(),
|
|
@@ -81,19 +113,26 @@ export function createMemoryService(dependencies) {
|
|
|
81
113
|
...input,
|
|
82
114
|
limit: input.limit ?? 8,
|
|
83
115
|
});
|
|
84
|
-
if (input.recordUsage
|
|
116
|
+
if (input.recordUsage !== true) {
|
|
85
117
|
return results;
|
|
86
118
|
}
|
|
87
119
|
const now = clock.now();
|
|
88
120
|
await runTransaction(async () => {
|
|
89
121
|
for (const result of results) {
|
|
90
|
-
await store.
|
|
91
|
-
|
|
122
|
+
const current = await store.findById(result.memory.id);
|
|
123
|
+
if (!current || current.archivedAt !== null) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const updated = {
|
|
127
|
+
...current,
|
|
128
|
+
revision: current.revision + 1,
|
|
92
129
|
lastUsedAt: now,
|
|
93
|
-
}
|
|
130
|
+
};
|
|
131
|
+
const committed = await store.update(updated, current.revision);
|
|
132
|
+
assertRevisionCommitted(committed, current.id, current.revision);
|
|
94
133
|
await auditLog.append({
|
|
95
134
|
id: ids.eventId(),
|
|
96
|
-
memoryId:
|
|
135
|
+
memoryId: current.id,
|
|
97
136
|
eventType: "memory.recalled",
|
|
98
137
|
actor: "core",
|
|
99
138
|
payload: { query: input.query, score: result.score },
|
|
@@ -108,10 +147,12 @@ export function createMemoryService(dependencies) {
|
|
|
108
147
|
return store.list(input);
|
|
109
148
|
},
|
|
110
149
|
async update(input) {
|
|
150
|
+
assertMemoryId(input.id);
|
|
111
151
|
const current = await store.findById(input.id);
|
|
112
152
|
if (!current) {
|
|
113
153
|
throw new NuzoMemoryError("MEMORY_NOT_FOUND", "Memory was not found.", { id: input.id });
|
|
114
154
|
}
|
|
155
|
+
assertExpectedRevision(input.expectedRevision, current);
|
|
115
156
|
const hasChanges = input.content !== undefined ||
|
|
116
157
|
input.kind !== undefined ||
|
|
117
158
|
input.scope !== undefined ||
|
|
@@ -125,6 +166,7 @@ export function createMemoryService(dependencies) {
|
|
|
125
166
|
await policy.assertCanUpdate(input, current);
|
|
126
167
|
const updated = {
|
|
127
168
|
...current,
|
|
169
|
+
revision: current.revision + 1,
|
|
128
170
|
content: input.content?.trim() ?? current.content,
|
|
129
171
|
kind: input.kind ?? current.kind,
|
|
130
172
|
scope: input.scope ?? current.scope,
|
|
@@ -133,7 +175,8 @@ export function createMemoryService(dependencies) {
|
|
|
133
175
|
updatedAt: clock.now(),
|
|
134
176
|
};
|
|
135
177
|
await runTransaction(async () => {
|
|
136
|
-
await store.update(updated);
|
|
178
|
+
const committed = await store.update(updated, current.revision);
|
|
179
|
+
assertRevisionCommitted(committed, input.id, current.revision);
|
|
137
180
|
await searchIndex.index(updated);
|
|
138
181
|
await auditLog.append({
|
|
139
182
|
id: ids.eventId(),
|
|
@@ -155,9 +198,7 @@ export function createMemoryService(dependencies) {
|
|
|
155
198
|
return updated;
|
|
156
199
|
},
|
|
157
200
|
async history(memoryId) {
|
|
158
|
-
|
|
159
|
-
throw new NuzoMemoryError("MEMORY_ID_EMPTY", "Memory ID cannot be empty.");
|
|
160
|
-
}
|
|
201
|
+
assertMemoryId(memoryId);
|
|
161
202
|
return auditLog.list(memoryId);
|
|
162
203
|
},
|
|
163
204
|
async exportMemories(input) {
|
|
@@ -188,9 +229,6 @@ export function createMemoryService(dependencies) {
|
|
|
188
229
|
async importMemories(input) {
|
|
189
230
|
assertActor(input.actor);
|
|
190
231
|
assertExportDocument(input.document);
|
|
191
|
-
const planned = [];
|
|
192
|
-
const duplicateKeysByScope = new Map();
|
|
193
|
-
let skipped = 0;
|
|
194
232
|
for (const item of input.document.memories) {
|
|
195
233
|
const scope = input.scope ?? item.scope;
|
|
196
234
|
await policy.assertCanRemember({
|
|
@@ -201,37 +239,53 @@ export function createMemoryService(dependencies) {
|
|
|
201
239
|
source: item.source,
|
|
202
240
|
confidence: item.confidence,
|
|
203
241
|
});
|
|
204
|
-
const tags = [...new Set(item.tags)];
|
|
205
|
-
let duplicateKeys = duplicateKeysByScope.get(scope);
|
|
206
|
-
if (!duplicateKeys) {
|
|
207
|
-
const existing = await store.list({ scope, includeArchived: true });
|
|
208
|
-
duplicateKeys = new Set(existing.map(toImportDuplicateKey));
|
|
209
|
-
duplicateKeysByScope.set(scope, duplicateKeys);
|
|
210
|
-
}
|
|
211
|
-
const duplicateKey = toImportDuplicateKey({
|
|
212
|
-
scope,
|
|
213
|
-
kind: item.kind,
|
|
214
|
-
content: item.content,
|
|
215
|
-
tags,
|
|
216
|
-
});
|
|
217
|
-
if (duplicateKeys.has(duplicateKey)) {
|
|
218
|
-
skipped += 1;
|
|
219
|
-
continue;
|
|
220
|
-
}
|
|
221
|
-
duplicateKeys.add(duplicateKey);
|
|
222
|
-
planned.push({ item, scope, tags });
|
|
223
242
|
}
|
|
243
|
+
const planImport = async () => {
|
|
244
|
+
const planned = [];
|
|
245
|
+
const duplicateKeysByScope = new Map();
|
|
246
|
+
let skipped = 0;
|
|
247
|
+
for (const item of input.document.memories) {
|
|
248
|
+
const scope = input.scope ?? item.scope;
|
|
249
|
+
const tags = [...new Set(item.tags)];
|
|
250
|
+
let duplicateKeys = duplicateKeysByScope.get(scope);
|
|
251
|
+
if (!duplicateKeys) {
|
|
252
|
+
const existing = await store.list({ scope, includeArchived: true });
|
|
253
|
+
duplicateKeys = new Set(existing.map(toImportDuplicateKey));
|
|
254
|
+
duplicateKeysByScope.set(scope, duplicateKeys);
|
|
255
|
+
}
|
|
256
|
+
const duplicateKey = toImportDuplicateKey({
|
|
257
|
+
scope,
|
|
258
|
+
kind: item.kind,
|
|
259
|
+
content: item.content,
|
|
260
|
+
tags,
|
|
261
|
+
});
|
|
262
|
+
if (duplicateKeys.has(duplicateKey)) {
|
|
263
|
+
skipped += 1;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
duplicateKeys.add(duplicateKey);
|
|
267
|
+
planned.push({ item, scope, tags });
|
|
268
|
+
}
|
|
269
|
+
return { planned, skipped };
|
|
270
|
+
};
|
|
224
271
|
if (input.dryRun === true) {
|
|
272
|
+
const { planned, skipped } = await planImport();
|
|
225
273
|
return {
|
|
226
274
|
imported: planned.length,
|
|
227
275
|
skipped,
|
|
228
276
|
dryRun: true,
|
|
229
277
|
};
|
|
230
278
|
}
|
|
279
|
+
let imported = 0;
|
|
280
|
+
let skipped = 0;
|
|
231
281
|
await runTransaction(async () => {
|
|
232
|
-
|
|
282
|
+
const plan = await planImport();
|
|
283
|
+
imported = plan.planned.length;
|
|
284
|
+
skipped = plan.skipped;
|
|
285
|
+
for (const { item, scope, tags } of plan.planned) {
|
|
233
286
|
const memory = {
|
|
234
287
|
id: ids.memoryId(),
|
|
288
|
+
revision: 1,
|
|
235
289
|
scope,
|
|
236
290
|
kind: item.kind,
|
|
237
291
|
content: item.content.trim(),
|
|
@@ -260,7 +314,7 @@ export function createMemoryService(dependencies) {
|
|
|
260
314
|
}
|
|
261
315
|
});
|
|
262
316
|
return {
|
|
263
|
-
imported
|
|
317
|
+
imported,
|
|
264
318
|
skipped,
|
|
265
319
|
dryRun: false,
|
|
266
320
|
};
|
|
@@ -277,6 +331,7 @@ export function createMemoryService(dependencies) {
|
|
|
277
331
|
throw new NuzoMemoryError("MEMORY_BULK_SELECTOR_CONFLICT", "Bulk forget all cannot be combined with scope or tags.");
|
|
278
332
|
}
|
|
279
333
|
assertActor(input.actor);
|
|
334
|
+
assertReason(input.reason);
|
|
280
335
|
await policy.assertCanList({
|
|
281
336
|
...(input.scope === undefined ? {} : { scope: input.scope }),
|
|
282
337
|
...(input.tags === undefined ? {} : { tags: input.tags }),
|
|
@@ -299,6 +354,7 @@ export function createMemoryService(dependencies) {
|
|
|
299
354
|
for (const memory of memories) {
|
|
300
355
|
const forgetInput = {
|
|
301
356
|
id: memory.id,
|
|
357
|
+
expectedRevision: memory.revision,
|
|
302
358
|
mode,
|
|
303
359
|
actor: input.actor,
|
|
304
360
|
};
|
|
@@ -321,10 +377,63 @@ export function createMemoryService(dependencies) {
|
|
|
321
377
|
},
|
|
322
378
|
};
|
|
323
379
|
}
|
|
380
|
+
function assertExpectedRevision(expectedRevision, memory) {
|
|
381
|
+
if (expectedRevision === undefined) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
if (!Number.isInteger(expectedRevision) || expectedRevision < 1) {
|
|
385
|
+
throw new NuzoMemoryError("MEMORY_REVISION_INVALID", "Memory revision is invalid.", {
|
|
386
|
+
expectedRevision,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
if (memory.revision !== expectedRevision) {
|
|
390
|
+
throw new NuzoMemoryError("MEMORY_REVISION_CONFLICT", "Memory changed before this operation could commit.", {
|
|
391
|
+
id: memory.id,
|
|
392
|
+
expectedRevision,
|
|
393
|
+
currentRevision: memory.revision,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
function assertRevisionCommitted(committed, id, expectedRevision) {
|
|
398
|
+
if (!committed) {
|
|
399
|
+
throw new NuzoMemoryError("MEMORY_REVISION_CONFLICT", "Memory changed before this operation could commit.", {
|
|
400
|
+
id,
|
|
401
|
+
expectedRevision,
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
324
405
|
function assertActor(actor) {
|
|
325
406
|
if (actor.trim().length === 0) {
|
|
326
407
|
throw new NuzoMemoryError("MEMORY_ACTOR_EMPTY", "Memory actor cannot be empty.");
|
|
327
408
|
}
|
|
409
|
+
if (actor.length > memoryLimits.actorLength) {
|
|
410
|
+
throw new NuzoMemoryError("MEMORY_ACTOR_INVALID", "Memory actor is too long.", {
|
|
411
|
+
maxLength: memoryLimits.actorLength,
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
function assertMemoryId(memoryId) {
|
|
416
|
+
if (memoryId.trim().length === 0) {
|
|
417
|
+
throw new NuzoMemoryError("MEMORY_ID_EMPTY", "Memory ID cannot be empty.");
|
|
418
|
+
}
|
|
419
|
+
if (memoryId.length > memoryLimits.identifierLength) {
|
|
420
|
+
throw new NuzoMemoryError("MEMORY_ID_INVALID", "Memory ID is too long.", {
|
|
421
|
+
maxLength: memoryLimits.identifierLength,
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
function assertReason(reason) {
|
|
426
|
+
if (reason !== undefined && reason.length > memoryLimits.reasonLength) {
|
|
427
|
+
throw new NuzoMemoryError("MEMORY_REASON_TOO_LONG", "Memory reason is too long.", {
|
|
428
|
+
maxLength: memoryLimits.reasonLength,
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
function assertCaptureReason(reason) {
|
|
433
|
+
if (reason.trim().length === 0) {
|
|
434
|
+
throw new NuzoMemoryError("MEMORY_REASON_EMPTY", "Memory reason cannot be empty.");
|
|
435
|
+
}
|
|
436
|
+
assertReason(reason);
|
|
328
437
|
}
|
|
329
438
|
function toExportItem(memory) {
|
|
330
439
|
return {
|
|
@@ -355,6 +464,9 @@ function assertExportDocument(document) {
|
|
|
355
464
|
if (!Array.isArray(value.memories)) {
|
|
356
465
|
throw new NuzoMemoryError("MEMORY_EXPORT_INVALID", "Memory export document is invalid.");
|
|
357
466
|
}
|
|
467
|
+
if (value.memories.length > memoryLimits.importItems) {
|
|
468
|
+
throw new NuzoMemoryError("MEMORY_IMPORT_LIMIT_EXCEEDED", "Memory import contains too many items.", { maxItems: memoryLimits.importItems });
|
|
469
|
+
}
|
|
358
470
|
value.memories.forEach(assertExportItem);
|
|
359
471
|
}
|
|
360
472
|
function assertExportItem(item, index) {
|
|
@@ -430,6 +542,12 @@ function throwInvalidExportItem(index, reason, details = {}) {
|
|
|
430
542
|
});
|
|
431
543
|
}
|
|
432
544
|
function parseExportDate(value, field) {
|
|
545
|
+
if (value.length > memoryLimits.dateLength) {
|
|
546
|
+
throw new NuzoMemoryError("MEMORY_EXPORT_INVALID", "Memory export contains an invalid date.", {
|
|
547
|
+
field,
|
|
548
|
+
maxLength: memoryLimits.dateLength,
|
|
549
|
+
});
|
|
550
|
+
}
|
|
433
551
|
const date = new Date(value);
|
|
434
552
|
if (Number.isNaN(date.getTime())) {
|
|
435
553
|
throw new NuzoMemoryError("MEMORY_EXPORT_INVALID", "Memory export contains an invalid date.", {
|
|
@@ -450,6 +568,9 @@ function toImportDuplicateKey(memory) {
|
|
|
450
568
|
function normalizeContent(content) {
|
|
451
569
|
return content.trim().replace(/\s+/g, " ");
|
|
452
570
|
}
|
|
571
|
+
function toCaptureDuplicateKey(content) {
|
|
572
|
+
return normalizeContent(content).toLowerCase();
|
|
573
|
+
}
|
|
453
574
|
function normalizeTags(tags) {
|
|
454
575
|
return [...new Set(tags)].sort();
|
|
455
576
|
}
|
package/dist/sqlite/adapter.d.ts
CHANGED
|
@@ -12,10 +12,10 @@ export declare class SQLiteMemoryDatabase implements MemoryStore, SearchIndex, A
|
|
|
12
12
|
getSchemaVersion(): number;
|
|
13
13
|
run<T>(operation: () => Promise<T>): Promise<T>;
|
|
14
14
|
create(memory: MemoryRecord): Promise<void>;
|
|
15
|
-
update(memory: MemoryRecord): Promise<
|
|
15
|
+
update(memory: MemoryRecord, expectedRevision?: number): Promise<boolean>;
|
|
16
16
|
findById(id: string): Promise<MemoryRecord | null>;
|
|
17
|
-
archive(id: string, archivedAt: Date): Promise<
|
|
18
|
-
delete(id: string): Promise<
|
|
17
|
+
archive(id: string, archivedAt: Date, expectedRevision?: number): Promise<boolean>;
|
|
18
|
+
delete(id: string, expectedRevision?: number): Promise<boolean>;
|
|
19
19
|
index(memory: MemoryRecord): Promise<void>;
|
|
20
20
|
remove(memoryId: string): Promise<void>;
|
|
21
21
|
search(input: RecallMemoriesInput): Promise<RecallMemoryResult[]>;
|
package/dist/sqlite/adapter.js
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
import Database from "better-sqlite3";
|
|
2
|
+
import { chmodSync, closeSync, existsSync, openSync } from "node:fs";
|
|
2
3
|
import { NuzoMemoryError } from "../errors.js";
|
|
3
4
|
import { migrate } from "./schema.js";
|
|
4
5
|
export class SQLiteMemoryDatabase {
|
|
5
6
|
database;
|
|
6
7
|
transactionQueue = Promise.resolve();
|
|
7
8
|
constructor(options) {
|
|
9
|
+
createPrivateDatabaseFile(options.path);
|
|
8
10
|
this.database = new Database(options.path);
|
|
9
11
|
try {
|
|
10
12
|
migrate(this.database);
|
|
13
|
+
protectDatabaseFiles(options.path);
|
|
11
14
|
}
|
|
12
15
|
catch (error) {
|
|
13
16
|
this.database.close();
|
|
17
|
+
protectDatabaseFiles(options.path);
|
|
14
18
|
throw error;
|
|
15
19
|
}
|
|
16
20
|
}
|
|
17
21
|
close() {
|
|
18
22
|
this.database.close();
|
|
23
|
+
protectDatabaseFiles(this.database.name);
|
|
19
24
|
}
|
|
20
25
|
getSchemaVersion() {
|
|
21
26
|
return this.database.pragma("user_version", { simple: true });
|
|
@@ -31,6 +36,7 @@ export class SQLiteMemoryDatabase {
|
|
|
31
36
|
try {
|
|
32
37
|
this.database.exec("BEGIN IMMEDIATE");
|
|
33
38
|
started = true;
|
|
39
|
+
protectDatabaseFiles(this.database.name);
|
|
34
40
|
const result = await operation();
|
|
35
41
|
this.database.exec("COMMIT");
|
|
36
42
|
return result;
|
|
@@ -42,6 +48,7 @@ export class SQLiteMemoryDatabase {
|
|
|
42
48
|
throw error;
|
|
43
49
|
}
|
|
44
50
|
finally {
|
|
51
|
+
protectDatabaseFiles(this.database.name);
|
|
45
52
|
release();
|
|
46
53
|
}
|
|
47
54
|
}
|
|
@@ -59,11 +66,13 @@ export class SQLiteMemoryDatabase {
|
|
|
59
66
|
`)
|
|
60
67
|
.run(toMemoryRow(memory));
|
|
61
68
|
}
|
|
62
|
-
async update(memory) {
|
|
63
|
-
|
|
69
|
+
async update(memory, expectedRevision) {
|
|
70
|
+
const where = expectedRevision === undefined ? "id = @id" : "id = @id AND revision = @expected_revision";
|
|
71
|
+
const result = this.database
|
|
64
72
|
.prepare(`
|
|
65
73
|
UPDATE memories
|
|
66
|
-
SET
|
|
74
|
+
SET revision = @revision,
|
|
75
|
+
scope = @scope,
|
|
67
76
|
kind = @kind,
|
|
68
77
|
content = @content,
|
|
69
78
|
tags = @tags,
|
|
@@ -73,21 +82,37 @@ export class SQLiteMemoryDatabase {
|
|
|
73
82
|
updated_at = @updated_at,
|
|
74
83
|
last_used_at = @last_used_at,
|
|
75
84
|
archived_at = @archived_at
|
|
76
|
-
WHERE
|
|
85
|
+
WHERE ${where}
|
|
77
86
|
`)
|
|
78
|
-
.run(
|
|
87
|
+
.run({
|
|
88
|
+
...toMemoryRow(memory),
|
|
89
|
+
expected_revision: expectedRevision,
|
|
90
|
+
});
|
|
91
|
+
return result.changes === 1;
|
|
79
92
|
}
|
|
80
93
|
async findById(id) {
|
|
81
94
|
const row = this.database.prepare("SELECT * FROM memories WHERE id = ?").get(id);
|
|
82
95
|
return row ? fromMemoryRow(row) : null;
|
|
83
96
|
}
|
|
84
|
-
async archive(id, archivedAt) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
.
|
|
97
|
+
async archive(id, archivedAt, expectedRevision) {
|
|
98
|
+
const where = expectedRevision === undefined ? "id = @id" : "id = @id AND revision = @expected_revision";
|
|
99
|
+
const result = this.database
|
|
100
|
+
.prepare(`
|
|
101
|
+
UPDATE memories
|
|
102
|
+
SET revision = revision + 1,
|
|
103
|
+
archived_at = @archived_at,
|
|
104
|
+
updated_at = @archived_at
|
|
105
|
+
WHERE ${where}
|
|
106
|
+
`)
|
|
107
|
+
.run({ id, archived_at: archivedAt.toISOString(), expected_revision: expectedRevision });
|
|
108
|
+
return result.changes === 1;
|
|
88
109
|
}
|
|
89
|
-
async delete(id) {
|
|
90
|
-
|
|
110
|
+
async delete(id, expectedRevision) {
|
|
111
|
+
const where = expectedRevision === undefined ? "id = ?" : "id = ? AND revision = ?";
|
|
112
|
+
const result = expectedRevision === undefined
|
|
113
|
+
? this.database.prepare(`DELETE FROM memories WHERE ${where}`).run(id)
|
|
114
|
+
: this.database.prepare(`DELETE FROM memories WHERE ${where}`).run(id, expectedRevision);
|
|
115
|
+
return result.changes === 1;
|
|
91
116
|
}
|
|
92
117
|
async index(memory) {
|
|
93
118
|
this.database.prepare("DELETE FROM memories_fts WHERE id = ?").run(memory.id);
|
|
@@ -170,6 +195,7 @@ export class SQLiteMemoryDatabase {
|
|
|
170
195
|
function toMemoryRow(memory) {
|
|
171
196
|
return {
|
|
172
197
|
id: memory.id,
|
|
198
|
+
revision: memory.revision,
|
|
173
199
|
scope: memory.scope,
|
|
174
200
|
kind: memory.kind,
|
|
175
201
|
content: memory.content,
|
|
@@ -185,6 +211,7 @@ function toMemoryRow(memory) {
|
|
|
185
211
|
function fromMemoryRow(row) {
|
|
186
212
|
return {
|
|
187
213
|
id: row.id,
|
|
214
|
+
revision: row.revision,
|
|
188
215
|
scope: row.scope,
|
|
189
216
|
kind: row.kind,
|
|
190
217
|
content: row.content,
|
|
@@ -234,8 +261,23 @@ function parsePayload(value) {
|
|
|
234
261
|
function toFtsQuery(query) {
|
|
235
262
|
return query
|
|
236
263
|
.trim()
|
|
237
|
-
.split(
|
|
264
|
+
.split(/[^\p{L}\p{N}_]+/u)
|
|
238
265
|
.filter(Boolean)
|
|
239
266
|
.map((term) => `"${term.replaceAll('"', '""')}"`)
|
|
240
267
|
.join(" OR ");
|
|
241
268
|
}
|
|
269
|
+
function protectDatabaseFiles(path) {
|
|
270
|
+
for (const candidate of [path, `${path}-wal`, `${path}-shm`]) {
|
|
271
|
+
if (existsSync(candidate)) {
|
|
272
|
+
chmodSync(candidate, 0o600);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
function createPrivateDatabaseFile(path) {
|
|
277
|
+
if (path === ":memory:") {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const descriptor = openSync(path, "a", 0o600);
|
|
281
|
+
closeSync(descriptor);
|
|
282
|
+
chmodSync(path, 0o600);
|
|
283
|
+
}
|
package/dist/sqlite/schema.d.ts
CHANGED
package/dist/sqlite/schema.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { NuzoMemoryError } from "../errors.js";
|
|
2
|
-
export const schemaVersion =
|
|
2
|
+
export const schemaVersion = 2;
|
|
3
3
|
export function migrate(database) {
|
|
4
4
|
database.pragma("journal_mode = WAL");
|
|
5
|
+
database.pragma("busy_timeout = 5000");
|
|
5
6
|
database.pragma("foreign_keys = ON");
|
|
6
7
|
const currentVersion = database.pragma("user_version", { simple: true });
|
|
7
8
|
if (currentVersion > schemaVersion) {
|
|
@@ -12,6 +13,10 @@ export function migrate(database) {
|
|
|
12
13
|
}
|
|
13
14
|
if (currentVersion < 1) {
|
|
14
15
|
migrateToV1(database);
|
|
16
|
+
database.pragma("user_version = 1");
|
|
17
|
+
}
|
|
18
|
+
if (currentVersion < 2) {
|
|
19
|
+
migrateToV2(database);
|
|
15
20
|
database.pragma(`user_version = ${schemaVersion}`);
|
|
16
21
|
}
|
|
17
22
|
}
|
|
@@ -19,6 +24,7 @@ function migrateToV1(database) {
|
|
|
19
24
|
database.exec(`
|
|
20
25
|
CREATE TABLE IF NOT EXISTS memories (
|
|
21
26
|
id TEXT PRIMARY KEY,
|
|
27
|
+
revision INTEGER NOT NULL DEFAULT 1,
|
|
22
28
|
scope TEXT NOT NULL,
|
|
23
29
|
kind TEXT NOT NULL,
|
|
24
30
|
content TEXT NOT NULL,
|
|
@@ -52,3 +58,9 @@ function migrateToV1(database) {
|
|
|
52
58
|
CREATE INDEX IF NOT EXISTS idx_memory_events_memory_id ON memory_events(memory_id);
|
|
53
59
|
`);
|
|
54
60
|
}
|
|
61
|
+
function migrateToV2(database) {
|
|
62
|
+
const columns = database.pragma("table_info(memories)");
|
|
63
|
+
if (!columns.some((column) => column.name === "revision")) {
|
|
64
|
+
database.exec("ALTER TABLE memories ADD COLUMN revision INTEGER NOT NULL DEFAULT 1;");
|
|
65
|
+
}
|
|
66
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export declare const memoryKinds: readonly ["preference", "project_decision", "f
|
|
|
3
3
|
export type MemoryScope = `user:${string}` | `project:${string}` | `agent:${string}` | `team:${string}`;
|
|
4
4
|
export interface MemoryRecord {
|
|
5
5
|
id: string;
|
|
6
|
+
revision: number;
|
|
6
7
|
scope: MemoryScope;
|
|
7
8
|
kind: MemoryKind;
|
|
8
9
|
content: string;
|
|
@@ -30,6 +31,25 @@ export interface RememberMemoryInput {
|
|
|
30
31
|
source: string;
|
|
31
32
|
confidence?: number;
|
|
32
33
|
}
|
|
34
|
+
export interface SuggestCaptureInput extends RememberMemoryInput {
|
|
35
|
+
reason: string;
|
|
36
|
+
}
|
|
37
|
+
export interface CaptureSuggestionDraft {
|
|
38
|
+
content: string;
|
|
39
|
+
kind: MemoryKind;
|
|
40
|
+
scope: MemoryScope;
|
|
41
|
+
tags: string[];
|
|
42
|
+
source: string;
|
|
43
|
+
confidence: number;
|
|
44
|
+
reason: string;
|
|
45
|
+
}
|
|
46
|
+
export interface CaptureSuggestionResult {
|
|
47
|
+
status: "ready" | "duplicate";
|
|
48
|
+
memoryWrites: false;
|
|
49
|
+
requiresConfirmation: true;
|
|
50
|
+
draft: CaptureSuggestionDraft;
|
|
51
|
+
duplicate: MemoryRecord | null;
|
|
52
|
+
}
|
|
33
53
|
export interface RecallMemoriesInput {
|
|
34
54
|
query: string;
|
|
35
55
|
scope: MemoryScope;
|
|
@@ -44,6 +64,7 @@ export interface ListMemoriesInput {
|
|
|
44
64
|
}
|
|
45
65
|
export interface ForgetMemoryInput {
|
|
46
66
|
id: string;
|
|
67
|
+
expectedRevision?: number;
|
|
47
68
|
mode?: "archive" | "delete";
|
|
48
69
|
confirm?: boolean;
|
|
49
70
|
actor: string;
|
|
@@ -68,6 +89,7 @@ export interface ForgetMemoriesResult {
|
|
|
68
89
|
}
|
|
69
90
|
export interface UpdateMemoryInput {
|
|
70
91
|
id: string;
|
|
92
|
+
expectedRevision?: number;
|
|
71
93
|
content?: string;
|
|
72
94
|
kind?: MemoryKind;
|
|
73
95
|
scope?: MemoryScope;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nuzo/memory-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Core memory lifecycle, ports, and domain contracts for Nuzo.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agents",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"LICENSE"
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"better-sqlite3": "^12.
|
|
31
|
+
"better-sqlite3": "^12.11.1"
|
|
32
32
|
},
|
|
33
33
|
"publishConfig": {
|
|
34
34
|
"access": "public"
|