om-memory-system 4.12.0-next.100 → 4.12.0-next.102
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/services/api-handlers.d.ts +5 -0
- package/dist/services/api-handlers.js +45 -27
- package/dist/services/keyword-counts.d.ts +10 -0
- package/dist/services/keyword-counts.js +21 -0
- package/dist/services/web-server.js +5 -1
- package/dist/web/assets/index-BH1QAU9V.js +125 -0
- package/dist/web/assets/index-xoKkyBBV.css +2 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-DGiJlMni.js +0 -125
- package/dist/web/assets/index-D_XfdWW4.css +0 -2
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type KeywordCount } from "./keyword-counts.js";
|
|
1
2
|
import type { MemoryType } from "../types/index.js";
|
|
2
3
|
import type { UserProfileData } from "./user-profile/types.js";
|
|
3
4
|
interface ApiResponse<T = any> {
|
|
@@ -41,6 +42,10 @@ interface PaginatedResponse<T> {
|
|
|
41
42
|
export declare function handleListTags(): Promise<ApiResponse<{
|
|
42
43
|
project: TagInfo[];
|
|
43
44
|
}>>;
|
|
45
|
+
/** Labels (comma-separated memory tags) with how many memories carry each one. */
|
|
46
|
+
export declare function handleListKeywords(tag?: string): Promise<ApiResponse<{
|
|
47
|
+
keywords: KeywordCount[];
|
|
48
|
+
}>>;
|
|
44
49
|
export declare function handleListMemories(tag?: string, page?: number, pageSize?: number, includePrompts?: boolean, keyword?: string): Promise<ApiResponse<PaginatedResponse<Memory | any>>>;
|
|
45
50
|
export declare function handleAddMemory(data: {
|
|
46
51
|
content: string;
|
|
@@ -6,6 +6,7 @@ import { ensureTursoReady } from "./turso/ready.js";
|
|
|
6
6
|
import { formatTagsForEmbedding } from "./turso/vector-utils.js";
|
|
7
7
|
import { extractScopeFromContainerTag, tryExtractScopeFromContainerTag } from "./memory-scope.js";
|
|
8
8
|
import { log } from "./logger.js";
|
|
9
|
+
import { countKeywords } from "./keyword-counts.js";
|
|
9
10
|
import { CONFIG } from "../config.js";
|
|
10
11
|
import { userPromptManager } from "./user-prompt/user-prompt-manager.js";
|
|
11
12
|
import { sortProfileItems, stripProfileVectors } from "../utils/profile.js";
|
|
@@ -99,38 +100,55 @@ export async function handleListTags() {
|
|
|
99
100
|
return { success: false, error: String(error) };
|
|
100
101
|
}
|
|
101
102
|
}
|
|
103
|
+
/** Reads stored memory rows for one project tag, or for every project and user shard. */
|
|
104
|
+
async function loadMemoryRows(tag) {
|
|
105
|
+
const allMemories = [];
|
|
106
|
+
if (tag) {
|
|
107
|
+
const { scope: tagScope, hash } = extractScopeFromTag(tag);
|
|
108
|
+
const shards = await tursoShardManager.getAllShards(tagScope, hash);
|
|
109
|
+
for (const shard of shards) {
|
|
110
|
+
const db = await tursoConnectionManager.getConnection(shard.dbPath);
|
|
111
|
+
const memories = await tursoVectorSearch.listMemories(db, tag, 10000);
|
|
112
|
+
allMemories.push(...memories);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
// Iterate both project- and user-scoped shards. Previously this only
|
|
117
|
+
// walked project shards, which silently hid user-scope memories from the
|
|
118
|
+
// listing endpoint (Web UI navigation, /api/memories without a tag
|
|
119
|
+
// filter, …). User-scope memories still showed up in /api/search and
|
|
120
|
+
// /api/stats `byType`, but were invisible in /api/stats `byScope` and
|
|
121
|
+
// unbrowseable in the UI — a confusing UX gap. The filter keeps the
|
|
122
|
+
// defense-in-depth check on container_tag, just widens it to both
|
|
123
|
+
// canonical scope markers.
|
|
124
|
+
const projectShards = await tursoShardManager.getAllShards("project", "");
|
|
125
|
+
const userShards = await tursoShardManager.getAllShards("user", "");
|
|
126
|
+
for (const shard of [...projectShards, ...userShards]) {
|
|
127
|
+
const db = await tursoConnectionManager.getConnection(shard.dbPath);
|
|
128
|
+
const memories = await tursoVectorSearch.getAllMemories(db);
|
|
129
|
+
allMemories.push(...memories.filter((m) => m.container_tag?.includes("_project_") || m.container_tag?.includes("_user_")));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return allMemories;
|
|
133
|
+
}
|
|
134
|
+
/** Labels (comma-separated memory tags) with how many memories carry each one. */
|
|
135
|
+
export async function handleListKeywords(tag) {
|
|
136
|
+
try {
|
|
137
|
+
await ensureTursoReady();
|
|
138
|
+
const rows = await loadMemoryRows(tag);
|
|
139
|
+
return { success: true, data: { keywords: countKeywords(rows.map((r) => r.tags)) } };
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
log("handleListKeywords: error", { error: String(error) });
|
|
143
|
+
return { success: false, error: String(error) };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
102
146
|
export async function handleListMemories(tag, page = 1, pageSize = 20, includePrompts = true, keyword) {
|
|
103
147
|
try {
|
|
104
148
|
await ensureTursoReady();
|
|
105
149
|
// Listing only reads SQLite rows; no vector ops happen here.
|
|
106
150
|
// See handleListTags comment - keep embedding init out of read paths.
|
|
107
|
-
const allMemories =
|
|
108
|
-
if (tag) {
|
|
109
|
-
const { scope: tagScope, hash } = extractScopeFromTag(tag);
|
|
110
|
-
const shards = await tursoShardManager.getAllShards(tagScope, hash);
|
|
111
|
-
for (const shard of shards) {
|
|
112
|
-
const db = await tursoConnectionManager.getConnection(shard.dbPath);
|
|
113
|
-
const memories = await tursoVectorSearch.listMemories(db, tag, 10000);
|
|
114
|
-
allMemories.push(...memories);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
// Iterate both project- and user-scoped shards. Previously this only
|
|
119
|
-
// walked project shards, which silently hid user-scope memories from the
|
|
120
|
-
// listing endpoint (Web UI navigation, /api/memories without a tag
|
|
121
|
-
// filter, …). User-scope memories still showed up in /api/search and
|
|
122
|
-
// /api/stats `byType`, but were invisible in /api/stats `byScope` and
|
|
123
|
-
// unbrowseable in the UI — a confusing UX gap. The filter keeps the
|
|
124
|
-
// defense-in-depth check on container_tag, just widens it to both
|
|
125
|
-
// canonical scope markers.
|
|
126
|
-
const projectShards = await tursoShardManager.getAllShards("project", "");
|
|
127
|
-
const userShards = await tursoShardManager.getAllShards("user", "");
|
|
128
|
-
for (const shard of [...projectShards, ...userShards]) {
|
|
129
|
-
const db = await tursoConnectionManager.getConnection(shard.dbPath);
|
|
130
|
-
const memories = await tursoVectorSearch.getAllMemories(db);
|
|
131
|
-
allMemories.push(...memories.filter((m) => m.container_tag?.includes("_project_") || m.container_tag?.includes("_user_")));
|
|
132
|
-
}
|
|
133
|
-
}
|
|
151
|
+
const allMemories = await loadMemoryRows(tag);
|
|
134
152
|
const memoriesWithType = allMemories.map((r) => {
|
|
135
153
|
const metadata = safeJSONParse(r.metadata);
|
|
136
154
|
return {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface KeywordCount {
|
|
2
|
+
keyword: string;
|
|
3
|
+
count: number;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Counts labels across stored `tags` fields (comma-separated). Labels are matched
|
|
7
|
+
* case-insensitively, the same rule the keyword filter uses, and each memory counts
|
|
8
|
+
* a label once. Sorted by count, then name.
|
|
9
|
+
*/
|
|
10
|
+
export declare function countKeywords(tagFields: Array<string | null | undefined>): KeywordCount[];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Counts labels across stored `tags` fields (comma-separated). Labels are matched
|
|
3
|
+
* case-insensitively, the same rule the keyword filter uses, and each memory counts
|
|
4
|
+
* a label once. Sorted by count, then name.
|
|
5
|
+
*/
|
|
6
|
+
export function countKeywords(tagFields) {
|
|
7
|
+
const counts = new Map();
|
|
8
|
+
for (const field of tagFields) {
|
|
9
|
+
if (!field)
|
|
10
|
+
continue;
|
|
11
|
+
const labels = new Set(field
|
|
12
|
+
.split(",")
|
|
13
|
+
.map((label) => label.trim().toLowerCase())
|
|
14
|
+
.filter(Boolean));
|
|
15
|
+
for (const label of labels)
|
|
16
|
+
counts.set(label, (counts.get(label) ?? 0) + 1);
|
|
17
|
+
}
|
|
18
|
+
return [...counts]
|
|
19
|
+
.map(([keyword, count]) => ({ keyword, count }))
|
|
20
|
+
.sort((a, b) => b.count - a.count || a.keyword.localeCompare(b.keyword));
|
|
21
|
+
}
|
|
@@ -12,7 +12,7 @@ import { isOlderVersion } from "./version-compare.js";
|
|
|
12
12
|
import { AUTH_HEADER, getOrCreateAuthToken, isAuthorizedApiRequest } from "./auth-token.js";
|
|
13
13
|
import { WebAuth } from "./web-auth.js";
|
|
14
14
|
import { NODE_HTTP_IDLE_TIMEOUT_MS } from "./request-timeouts.js";
|
|
15
|
-
import { handleListTags, handleListMemories, handleAddMemory, handleDeleteMemory, handleBulkDelete, handleUpdateMemory, handleSearch, handleStats, handlePinMemory, handleUnpinMemory, handleRunCleanup, handleRunDeduplication, handleDetectMigration, handleRunMigration, handleDetectTagMigration, handleRunTagMigrationBatch, handleGetTagMigrationProgress, handleDeletePrompt, handleBulkDeletePrompts, handleGetUserProfile, handleGetProfileChangelog, handleGetProfileSnapshot, handleRefreshProfile, handleAICleanup, handleApplyCleanup, handleUpdateProfileItem, } from "./api-handlers.js";
|
|
15
|
+
import { handleListTags, handleListKeywords, handleListMemories, handleAddMemory, handleDeleteMemory, handleBulkDelete, handleUpdateMemory, handleSearch, handleStats, handlePinMemory, handleUnpinMemory, handleRunCleanup, handleRunDeduplication, handleDetectMigration, handleRunMigration, handleDetectTagMigration, handleRunTagMigrationBatch, handleGetTagMigrationProgress, handleDeletePrompt, handleBulkDeletePrompts, handleGetUserProfile, handleGetProfileChangelog, handleGetProfileSnapshot, handleRefreshProfile, handleAICleanup, handleApplyCleanup, handleUpdateProfileItem, } from "./api-handlers.js";
|
|
16
16
|
const isBun = typeof globalThis.Bun !== "undefined";
|
|
17
17
|
const MIN_FAILED_TAKEOVERS = 3;
|
|
18
18
|
export function attachNodeDisconnectHandlers(req, res, onDisconnect) {
|
|
@@ -1184,6 +1184,10 @@ export class WebServer {
|
|
|
1184
1184
|
const result = await handleListTags();
|
|
1185
1185
|
return this.jsonResponse(result);
|
|
1186
1186
|
}
|
|
1187
|
+
if (path === "/api/keywords" && method === "GET") {
|
|
1188
|
+
const result = await handleListKeywords(url.searchParams.get("tag") || undefined);
|
|
1189
|
+
return this.jsonResponse(result);
|
|
1190
|
+
}
|
|
1187
1191
|
if (path === "/api/memories" && method === "GET") {
|
|
1188
1192
|
const tag = url.searchParams.get("tag") || undefined;
|
|
1189
1193
|
const page = parseInt(url.searchParams.get("page") || "1");
|