om-memory-system 5.9.1 → 5.10.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/README.md +7 -11
- package/dist/adapters/opencode/conversation.js +4 -2
- package/dist/adapters/opencode/memory-tool.d.ts +4 -0
- package/dist/adapters/opencode/memory-tool.js +53 -0
- package/dist/adapters/opencode/package-skills.d.ts +1 -1
- package/dist/adapters/opencode/package-skills.js +17 -15
- package/dist/adapters/pi/extension.js +7 -0
- package/dist/cli/memory-command.d.ts +2 -0
- package/dist/cli/memory-command.js +35 -9
- package/dist/config-paths.d.ts +5 -0
- package/dist/config-paths.js +15 -0
- package/dist/config-template.d.ts +1 -1
- package/dist/config-template.js +8 -0
- package/dist/config.d.ts +51 -40
- package/dist/config.js +8 -15
- package/dist/core/capture.d.ts +2 -1
- package/dist/core/capture.js +8 -1
- package/dist/core/host.d.ts +12 -0
- package/dist/core/memorisation-operations.d.ts +33 -0
- package/dist/core/memorisation-operations.js +160 -0
- package/dist/core/memorisation-settings.d.ts +11 -0
- package/dist/core/memorisation-settings.js +48 -0
- package/dist/core/memorisation.d.ts +43 -0
- package/dist/core/memorisation.js +76 -0
- package/dist/core/memorise-turn.d.ts +11 -0
- package/dist/core/memorise-turn.js +64 -0
- package/dist/core/memory-operations.d.ts +6 -1
- package/dist/core/memory-operations.js +29 -0
- package/dist/core/memory-tool-text.js +1 -1
- package/dist/core/retrieval.d.ts +3 -2
- package/dist/core/retrieval.js +11 -4
- package/dist/core/tool-outcome.d.ts +6 -0
- package/dist/core/tool-outcome.js +31 -0
- package/dist/importer/claude-conversation.js +8 -1
- package/dist/importer/claude-turn-units.js +1 -0
- package/dist/importer/importer.js +3 -3
- package/dist/importer/opencode-reader.js +2 -1
- package/dist/importer/pi-conversation.js +11 -5
- package/dist/index.js +2 -42
- package/dist/services/api-handlers.js +13 -35
- package/dist/services/capture-diagnostics.d.ts +3 -2
- package/dist/services/capture-diagnostics.js +5 -1
- package/dist/services/cleanup-service.js +2 -1
- package/dist/services/client-session-read.d.ts +16 -0
- package/dist/services/client-session-read.js +32 -0
- package/dist/services/client.d.ts +6 -1
- package/dist/services/client.js +20 -36
- package/dist/services/deduplication-service.js +12 -1
- package/dist/services/global-config-writer.js +14 -1
- package/dist/services/linked-search.d.ts +15 -0
- package/dist/services/linked-search.js +23 -2
- package/dist/services/live-captured-entries.js +2 -1
- package/dist/services/memorisation-routes.d.ts +16 -0
- package/dist/services/memorisation-routes.js +94 -0
- package/dist/services/memorisation-store.d.ts +45 -0
- package/dist/services/memorisation-store.js +120 -0
- package/dist/services/memory-kind.d.ts +6 -0
- package/dist/services/memory-kind.js +6 -0
- package/dist/services/memory-portability-service.js +2 -1
- package/dist/services/memory-replace.d.ts +8 -0
- package/dist/services/memory-replace.js +26 -0
- package/dist/services/retrieval-rerank.d.ts +3 -1
- package/dist/services/retrieval-rerank.js +1 -1
- package/dist/services/settings-snapshot.js +6 -0
- package/dist/services/turso/exact-scan.d.ts +2 -1
- package/dist/services/turso/exact-scan.js +4 -3
- package/dist/services/turso/keyword-search.d.ts +2 -1
- package/dist/services/turso/keyword-search.js +3 -2
- package/dist/services/turso/vector-search.d.ts +10 -9
- package/dist/services/turso/vector-search.js +38 -95
- package/dist/services/turso/vector-writes.d.ts +8 -0
- package/dist/services/turso/vector-writes.js +80 -0
- package/dist/services/web-ensure.d.ts +1 -0
- package/dist/services/web-ensure.js +5 -1
- package/dist/services/web-memory-rows.d.ts +12 -0
- package/dist/services/web-memory-rows.js +58 -0
- package/dist/services/web-route-modules.js +5 -0
- package/dist/v2/adapter.d.ts +13 -1
- package/dist/v2/adapter.js +7 -0
- package/dist/web/assets/{index-Be6Uh64d.css → index-CoYuPYCS.css} +1 -1
- package/dist/web/assets/index-DQgI670D.js +171 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/skills/omms-memory/SKILL.md +18 -0
- package/skills/omms-remember/SKILL.md +118 -0
- package/dist/web/assets/index-BX_zZiKa.js +0 -170
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { tursoConnectionManager } from "./turso/connection-manager.js";
|
|
2
|
+
import { tursoShardManager } from "./turso/shard-manager.js";
|
|
3
|
+
import { tursoVectorSearch } from "./turso/vector-search.js";
|
|
4
|
+
/**
|
|
5
|
+
* Swaps the stored row that has `record.id` for `record`, in the shard that
|
|
6
|
+
* holds it, in one transaction: a failed insert rolls the delete back, so the
|
|
7
|
+
* old row survives. Returns false when no row has that ID. The caller embeds
|
|
8
|
+
* first, so an embedding failure never reaches this point.
|
|
9
|
+
*/
|
|
10
|
+
export async function replaceMemoryRow(record) {
|
|
11
|
+
const shards = [
|
|
12
|
+
...(await tursoShardManager.getAllShards("user", "")),
|
|
13
|
+
...(await tursoShardManager.getAllShards("project", "")),
|
|
14
|
+
];
|
|
15
|
+
for (const shard of shards) {
|
|
16
|
+
const db = await tursoConnectionManager.getConnection(shard.dbPath);
|
|
17
|
+
if (!(await tursoVectorSearch.getMemoryById(db, record.id)))
|
|
18
|
+
continue;
|
|
19
|
+
await db.transaction("write", async (tx) => {
|
|
20
|
+
await tx.execute({ sql: `DELETE FROM memories WHERE id = ?`, args: [record.id] });
|
|
21
|
+
await tursoVectorSearch.insertVectorInTransaction(tx, record);
|
|
22
|
+
});
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import type { SearchResult, ShardInfo } from "./turso/types.js";
|
|
12
12
|
import { type RerankerSettings } from "./reranker.js";
|
|
13
|
+
import type { MemoryKind } from "./memory-kind.js";
|
|
13
14
|
/** The shape of tursoVectorSearch.searchAcrossShards, injectable for tests. */
|
|
14
|
-
export type SearchAcrossShards = (shards: ShardInfo[], queryVector: Float32Array, containerTag: string, limit: number, similarityThreshold: number, queryText?: string) => Promise<{
|
|
15
|
+
export type SearchAcrossShards = (shards: ShardInfo[], queryVector: Float32Array, containerTag: string, limit: number, similarityThreshold: number, queryText?: string, kind?: MemoryKind) => Promise<{
|
|
15
16
|
results: SearchResult[];
|
|
16
17
|
warnings: string[];
|
|
17
18
|
}>;
|
|
@@ -22,6 +23,7 @@ export interface RerankedSearchOptions {
|
|
|
22
23
|
queryText: string;
|
|
23
24
|
maxMemories: number;
|
|
24
25
|
similarityThreshold: number;
|
|
26
|
+
kind?: MemoryKind;
|
|
25
27
|
}
|
|
26
28
|
export interface RerankedSearchDeps {
|
|
27
29
|
search: SearchAcrossShards;
|
|
@@ -9,7 +9,7 @@ import { rerankDocuments } from "./reranker.js";
|
|
|
9
9
|
*/
|
|
10
10
|
export async function rerankedSearch(options, settings, deps) {
|
|
11
11
|
const { search } = deps;
|
|
12
|
-
const { results: candidates, warnings } = await search(options.shards, options.queryVector, options.containerTag, Math.max(options.maxMemories, RERANKER_POOL_SIZE), 0, options.queryText);
|
|
12
|
+
const { results: candidates, warnings } = await search(options.shards, options.queryVector, options.containerTag, Math.max(options.maxMemories, RERANKER_POOL_SIZE), 0, options.queryText, options.kind ?? "normal");
|
|
13
13
|
// Nothing to reorder: skip the server round trip for an empty search.
|
|
14
14
|
if (candidates.length === 0)
|
|
15
15
|
return { results: [], warnings, reranked: false };
|
|
@@ -42,6 +42,9 @@ const editable = [
|
|
|
42
42
|
"openingContextQuery",
|
|
43
43
|
"subagentMemory",
|
|
44
44
|
"compaction.enabled",
|
|
45
|
+
"memorisationRepeat",
|
|
46
|
+
"memorisationMaxSize",
|
|
47
|
+
"memorisationSizeUnit",
|
|
45
48
|
...MEMORY_LIMIT_SETTINGS,
|
|
46
49
|
];
|
|
47
50
|
/**
|
|
@@ -73,6 +76,9 @@ const globalOnly = [
|
|
|
73
76
|
"rerankerTimeoutMs",
|
|
74
77
|
"skillLearningRedact",
|
|
75
78
|
"skillFolders",
|
|
79
|
+
"memorisationRepeat",
|
|
80
|
+
"memorisationMaxSize",
|
|
81
|
+
"memorisationSizeUnit",
|
|
76
82
|
];
|
|
77
83
|
function readSettingsFile(path) {
|
|
78
84
|
if (!path)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { TursoDb } from "./turso-db.js";
|
|
2
|
+
import { type MemoryKind } from "../memory-kind.js";
|
|
2
3
|
/** Exact (non-indexed) nearest-neighbour scan, used when `vector_top_k` fails. */
|
|
3
|
-
export declare function exactScanKind(db: TursoDb, queryJson: string, k: number, containerTag: string, columnName: string): Promise<Array<{
|
|
4
|
+
export declare function exactScanKind(db: TursoDb, queryJson: string, k: number, containerTag: string, columnName: string, kind?: MemoryKind): Promise<Array<{
|
|
4
5
|
id: string;
|
|
5
6
|
similarity: number;
|
|
6
7
|
}>>;
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { distanceToSimilarity } from "./vector-utils.js";
|
|
2
|
+
import { memoryKindPredicate } from "../memory-kind.js";
|
|
2
3
|
/** Exact (non-indexed) nearest-neighbour scan, used when `vector_top_k` fails. */
|
|
3
|
-
export async function exactScanKind(db, queryJson, k, containerTag, columnName) {
|
|
4
|
+
export async function exactScanKind(db, queryJson, k, containerTag, columnName, kind = "normal") {
|
|
4
5
|
const rows = await db.all(containerTag === ""
|
|
5
6
|
? `
|
|
6
7
|
SELECT m.id AS id, vector_distance_cos(m.${columnName}, vector32(?)) AS dist
|
|
7
8
|
FROM memories m
|
|
8
|
-
WHERE m.${columnName} IS NOT NULL
|
|
9
|
+
WHERE m.${columnName} IS NOT NULL AND ${memoryKindPredicate("m.type", kind)}
|
|
9
10
|
ORDER BY dist ASC
|
|
10
11
|
LIMIT ?
|
|
11
12
|
`
|
|
12
13
|
: `
|
|
13
14
|
SELECT m.id AS id, vector_distance_cos(m.${columnName}, vector32(?)) AS dist
|
|
14
15
|
FROM memories m
|
|
15
|
-
WHERE m.${columnName} IS NOT NULL AND m.container_tag = ?
|
|
16
|
+
WHERE m.${columnName} IS NOT NULL AND m.container_tag = ? AND ${memoryKindPredicate("m.type", kind)}
|
|
16
17
|
ORDER BY dist ASC
|
|
17
18
|
LIMIT ?
|
|
18
19
|
`, containerTag === "" ? [queryJson, k] : [queryJson, containerTag, k]);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { KeywordMode } from "../../utils/keyword-boost.js";
|
|
2
2
|
import type { TursoDb } from "./turso-db.js";
|
|
3
|
+
import { type MemoryKind } from "../memory-kind.js";
|
|
3
4
|
/**
|
|
4
5
|
* FTS5 query for the words of a prompt: lower-case terms from letters and
|
|
5
6
|
* digits only ("ADR-023" gives `adr` and `023`), each in quotes, joined by OR.
|
|
@@ -27,7 +28,7 @@ export interface KeywordHits {
|
|
|
27
28
|
* continues with vector and tag results. The log holds a code and an error
|
|
28
29
|
* name only, never the prompt or memory text.
|
|
29
30
|
*/
|
|
30
|
-
export declare function searchKeywords(db: TursoDb, queryText: string | undefined, containerTag: string, limit: number): Promise<KeywordHits | null>;
|
|
31
|
+
export declare function searchKeywords(db: TursoDb, queryText: string | undefined, containerTag: string, limit: number, kind?: MemoryKind): Promise<KeywordHits | null>;
|
|
31
32
|
/**
|
|
32
33
|
* `base` with the boost for memory `id`; `base` itself when it has no keyword hit
|
|
33
34
|
* or the mode is `rrf` (rank fusion keeps each memory's own similarity).
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* arguments: do not import CONFIG here, many tests stub src/config.js.
|
|
5
5
|
*/
|
|
6
6
|
import { log } from "../logger.js";
|
|
7
|
+
import { memoryKindPredicate } from "../memory-kind.js";
|
|
7
8
|
const MAX_TERMS = 24;
|
|
8
9
|
// Words that carry no search value. With the OR query below, one of them
|
|
9
10
|
// would pull most of a shard in as candidates.
|
|
@@ -121,7 +122,7 @@ export async function ensureKeywordIndex(db) {
|
|
|
121
122
|
* continues with vector and tag results. The log holds a code and an error
|
|
122
123
|
* name only, never the prompt or memory text.
|
|
123
124
|
*/
|
|
124
|
-
export async function searchKeywords(db, queryText, containerTag, limit) {
|
|
125
|
+
export async function searchKeywords(db, queryText, containerTag, limit, kind = "normal") {
|
|
125
126
|
const match = buildKeywordQuery(queryText);
|
|
126
127
|
if (!match)
|
|
127
128
|
return null;
|
|
@@ -129,7 +130,7 @@ export async function searchKeywords(db, queryText, containerTag, limit) {
|
|
|
129
130
|
await ensureKeywordIndex(db);
|
|
130
131
|
const rows = await db.all(`SELECT m.id AS id, -bm25(memories_fts) AS score
|
|
131
132
|
FROM memories_fts JOIN memories m ON m.rowid = memories_fts.rowid
|
|
132
|
-
WHERE memories_fts MATCH
|
|
133
|
+
WHERE memories_fts MATCH ? AND ${memoryKindPredicate("m.type", kind)}${containerTag === "" ? "" : " AND m.container_tag = ?"}
|
|
133
134
|
ORDER BY score DESC LIMIT ?`, containerTag === "" ? [match, limit] : [match, containerTag, limit]);
|
|
134
135
|
const scores = new Map(rows.map((row) => [String(row.id), Number(row.score)]));
|
|
135
136
|
return { scores, best: Math.max(0, ...scores.values()) };
|
|
@@ -1,29 +1,30 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SearchResult, ShardInfo } from "./types.js";
|
|
2
2
|
import type { KeywordMode } from "../../utils/keyword-boost.js";
|
|
3
3
|
import type { TursoDb } from "./turso-db.js";
|
|
4
|
-
import type
|
|
4
|
+
import { type MemoryKind } from "../memory-kind.js";
|
|
5
5
|
export declare class TursoVectorSearch {
|
|
6
|
-
insertVectorInTransaction(tx: Transaction, record: MemoryRecord)
|
|
7
|
-
insertVector(db: TursoDb, record: MemoryRecord)
|
|
6
|
+
insertVectorInTransaction: (tx: import("@libsql/client").Transaction, record: import("./types.js").MemoryRecord) => Promise<void>;
|
|
7
|
+
insertVector: (db: TursoDb, record: import("./types.js").MemoryRecord) => Promise<void>;
|
|
8
8
|
searchInShard(shard: ShardInfo, queryVector: Float32Array, containerTag: string, limit: number, queryText?: string, keywordBoost?: number, keywordMode?: KeywordMode,
|
|
9
9
|
/** Opens the shard database; linked-space searches pass a read-only opener. */
|
|
10
|
-
openDb?: (dbPath: string) => Promise<TursoDb
|
|
10
|
+
openDb?: (dbPath: string) => Promise<TursoDb>, kind?: MemoryKind): Promise<SearchResult[]>;
|
|
11
11
|
private searchKind;
|
|
12
12
|
searchAcrossShards(shards: ShardInfo[], queryVector: Float32Array, containerTag: string, limit: number, similarityThreshold: number, queryText?: string, keywordBoost?: number, keywordMode?: KeywordMode,
|
|
13
13
|
/** Opens each shard database; linked-space searches pass a read-only opener. */
|
|
14
|
-
openDb?: (dbPath: string) => Promise<TursoDb
|
|
14
|
+
openDb?: (dbPath: string) => Promise<TursoDb>, kind?: MemoryKind): Promise<{
|
|
15
15
|
results: SearchResult[];
|
|
16
16
|
warnings: string[];
|
|
17
17
|
}>;
|
|
18
18
|
deleteVector(db: TursoDb, memoryId: string): Promise<void>;
|
|
19
19
|
updateVector(db: TursoDb, memoryId: string, vector: Float32Array, tagsVector?: Float32Array, tags?: string): Promise<void>;
|
|
20
|
-
listMemories(db: TursoDb, containerTag: string, limit: number): Promise<Record<string, unknown>[]>;
|
|
21
|
-
|
|
20
|
+
listMemories(db: TursoDb, containerTag: string, limit: number, kind?: MemoryKind): Promise<Record<string, unknown>[]>;
|
|
21
|
+
/** Every row of one kind. Pass "all" only for kind-neutral readers such as export. */
|
|
22
|
+
getAllMemories(db: TursoDb, kind?: MemoryKind | "all"): Promise<Record<string, unknown>[]>;
|
|
22
23
|
getAllMemoriesWithExtractedVectors(db: TursoDb): Promise<Array<Record<string, unknown> & {
|
|
23
24
|
vector_json: string | null;
|
|
24
25
|
}>>;
|
|
25
26
|
getMemoryById(db: TursoDb, memoryId: string): Promise<Record<string, unknown> | null>;
|
|
26
|
-
getMemoriesBySessionID(db: TursoDb, sessionID: string): Promise<Record<string, unknown>[]>;
|
|
27
|
+
getMemoriesBySessionID(db: TursoDb, sessionID: string, kind?: MemoryKind): Promise<Record<string, unknown>[]>;
|
|
27
28
|
countVectors(db: TursoDb, containerTag: string): Promise<number>;
|
|
28
29
|
countAllVectors(db: TursoDb): Promise<number>;
|
|
29
30
|
getDistinctTags(db: TursoDb): Promise<Record<string, unknown>[]>;
|
|
@@ -5,6 +5,8 @@ import { tagMatchBoost } from "./tag-match.js";
|
|
|
5
5
|
import { exactScanKind } from "./exact-scan.js";
|
|
6
6
|
import { boostedSimilarity, searchKeywords } from "./keyword-search.js";
|
|
7
7
|
import { fuseShardResults } from "./rank-fusion.js";
|
|
8
|
+
import { tursoVectorWrites } from "./vector-writes.js";
|
|
9
|
+
import { memoryKindPredicate } from "../memory-kind.js";
|
|
8
10
|
function parseMetadata(value) {
|
|
9
11
|
if (!value || typeof value !== "string")
|
|
10
12
|
return undefined;
|
|
@@ -16,99 +18,25 @@ function parseMetadata(value) {
|
|
|
16
18
|
}
|
|
17
19
|
}
|
|
18
20
|
export class TursoVectorSearch {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const commonArgs = [
|
|
22
|
-
record.containerTag,
|
|
23
|
-
record.tags || null,
|
|
24
|
-
record.type || null,
|
|
25
|
-
record.createdAt,
|
|
26
|
-
record.updatedAt,
|
|
27
|
-
record.metadata || null,
|
|
28
|
-
record.displayName || null,
|
|
29
|
-
record.userName || null,
|
|
30
|
-
record.userEmail || null,
|
|
31
|
-
record.projectPath || null,
|
|
32
|
-
record.projectName || null,
|
|
33
|
-
record.gitRepoUrl || null,
|
|
34
|
-
];
|
|
35
|
-
if (record.tagsVector) {
|
|
36
|
-
await tx.execute({
|
|
37
|
-
sql: `
|
|
38
|
-
INSERT INTO memories (
|
|
39
|
-
id, content, vector, tags_vector, container_tag, tags, type, created_at, updated_at,
|
|
40
|
-
metadata, display_name, user_name, user_email, project_path, project_name, git_repo_url
|
|
41
|
-
) VALUES (?, ?, vector32(?), vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
42
|
-
`,
|
|
43
|
-
args: [
|
|
44
|
-
record.id,
|
|
45
|
-
record.content,
|
|
46
|
-
contentVector,
|
|
47
|
-
vectorToJson(record.tagsVector),
|
|
48
|
-
...commonArgs,
|
|
49
|
-
],
|
|
50
|
-
});
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
await tx.execute({
|
|
54
|
-
sql: `
|
|
55
|
-
INSERT INTO memories (
|
|
56
|
-
id, content, vector, tags_vector, container_tag, tags, type, created_at, updated_at,
|
|
57
|
-
metadata, display_name, user_name, user_email, project_path, project_name, git_repo_url
|
|
58
|
-
) VALUES (?, ?, vector32(?), NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
59
|
-
`,
|
|
60
|
-
args: [record.id, record.content, contentVector, ...commonArgs],
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
async insertVector(db, record) {
|
|
64
|
-
const contentVector = vectorToJson(record.vector);
|
|
65
|
-
const commonArgs = [
|
|
66
|
-
record.containerTag,
|
|
67
|
-
record.tags || null,
|
|
68
|
-
record.type || null,
|
|
69
|
-
record.createdAt,
|
|
70
|
-
record.updatedAt,
|
|
71
|
-
record.metadata || null,
|
|
72
|
-
record.displayName || null,
|
|
73
|
-
record.userName || null,
|
|
74
|
-
record.userEmail || null,
|
|
75
|
-
record.projectPath || null,
|
|
76
|
-
record.projectName || null,
|
|
77
|
-
record.gitRepoUrl || null,
|
|
78
|
-
];
|
|
79
|
-
if (record.tagsVector) {
|
|
80
|
-
await db.execute(`
|
|
81
|
-
INSERT INTO memories (
|
|
82
|
-
id, content, vector, tags_vector, container_tag, tags, type, created_at, updated_at,
|
|
83
|
-
metadata, display_name, user_name, user_email, project_path, project_name, git_repo_url
|
|
84
|
-
) VALUES (?, ?, vector32(?), vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
85
|
-
`, [record.id, record.content, contentVector, vectorToJson(record.tagsVector), ...commonArgs]);
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
await db.execute(`
|
|
89
|
-
INSERT INTO memories (
|
|
90
|
-
id, content, vector, tags_vector, container_tag, tags, type, created_at, updated_at,
|
|
91
|
-
metadata, display_name, user_name, user_email, project_path, project_name, git_repo_url
|
|
92
|
-
) VALUES (?, ?, vector32(?), NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
93
|
-
`, [record.id, record.content, contentVector, ...commonArgs]);
|
|
94
|
-
}
|
|
21
|
+
insertVectorInTransaction = tursoVectorWrites.insertVectorInTransaction.bind(tursoVectorWrites);
|
|
22
|
+
insertVector = tursoVectorWrites.insertVector.bind(tursoVectorWrites);
|
|
95
23
|
async searchInShard(shard, queryVector, containerTag, limit, queryText, keywordBoost = 0, keywordMode = "boost",
|
|
96
24
|
/** Opens the shard database; linked-space searches pass a read-only opener. */
|
|
97
|
-
openDb = (dbPath) => tursoConnectionManager.getConnection(dbPath)) {
|
|
25
|
+
openDb = (dbPath) => tursoConnectionManager.getConnection(dbPath), kind = "normal") {
|
|
98
26
|
const db = await openDb(shard.dbPath);
|
|
99
27
|
const queryJson = vectorToJson(queryVector);
|
|
100
28
|
// Over-fetch aggressively when filtering by container_tag after ANN,
|
|
101
29
|
// because post-filtering can discard many DiskANN neighbors.
|
|
102
30
|
const k = containerTag === "" ? Math.max(limit * 4, 32) : Math.max(limit * 16, 128);
|
|
103
|
-
const contentResults = await this.searchKind(db, queryJson, k, containerTag, "memories_vec_idx", "vector");
|
|
104
|
-
const tagsResults = await this.searchKind(db, queryJson, k, containerTag, "memories_tags_vec_idx", "tags_vector");
|
|
31
|
+
const contentResults = await this.searchKind(db, queryJson, k, containerTag, "memories_vec_idx", "vector", kind, limit);
|
|
32
|
+
const tagsResults = await this.searchKind(db, queryJson, k, containerTag, "memories_tags_vec_idx", "tags_vector", kind, limit);
|
|
105
33
|
const candidateIds = new Set();
|
|
106
34
|
for (const result of contentResults)
|
|
107
35
|
candidateIds.add(result.id);
|
|
108
36
|
for (const result of tagsResults)
|
|
109
37
|
candidateIds.add(result.id);
|
|
110
38
|
const kwHits = keywordBoost > 0
|
|
111
|
-
? await searchKeywords(db, queryText, containerTag, Math.max(limit * 4, 32))
|
|
39
|
+
? await searchKeywords(db, queryText, containerTag, Math.max(limit * 4, 32), kind)
|
|
112
40
|
: null;
|
|
113
41
|
for (const id of kwHits?.scores.keys() ?? [])
|
|
114
42
|
candidateIds.add(id);
|
|
@@ -128,7 +56,7 @@ export class TursoVectorSearch {
|
|
|
128
56
|
THEN vector_distance_cos(tags_vector, vector32(?))
|
|
129
57
|
ELSE NULL END AS tags_dist
|
|
130
58
|
FROM memories
|
|
131
|
-
WHERE id IN (${placeholders})
|
|
59
|
+
WHERE id IN (${placeholders}) AND ${memoryKindPredicate("type", kind)}
|
|
132
60
|
`
|
|
133
61
|
: `
|
|
134
62
|
SELECT id, content, tags, type, created_at, metadata, container_tag,
|
|
@@ -139,7 +67,7 @@ export class TursoVectorSearch {
|
|
|
139
67
|
THEN vector_distance_cos(tags_vector, vector32(?))
|
|
140
68
|
ELSE NULL END AS tags_dist
|
|
141
69
|
FROM memories
|
|
142
|
-
WHERE id IN (${placeholders}) AND container_tag = ?
|
|
70
|
+
WHERE id IN (${placeholders}) AND container_tag = ? AND ${memoryKindPredicate("type", kind)}
|
|
143
71
|
`, containerTag === ""
|
|
144
72
|
? [queryJson, queryJson, ...ids]
|
|
145
73
|
: [queryJson, queryJson, ...ids, containerTag]);
|
|
@@ -151,7 +79,9 @@ export class TursoVectorSearch {
|
|
|
151
79
|
const memoryTagsStr = String(row.tags || "");
|
|
152
80
|
const exactMatchBoost = tagMatchBoost(queryText, memoryTagsStr);
|
|
153
81
|
const finalTagsSim = Math.max(tagsSim, exactMatchBoost);
|
|
154
|
-
|
|
82
|
+
// Every memorisation carries the same tag, so the tags half is near constant and
|
|
83
|
+
// pulls a close match down. Score memorisations by content alone.
|
|
84
|
+
const base = kind === "memorisation" ? contentSim : contentSim * 0.6 + finalTagsSim * 0.4;
|
|
155
85
|
const similarity = boostedSimilarity(base, kwHits, String(row.id), keywordBoost, keywordMode);
|
|
156
86
|
return {
|
|
157
87
|
id: String(row.id),
|
|
@@ -177,21 +107,32 @@ export class TursoVectorSearch {
|
|
|
177
107
|
const fusing = keywordMode === "rrf" && keywordBoost > 0;
|
|
178
108
|
return fusing ? hydratedResults : hydratedResults.slice(0, Math.max(0, limit));
|
|
179
109
|
}
|
|
180
|
-
async searchKind(db, queryJson, k, containerTag, indexName, columnName) {
|
|
110
|
+
async searchKind(db, queryJson, k, containerTag, indexName, columnName, kind = "normal", wanted = 0) {
|
|
111
|
+
// The ANN index covers every row, and the kind filter runs after it. Normal rows
|
|
112
|
+
// can fill all k candidates and hide the few memorisations. Memorisations are few,
|
|
113
|
+
// so scan them exactly; the exact scan filters by kind in SQL.
|
|
114
|
+
if (kind === "memorisation") {
|
|
115
|
+
return exactScanKind(db, queryJson, k, containerTag, columnName, kind);
|
|
116
|
+
}
|
|
181
117
|
try {
|
|
182
118
|
const rows = await db.all(containerTag === ""
|
|
183
119
|
? `
|
|
184
120
|
SELECT m.id AS id, vector_distance_cos(m.${columnName}, vector32(?)) AS dist
|
|
185
121
|
FROM vector_top_k('${indexName}', vector32(?), ?) AS v
|
|
186
122
|
CROSS JOIN memories m ON m.rowid = v.id
|
|
187
|
-
WHERE m.${columnName} IS NOT NULL
|
|
123
|
+
WHERE m.${columnName} IS NOT NULL AND ${memoryKindPredicate("m.type", kind)}
|
|
188
124
|
`
|
|
189
125
|
: `
|
|
190
126
|
SELECT m.id AS id, vector_distance_cos(m.${columnName}, vector32(?)) AS dist
|
|
191
127
|
FROM vector_top_k('${indexName}', vector32(?), ?) AS v
|
|
192
128
|
CROSS JOIN memories m ON m.rowid = v.id
|
|
193
|
-
WHERE m.${columnName} IS NOT NULL AND m.container_tag = ?
|
|
129
|
+
WHERE m.${columnName} IS NOT NULL AND m.container_tag = ? AND ${memoryKindPredicate("m.type", kind)}
|
|
194
130
|
`, containerTag === "" ? [queryJson, queryJson, k] : [queryJson, queryJson, k, containerTag]);
|
|
131
|
+
// Memorisations or other projects can fill the k candidates and the filters then
|
|
132
|
+
// drop them all. A short result is rare, so only then pay for the exact scan.
|
|
133
|
+
if (rows.length < wanted) {
|
|
134
|
+
return exactScanKind(db, queryJson, k, containerTag, columnName, kind);
|
|
135
|
+
}
|
|
195
136
|
return rows.map((row) => ({
|
|
196
137
|
id: String(row.id),
|
|
197
138
|
similarity: distanceToSimilarity(Number(row.dist)),
|
|
@@ -202,16 +143,16 @@ export class TursoVectorSearch {
|
|
|
202
143
|
indexName,
|
|
203
144
|
error: String(error),
|
|
204
145
|
}, "warn");
|
|
205
|
-
return exactScanKind(db, queryJson, k, containerTag, columnName);
|
|
146
|
+
return exactScanKind(db, queryJson, k, containerTag, columnName, kind);
|
|
206
147
|
}
|
|
207
148
|
}
|
|
208
149
|
async searchAcrossShards(shards, queryVector, containerTag, limit, similarityThreshold, queryText, keywordBoost = 0, keywordMode = "boost",
|
|
209
150
|
/** Opens each shard database; linked-space searches pass a read-only opener. */
|
|
210
|
-
openDb) {
|
|
151
|
+
openDb, kind = "normal") {
|
|
211
152
|
const shardErrors = [];
|
|
212
153
|
const shardPromises = shards.map(async (shard) => {
|
|
213
154
|
try {
|
|
214
|
-
return await this.searchInShard(shard, queryVector, containerTag, limit, queryText, keywordBoost, keywordMode, openDb);
|
|
155
|
+
return await this.searchInShard(shard, queryVector, containerTag, limit, queryText, keywordBoost, keywordMode, openDb, kind);
|
|
215
156
|
}
|
|
216
157
|
catch (error) {
|
|
217
158
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -264,22 +205,24 @@ export class TursoVectorSearch {
|
|
|
264
205
|
await db.execute(`UPDATE memories SET vector = vector32(?), tags_vector = NULL WHERE id = ?`, [contentVector, memoryId]);
|
|
265
206
|
}
|
|
266
207
|
}
|
|
267
|
-
async listMemories(db, containerTag, limit) {
|
|
208
|
+
async listMemories(db, containerTag, limit, kind = "normal") {
|
|
268
209
|
return containerTag === ""
|
|
269
210
|
? db.all(`
|
|
270
211
|
SELECT * FROM memories
|
|
212
|
+
WHERE ${memoryKindPredicate("type", kind)}
|
|
271
213
|
ORDER BY created_at DESC
|
|
272
214
|
LIMIT ?
|
|
273
215
|
`, [limit])
|
|
274
216
|
: db.all(`
|
|
275
217
|
SELECT * FROM memories
|
|
276
|
-
WHERE container_tag = ?
|
|
218
|
+
WHERE container_tag = ? AND ${memoryKindPredicate("type", kind)}
|
|
277
219
|
ORDER BY created_at DESC
|
|
278
220
|
LIMIT ?
|
|
279
221
|
`, [containerTag, limit]);
|
|
280
222
|
}
|
|
281
|
-
|
|
282
|
-
|
|
223
|
+
/** Every row of one kind. Pass "all" only for kind-neutral readers such as export. */
|
|
224
|
+
async getAllMemories(db, kind = "normal") {
|
|
225
|
+
return db.all(`SELECT * FROM memories WHERE ${kind === "all" ? "1 = 1" : memoryKindPredicate("type", kind)} ORDER BY created_at DESC`);
|
|
283
226
|
}
|
|
284
227
|
async getAllMemoriesWithExtractedVectors(db) {
|
|
285
228
|
return db.all(`
|
|
@@ -296,10 +239,10 @@ export class TursoVectorSearch {
|
|
|
296
239
|
async getMemoryById(db, memoryId) {
|
|
297
240
|
return db.get(`SELECT * FROM memories WHERE id = ?`, [memoryId]);
|
|
298
241
|
}
|
|
299
|
-
async getMemoriesBySessionID(db, sessionID) {
|
|
242
|
+
async getMemoriesBySessionID(db, sessionID, kind = "normal") {
|
|
300
243
|
const rows = await db.all(`
|
|
301
244
|
SELECT * FROM memories
|
|
302
|
-
WHERE metadata LIKE ?
|
|
245
|
+
WHERE metadata LIKE ? AND ${memoryKindPredicate("type", kind)}
|
|
303
246
|
ORDER BY created_at DESC
|
|
304
247
|
`, [`%"sessionID":"${sessionID}"%`]);
|
|
305
248
|
return rows.map((row) => ({
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { MemoryRecord } from "./types.js";
|
|
2
|
+
import type { TursoDb } from "./turso-db.js";
|
|
3
|
+
import type { Transaction } from "@libsql/client";
|
|
4
|
+
export declare class TursoVectorWrites {
|
|
5
|
+
insertVectorInTransaction(tx: Transaction, record: MemoryRecord): Promise<void>;
|
|
6
|
+
insertVector(db: TursoDb, record: MemoryRecord): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
export declare const tursoVectorWrites: TursoVectorWrites;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { vectorToJson } from "./vector-utils.js";
|
|
2
|
+
export class TursoVectorWrites {
|
|
3
|
+
async insertVectorInTransaction(tx, record) {
|
|
4
|
+
const contentVector = vectorToJson(record.vector);
|
|
5
|
+
const commonArgs = [
|
|
6
|
+
record.containerTag,
|
|
7
|
+
record.tags || null,
|
|
8
|
+
record.type || null,
|
|
9
|
+
record.createdAt,
|
|
10
|
+
record.updatedAt,
|
|
11
|
+
record.metadata || null,
|
|
12
|
+
record.displayName || null,
|
|
13
|
+
record.userName || null,
|
|
14
|
+
record.userEmail || null,
|
|
15
|
+
record.projectPath || null,
|
|
16
|
+
record.projectName || null,
|
|
17
|
+
record.gitRepoUrl || null,
|
|
18
|
+
];
|
|
19
|
+
if (record.tagsVector) {
|
|
20
|
+
await tx.execute({
|
|
21
|
+
sql: `
|
|
22
|
+
INSERT INTO memories (
|
|
23
|
+
id, content, vector, tags_vector, container_tag, tags, type, created_at, updated_at,
|
|
24
|
+
metadata, display_name, user_name, user_email, project_path, project_name, git_repo_url
|
|
25
|
+
) VALUES (?, ?, vector32(?), vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
26
|
+
`,
|
|
27
|
+
args: [
|
|
28
|
+
record.id,
|
|
29
|
+
record.content,
|
|
30
|
+
contentVector,
|
|
31
|
+
vectorToJson(record.tagsVector),
|
|
32
|
+
...commonArgs,
|
|
33
|
+
],
|
|
34
|
+
});
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
await tx.execute({
|
|
38
|
+
sql: `
|
|
39
|
+
INSERT INTO memories (
|
|
40
|
+
id, content, vector, tags_vector, container_tag, tags, type, created_at, updated_at,
|
|
41
|
+
metadata, display_name, user_name, user_email, project_path, project_name, git_repo_url
|
|
42
|
+
) VALUES (?, ?, vector32(?), NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
43
|
+
`,
|
|
44
|
+
args: [record.id, record.content, contentVector, ...commonArgs],
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async insertVector(db, record) {
|
|
48
|
+
const contentVector = vectorToJson(record.vector);
|
|
49
|
+
const commonArgs = [
|
|
50
|
+
record.containerTag,
|
|
51
|
+
record.tags || null,
|
|
52
|
+
record.type || null,
|
|
53
|
+
record.createdAt,
|
|
54
|
+
record.updatedAt,
|
|
55
|
+
record.metadata || null,
|
|
56
|
+
record.displayName || null,
|
|
57
|
+
record.userName || null,
|
|
58
|
+
record.userEmail || null,
|
|
59
|
+
record.projectPath || null,
|
|
60
|
+
record.projectName || null,
|
|
61
|
+
record.gitRepoUrl || null,
|
|
62
|
+
];
|
|
63
|
+
if (record.tagsVector) {
|
|
64
|
+
await db.execute(`
|
|
65
|
+
INSERT INTO memories (
|
|
66
|
+
id, content, vector, tags_vector, container_tag, tags, type, created_at, updated_at,
|
|
67
|
+
metadata, display_name, user_name, user_email, project_path, project_name, git_repo_url
|
|
68
|
+
) VALUES (?, ?, vector32(?), vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
69
|
+
`, [record.id, record.content, contentVector, vectorToJson(record.tagsVector), ...commonArgs]);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
await db.execute(`
|
|
73
|
+
INSERT INTO memories (
|
|
74
|
+
id, content, vector, tags_vector, container_tag, tags, type, created_at, updated_at,
|
|
75
|
+
metadata, display_name, user_name, user_email, project_path, project_name, git_repo_url
|
|
76
|
+
) VALUES (?, ?, vector32(?), NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
77
|
+
`, [record.id, record.content, contentVector, ...commonArgs]);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export const tursoVectorWrites = new TursoVectorWrites();
|
|
@@ -79,6 +79,7 @@ export declare function replaceRetrying(path: string, text: string, deps?: {
|
|
|
79
79
|
mkdir?: (dir: string) => void;
|
|
80
80
|
write?: (path: string, text: string) => void;
|
|
81
81
|
rename?: (from: string, to: string) => void;
|
|
82
|
+
remove?: (path: string) => void;
|
|
82
83
|
rewrites?: number;
|
|
83
84
|
}, rewrites?: number): void;
|
|
84
85
|
/** `renameSync` that retries while Windows reports the target busy. */
|
|
@@ -95,9 +95,12 @@ export function replaceRetrying(path, text, deps = {}, rewrites = deps.rewrites
|
|
|
95
95
|
const mkdir = deps.mkdir ?? ((dir) => mkdirSync(dir, { recursive: true, mode: 0o700 }));
|
|
96
96
|
const write = deps.write ?? ((file, body) => writeFileSync(file, body, { mode: 0o600 }));
|
|
97
97
|
const rename = deps.rename ?? ((from, to) => renameRetrying(from, to));
|
|
98
|
+
const remove = deps.remove ?? ((file) => rmSync(file, { force: true }));
|
|
98
99
|
mkdir(dirname(path));
|
|
99
|
-
const temp = `${path}.${process.pid}.tmp`;
|
|
100
100
|
for (let attempt = 0;; attempt++) {
|
|
101
|
+
// A new name for each attempt: the Bun rename fault can stick to one
|
|
102
|
+
// temp path, so a retry with the same name fails again (TDR-030).
|
|
103
|
+
const temp = `${path}.${process.pid}.${attempt}.tmp`;
|
|
101
104
|
write(temp, text);
|
|
102
105
|
try {
|
|
103
106
|
rename(temp, path);
|
|
@@ -106,6 +109,7 @@ export function replaceRetrying(path, text, deps = {}, rewrites = deps.rewrites
|
|
|
106
109
|
catch (error) {
|
|
107
110
|
if (error.code !== "ENOENT" || attempt >= rewrites)
|
|
108
111
|
throw error;
|
|
112
|
+
remove(temp);
|
|
109
113
|
}
|
|
110
114
|
}
|
|
111
115
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Why the Memories page cannot edit a memorisation. */
|
|
2
|
+
export declare const MEMORISATION_EDIT_REFUSAL = "A memorisation cannot be edited or bulk deleted from the Memories page. Delete it on the Memorisation page, or replace it by memorising again.";
|
|
3
|
+
/** Why the Memories page cannot create a memorisation. */
|
|
4
|
+
export declare const MEMORISATION_TYPE_REFUSAL = "The memorisation type is reserved. Save a memorisation with the memorise operation, not from the Memories page.";
|
|
5
|
+
/** Reads stored normal memory rows for one project tag, or for every project and user shard. */
|
|
6
|
+
export declare function loadMemoryRows(tag?: string): Promise<any[]>;
|
|
7
|
+
/** The IDs among `ids` whose stored row is a memorisation, across every project and user shard. */
|
|
8
|
+
export declare function memorisationIdsAmong(ids: string[]): Promise<string[]>;
|
|
9
|
+
/** The refusal for a web edit of memory `id` with `data`, or undefined when the edit is allowed. */
|
|
10
|
+
export declare function webUpdateRefusal(id: string, data: {
|
|
11
|
+
type?: string;
|
|
12
|
+
}): Promise<string | undefined>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { tursoShardManager } from "./turso/shard-manager.js";
|
|
2
|
+
import { tursoVectorSearch } from "./turso/vector-search.js";
|
|
3
|
+
import { tursoConnectionManager } from "./turso/connection-manager.js";
|
|
4
|
+
import { ensureTursoReady } from "./turso/ready.js";
|
|
5
|
+
import { extractScopeFromContainerTag } from "./memory-scope.js";
|
|
6
|
+
/** Why the Memories page cannot edit a memorisation. */
|
|
7
|
+
export const MEMORISATION_EDIT_REFUSAL = "A memorisation cannot be edited or bulk deleted from the Memories page. Delete it on the Memorisation page, or replace it by memorising again.";
|
|
8
|
+
/** Why the Memories page cannot create a memorisation. */
|
|
9
|
+
export const MEMORISATION_TYPE_REFUSAL = "The memorisation type is reserved. Save a memorisation with the memorise operation, not from the Memories page.";
|
|
10
|
+
/** Reads stored normal memory rows for one project tag, or for every project and user shard. */
|
|
11
|
+
export async function loadMemoryRows(tag) {
|
|
12
|
+
const allMemories = [];
|
|
13
|
+
if (tag) {
|
|
14
|
+
const { scope: tagScope, hash } = extractScopeFromContainerTag(tag);
|
|
15
|
+
const shards = await tursoShardManager.getAllShards(tagScope, hash);
|
|
16
|
+
for (const shard of shards) {
|
|
17
|
+
const db = await tursoConnectionManager.getConnection(shard.dbPath);
|
|
18
|
+
const memories = await tursoVectorSearch.listMemories(db, tag, 10000);
|
|
19
|
+
allMemories.push(...memories);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
// Walk both project and user shards, so user-scope memories are listed too.
|
|
24
|
+
// The container_tag check stays as defence in depth. Memorisations are not
|
|
25
|
+
// listed here: getAllMemories returns normal memories unless told otherwise.
|
|
26
|
+
const projectShards = await tursoShardManager.getAllShards("project", "");
|
|
27
|
+
const userShards = await tursoShardManager.getAllShards("user", "");
|
|
28
|
+
for (const shard of [...projectShards, ...userShards]) {
|
|
29
|
+
const db = await tursoConnectionManager.getConnection(shard.dbPath);
|
|
30
|
+
const memories = await tursoVectorSearch.getAllMemories(db);
|
|
31
|
+
allMemories.push(...memories.filter((m) => m.container_tag?.includes("_project_") || m.container_tag?.includes("_user_")));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return allMemories;
|
|
35
|
+
}
|
|
36
|
+
/** The IDs among `ids` whose stored row is a memorisation, across every project and user shard. */
|
|
37
|
+
export async function memorisationIdsAmong(ids) {
|
|
38
|
+
if (ids.length === 0)
|
|
39
|
+
return [];
|
|
40
|
+
await ensureTursoReady();
|
|
41
|
+
const shards = [
|
|
42
|
+
...(await tursoShardManager.getAllShards("project", "")),
|
|
43
|
+
...(await tursoShardManager.getAllShards("user", "")),
|
|
44
|
+
];
|
|
45
|
+
const found = [];
|
|
46
|
+
for (const shard of shards) {
|
|
47
|
+
const db = await tursoConnectionManager.getConnection(shard.dbPath);
|
|
48
|
+
const rows = await db.all(`SELECT id FROM memories WHERE type = 'memorisation' AND id IN (${ids.map(() => "?").join(",")})`, ids);
|
|
49
|
+
found.push(...rows.map((row) => String(row.id)));
|
|
50
|
+
}
|
|
51
|
+
return found;
|
|
52
|
+
}
|
|
53
|
+
/** The refusal for a web edit of memory `id` with `data`, or undefined when the edit is allowed. */
|
|
54
|
+
export async function webUpdateRefusal(id, data) {
|
|
55
|
+
if (data.type === "memorisation")
|
|
56
|
+
return MEMORISATION_TYPE_REFUSAL;
|
|
57
|
+
return (await memorisationIdsAmong([id])).length > 0 ? MEMORISATION_EDIT_REFUSAL : undefined;
|
|
58
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { routeChat } from "./chat/chat-routes.js";
|
|
2
|
+
import { linkedSettingsFrom } from "./linked-search.js";
|
|
3
|
+
import { routeMemorisations } from "./memorisation-routes.js";
|
|
2
4
|
import { routeMemoryHosts } from "./memory-host-routes.js";
|
|
3
5
|
import { routeProfileCleanup } from "./profile-cleanup-routes.js";
|
|
4
6
|
import { routeSpaceTransfers } from "./space-transfer-routes.js";
|
|
@@ -31,5 +33,8 @@ export async function routeModules(path, method, url, req, ctx) {
|
|
|
31
33
|
});
|
|
32
34
|
if (spaces !== undefined)
|
|
33
35
|
return spaces;
|
|
36
|
+
const memorisations = await routeMemorisations(path, method, url, ctx.cwd, { defaultScope: config.memory.defaultScope, linked: linkedSettingsFrom(config) }, readBody);
|
|
37
|
+
if (memorisations !== undefined)
|
|
38
|
+
return memorisations;
|
|
34
39
|
return routeChat(path, method, readBody, config);
|
|
35
40
|
}
|