@memtensor/memos-local-openclaw-plugin 1.0.4-beta.0 → 1.0.4-beta.10
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/.env.example +7 -0
- package/README.md +24 -24
- package/dist/capture/index.d.ts +1 -1
- package/dist/capture/index.d.ts.map +1 -1
- package/dist/capture/index.js +34 -2
- package/dist/capture/index.js.map +1 -1
- package/dist/client/connector.d.ts +5 -2
- package/dist/client/connector.d.ts.map +1 -1
- package/dist/client/connector.js +173 -14
- package/dist/client/connector.js.map +1 -1
- package/dist/client/hub.d.ts.map +1 -1
- package/dist/client/hub.js +22 -0
- package/dist/client/hub.js.map +1 -1
- package/dist/client/skill-sync.d.ts +7 -0
- package/dist/client/skill-sync.d.ts.map +1 -1
- package/dist/client/skill-sync.js +10 -0
- package/dist/client/skill-sync.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +9 -11
- package/dist/config.js.map +1 -1
- package/dist/hub/server.d.ts +7 -0
- package/dist/hub/server.d.ts.map +1 -1
- package/dist/hub/server.js +301 -106
- package/dist/hub/server.js.map +1 -1
- package/dist/hub/user-manager.d.ts +3 -0
- package/dist/hub/user-manager.d.ts.map +1 -1
- package/dist/hub/user-manager.js +18 -1
- package/dist/hub/user-manager.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -2
- package/dist/index.js.map +1 -1
- package/dist/ingest/providers/index.d.ts.map +1 -1
- package/dist/ingest/providers/index.js +37 -6
- package/dist/ingest/providers/index.js.map +1 -1
- package/dist/recall/engine.d.ts.map +1 -1
- package/dist/recall/engine.js +91 -1
- package/dist/recall/engine.js.map +1 -1
- package/dist/shared/llm-call.d.ts +1 -0
- package/dist/shared/llm-call.d.ts.map +1 -1
- package/dist/shared/llm-call.js +82 -8
- package/dist/shared/llm-call.js.map +1 -1
- package/dist/sharing/types.d.ts +1 -1
- package/dist/sharing/types.d.ts.map +1 -1
- package/dist/skill/evolver.d.ts +2 -0
- package/dist/skill/evolver.d.ts.map +1 -1
- package/dist/skill/evolver.js +3 -0
- package/dist/skill/evolver.js.map +1 -1
- package/dist/storage/ensure-binding.d.ts +12 -0
- package/dist/storage/ensure-binding.d.ts.map +1 -0
- package/dist/storage/ensure-binding.js +53 -0
- package/dist/storage/ensure-binding.js.map +1 -0
- package/dist/storage/sqlite.d.ts +74 -20
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +301 -207
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/telemetry.d.ts +12 -5
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +156 -40
- package/dist/telemetry.js.map +1 -1
- package/dist/tools/memory-search.d.ts +3 -1
- package/dist/tools/memory-search.d.ts.map +1 -1
- package/dist/tools/memory-search.js +3 -1
- package/dist/tools/memory-search.js.map +1 -1
- package/dist/types.d.ts +1 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/viewer/html.d.ts.map +1 -1
- package/dist/viewer/html.js +2991 -1041
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts +32 -8
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +1122 -261
- package/dist/viewer/server.js.map +1 -1
- package/index.ts +384 -43
- package/openclaw.plugin.json +1 -1
- package/package.json +3 -2
- package/scripts/postinstall.cjs +1 -1
- package/skill/memos-memory-guide/SKILL.md +64 -26
- package/src/capture/index.ts +37 -1
- package/src/client/connector.ts +173 -16
- package/src/client/hub.ts +18 -0
- package/src/client/skill-sync.ts +14 -0
- package/src/config.ts +9 -11
- package/src/hub/server.ts +285 -98
- package/src/hub/user-manager.ts +20 -3
- package/src/index.ts +10 -2
- package/src/ingest/providers/index.ts +41 -7
- package/src/recall/engine.ts +84 -1
- package/src/shared/llm-call.ts +97 -9
- package/src/sharing/types.ts +1 -1
- package/src/skill/evolver.ts +5 -0
- package/src/storage/ensure-binding.ts +52 -0
- package/src/storage/sqlite.ts +310 -233
- package/src/telemetry.ts +172 -41
- package/src/tools/memory-search.ts +2 -1
- package/src/types.ts +1 -2
- package/src/viewer/html.ts +2991 -1041
- package/src/viewer/server.ts +984 -190
package/dist/storage/sqlite.js
CHANGED
|
@@ -146,11 +146,23 @@ class SqliteStore {
|
|
|
146
146
|
this.migrateSkillEmbeddingsAndFts();
|
|
147
147
|
this.migrateFtsToTrigram();
|
|
148
148
|
this.migrateHubTables();
|
|
149
|
+
this.migrateHubFtsToTrigram();
|
|
150
|
+
this.migrateLocalSharedTasksOwner();
|
|
149
151
|
this.log.debug("Database schema initialized");
|
|
150
152
|
}
|
|
151
153
|
migrateChunksIndexesForRecall() {
|
|
152
154
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_chunks_dedup_created ON chunks(dedup_status, created_at DESC)");
|
|
153
155
|
}
|
|
156
|
+
migrateLocalSharedTasksOwner() {
|
|
157
|
+
try {
|
|
158
|
+
const cols = this.db.prepare("PRAGMA table_info(local_shared_tasks)").all();
|
|
159
|
+
if (cols.length > 0 && !cols.some((c) => c.name === "original_owner")) {
|
|
160
|
+
this.db.exec("ALTER TABLE local_shared_tasks ADD COLUMN original_owner TEXT NOT NULL DEFAULT 'agent:main'");
|
|
161
|
+
this.log.info("Migrated: added original_owner column to local_shared_tasks");
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch { /* table may not exist yet */ }
|
|
165
|
+
}
|
|
154
166
|
migrateOwnerFields() {
|
|
155
167
|
const chunkCols = this.db.prepare("PRAGMA table_info(chunks)").all();
|
|
156
168
|
if (!chunkCols.some((c) => c.name === "owner")) {
|
|
@@ -298,6 +310,54 @@ class SqliteStore {
|
|
|
298
310
|
this.log.warn(`Failed to migrate skills_fts to trigram: ${err}`);
|
|
299
311
|
}
|
|
300
312
|
}
|
|
313
|
+
migrateHubFtsToTrigram() {
|
|
314
|
+
const tables = [
|
|
315
|
+
{
|
|
316
|
+
fts: "hub_chunks_fts", source: "hub_chunks", columns: "summary, content",
|
|
317
|
+
triggers: ["hub_chunks_ai", "hub_chunks_ad", "hub_chunks_au"],
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
fts: "hub_skills_fts", source: "hub_skills", columns: "name, description",
|
|
321
|
+
triggers: ["hub_skills_ai", "hub_skills_ad", "hub_skills_au"],
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
fts: "hub_memories_fts", source: "hub_memories", columns: "summary, content",
|
|
325
|
+
triggers: ["hub_memories_ai", "hub_memories_ad", "hub_memories_au"],
|
|
326
|
+
},
|
|
327
|
+
];
|
|
328
|
+
for (const t of tables) {
|
|
329
|
+
try {
|
|
330
|
+
const row = this.db.prepare(`SELECT sql FROM sqlite_master WHERE name='${t.fts}'`).get();
|
|
331
|
+
if (!row || !row.sql)
|
|
332
|
+
continue;
|
|
333
|
+
if (row.sql.includes("trigram"))
|
|
334
|
+
continue;
|
|
335
|
+
this.log.info(`Migrating ${t.fts} to trigram tokenizer...`);
|
|
336
|
+
for (const tr of t.triggers)
|
|
337
|
+
this.db.exec(`DROP TRIGGER IF EXISTS ${tr}`);
|
|
338
|
+
this.db.exec(`DROP TABLE IF EXISTS ${t.fts}`);
|
|
339
|
+
this.db.exec(`CREATE VIRTUAL TABLE ${t.fts} USING fts5(${t.columns}, content='${t.source}', content_rowid='rowid', tokenize='trigram')`);
|
|
340
|
+
this.db.exec(`
|
|
341
|
+
CREATE TRIGGER ${t.triggers[0]} AFTER INSERT ON ${t.source} BEGIN
|
|
342
|
+
INSERT INTO ${t.fts}(rowid, ${t.columns}) VALUES (new.rowid, ${t.columns.split(", ").map(c => "new." + c).join(", ")});
|
|
343
|
+
END;
|
|
344
|
+
CREATE TRIGGER ${t.triggers[1]} AFTER DELETE ON ${t.source} BEGIN
|
|
345
|
+
INSERT INTO ${t.fts}(${t.fts}, rowid, ${t.columns}) VALUES ('delete', old.rowid, ${t.columns.split(", ").map(c => "old." + c).join(", ")});
|
|
346
|
+
END;
|
|
347
|
+
CREATE TRIGGER ${t.triggers[2]} AFTER UPDATE ON ${t.source} BEGIN
|
|
348
|
+
INSERT INTO ${t.fts}(${t.fts}, rowid, ${t.columns}) VALUES ('delete', old.rowid, ${t.columns.split(", ").map(c => "old." + c).join(", ")});
|
|
349
|
+
INSERT INTO ${t.fts}(rowid, ${t.columns}) VALUES (new.rowid, ${t.columns.split(", ").map(c => "new." + c).join(", ")});
|
|
350
|
+
END
|
|
351
|
+
`);
|
|
352
|
+
this.db.exec(`INSERT INTO ${t.fts}(rowid, ${t.columns}) SELECT rowid, ${t.columns} FROM ${t.source}`);
|
|
353
|
+
const cnt = this.db.prepare(`SELECT COUNT(*) as c FROM ${t.fts}`).get().c;
|
|
354
|
+
this.log.info(`Migrated ${t.fts} to trigram: ${cnt} rows indexed`);
|
|
355
|
+
}
|
|
356
|
+
catch (err) {
|
|
357
|
+
this.log.warn(`Failed to migrate ${t.fts} to trigram: ${err}`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
301
361
|
migrateTaskId() {
|
|
302
362
|
const cols = this.db.prepare("PRAGMA table_info(chunks)").all();
|
|
303
363
|
if (!cols.some((c) => c.name === "task_id")) {
|
|
@@ -503,15 +563,16 @@ class SqliteStore {
|
|
|
503
563
|
recordToolCall(toolName, durationMs, success) {
|
|
504
564
|
this.db.prepare("INSERT INTO tool_calls (tool_name, duration_ms, success, called_at) VALUES (?, ?, ?, ?)").run(toolName, Math.round(durationMs), success ? 1 : 0, Date.now());
|
|
505
565
|
}
|
|
506
|
-
getToolMetrics(minutes) {
|
|
507
|
-
const since = Date.now() - minutes * 60 * 1000;
|
|
566
|
+
getToolMetrics(minutes, fromMs, toMs) {
|
|
567
|
+
const since = fromMs ?? (Date.now() - minutes * 60 * 1000);
|
|
568
|
+
const until = toMs ?? Date.now();
|
|
508
569
|
const rows = this.db.prepare(`SELECT tool_name,
|
|
509
570
|
duration_ms,
|
|
510
571
|
success,
|
|
511
572
|
strftime('%Y-%m-%d %H:%M', called_at/1000, 'unixepoch', 'localtime') as minute_key
|
|
512
573
|
FROM tool_calls
|
|
513
|
-
WHERE called_at >= ?
|
|
514
|
-
ORDER BY called_at`).all(since);
|
|
574
|
+
WHERE called_at >= ? AND called_at <= ?
|
|
575
|
+
ORDER BY called_at`).all(since, until);
|
|
515
576
|
const toolSet = new Set();
|
|
516
577
|
const minuteMap = new Map();
|
|
517
578
|
const aggMap = new Map();
|
|
@@ -644,34 +705,27 @@ class SqliteStore {
|
|
|
644
705
|
shared_at INTEGER NOT NULL
|
|
645
706
|
);
|
|
646
707
|
|
|
708
|
+
CREATE TABLE IF NOT EXISTS local_shared_memories (
|
|
709
|
+
chunk_id TEXT PRIMARY KEY REFERENCES chunks(id) ON DELETE CASCADE,
|
|
710
|
+
original_owner TEXT NOT NULL,
|
|
711
|
+
shared_at INTEGER NOT NULL
|
|
712
|
+
);
|
|
713
|
+
|
|
647
714
|
CREATE TABLE IF NOT EXISTS hub_users (
|
|
648
|
-
id
|
|
649
|
-
username
|
|
650
|
-
device_name
|
|
651
|
-
role
|
|
652
|
-
status
|
|
653
|
-
token_hash
|
|
654
|
-
created_at
|
|
655
|
-
approved_at
|
|
715
|
+
id TEXT PRIMARY KEY,
|
|
716
|
+
username TEXT NOT NULL UNIQUE,
|
|
717
|
+
device_name TEXT NOT NULL DEFAULT '',
|
|
718
|
+
role TEXT NOT NULL,
|
|
719
|
+
status TEXT NOT NULL,
|
|
720
|
+
token_hash TEXT NOT NULL DEFAULT '',
|
|
721
|
+
created_at INTEGER NOT NULL,
|
|
722
|
+
approved_at INTEGER,
|
|
723
|
+
last_ip TEXT NOT NULL DEFAULT '',
|
|
724
|
+
last_active_at INTEGER
|
|
656
725
|
);
|
|
657
726
|
CREATE INDEX IF NOT EXISTS idx_hub_users_status ON hub_users(status);
|
|
658
727
|
CREATE INDEX IF NOT EXISTS idx_hub_users_role ON hub_users(role);
|
|
659
728
|
|
|
660
|
-
CREATE TABLE IF NOT EXISTS hub_groups (
|
|
661
|
-
id TEXT PRIMARY KEY,
|
|
662
|
-
name TEXT NOT NULL UNIQUE,
|
|
663
|
-
description TEXT NOT NULL DEFAULT '',
|
|
664
|
-
created_at INTEGER NOT NULL
|
|
665
|
-
);
|
|
666
|
-
|
|
667
|
-
CREATE TABLE IF NOT EXISTS hub_group_members (
|
|
668
|
-
group_id TEXT NOT NULL REFERENCES hub_groups(id) ON DELETE CASCADE,
|
|
669
|
-
user_id TEXT NOT NULL REFERENCES hub_users(id) ON DELETE CASCADE,
|
|
670
|
-
joined_at INTEGER NOT NULL,
|
|
671
|
-
PRIMARY KEY (group_id, user_id)
|
|
672
|
-
);
|
|
673
|
-
CREATE INDEX IF NOT EXISTS idx_hub_group_members_user ON hub_group_members(user_id);
|
|
674
|
-
|
|
675
729
|
CREATE TABLE IF NOT EXISTS hub_tasks (
|
|
676
730
|
id TEXT PRIMARY KEY,
|
|
677
731
|
source_task_id TEXT NOT NULL,
|
|
@@ -713,7 +767,7 @@ class SqliteStore {
|
|
|
713
767
|
content,
|
|
714
768
|
content='hub_chunks',
|
|
715
769
|
content_rowid='rowid',
|
|
716
|
-
tokenize='
|
|
770
|
+
tokenize='trigram'
|
|
717
771
|
);
|
|
718
772
|
|
|
719
773
|
CREATE TRIGGER IF NOT EXISTS hub_chunks_ai AFTER INSERT ON hub_chunks BEGIN
|
|
@@ -763,7 +817,7 @@ class SqliteStore {
|
|
|
763
817
|
description,
|
|
764
818
|
content='hub_skills',
|
|
765
819
|
content_rowid='rowid',
|
|
766
|
-
tokenize='
|
|
820
|
+
tokenize='trigram'
|
|
767
821
|
);
|
|
768
822
|
|
|
769
823
|
CREATE TRIGGER IF NOT EXISTS hub_skills_ai AFTER INSERT ON hub_skills BEGIN
|
|
@@ -813,7 +867,7 @@ class SqliteStore {
|
|
|
813
867
|
content,
|
|
814
868
|
content='hub_memories',
|
|
815
869
|
content_rowid='rowid',
|
|
816
|
-
tokenize='
|
|
870
|
+
tokenize='trigram'
|
|
817
871
|
);
|
|
818
872
|
|
|
819
873
|
CREATE TRIGGER IF NOT EXISTS hub_memories_ai AFTER INSERT ON hub_memories BEGIN
|
|
@@ -833,6 +887,31 @@ class SqliteStore {
|
|
|
833
887
|
VALUES (new.rowid, new.summary, new.content);
|
|
834
888
|
END;
|
|
835
889
|
`);
|
|
890
|
+
this.db.exec(`
|
|
891
|
+
CREATE TABLE IF NOT EXISTS hub_notifications (
|
|
892
|
+
id TEXT PRIMARY KEY,
|
|
893
|
+
user_id TEXT NOT NULL,
|
|
894
|
+
type TEXT NOT NULL,
|
|
895
|
+
resource TEXT NOT NULL,
|
|
896
|
+
title TEXT NOT NULL,
|
|
897
|
+
message TEXT NOT NULL DEFAULT '',
|
|
898
|
+
read INTEGER NOT NULL DEFAULT 0,
|
|
899
|
+
created_at INTEGER NOT NULL
|
|
900
|
+
);
|
|
901
|
+
CREATE INDEX IF NOT EXISTS idx_hub_notif_user ON hub_notifications(user_id, read, created_at DESC);
|
|
902
|
+
`);
|
|
903
|
+
try {
|
|
904
|
+
const cols = this.db.prepare("PRAGMA table_info(hub_users)").all();
|
|
905
|
+
if (cols.length > 0 && !cols.some(c => c.name === "last_ip")) {
|
|
906
|
+
this.db.exec("ALTER TABLE hub_users ADD COLUMN last_ip TEXT NOT NULL DEFAULT ''");
|
|
907
|
+
this.log.info("Migrated: added last_ip column to hub_users");
|
|
908
|
+
}
|
|
909
|
+
if (cols.length > 0 && !cols.some(c => c.name === "last_active_at")) {
|
|
910
|
+
this.db.exec("ALTER TABLE hub_users ADD COLUMN last_active_at INTEGER");
|
|
911
|
+
this.log.info("Migrated: added last_active_at column to hub_users");
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
catch { /* table may not exist yet */ }
|
|
836
915
|
}
|
|
837
916
|
// ─── Write ───
|
|
838
917
|
insertChunk(chunk) {
|
|
@@ -959,6 +1038,30 @@ class SqliteStore {
|
|
|
959
1038
|
return [];
|
|
960
1039
|
}
|
|
961
1040
|
}
|
|
1041
|
+
hubMemoryPatternSearch(patterns, opts = {}) {
|
|
1042
|
+
if (patterns.length === 0)
|
|
1043
|
+
return [];
|
|
1044
|
+
const limit = opts.limit ?? 10;
|
|
1045
|
+
const conditions = patterns.map(() => "(hm.content LIKE ? OR hm.summary LIKE ?)");
|
|
1046
|
+
const params = [];
|
|
1047
|
+
for (const p of patterns) {
|
|
1048
|
+
params.push(`%${p}%`, `%${p}%`);
|
|
1049
|
+
}
|
|
1050
|
+
params.push(limit);
|
|
1051
|
+
try {
|
|
1052
|
+
const rows = this.db.prepare(`
|
|
1053
|
+
SELECT hm.id as memory_id, hm.content, hm.role, hm.created_at
|
|
1054
|
+
FROM hub_memories hm
|
|
1055
|
+
WHERE ${conditions.join(" OR ")}
|
|
1056
|
+
ORDER BY hm.created_at DESC
|
|
1057
|
+
LIMIT ?
|
|
1058
|
+
`).all(...params);
|
|
1059
|
+
return rows.map(r => ({ memoryId: r.memory_id, content: r.content, role: r.role, createdAt: r.created_at }));
|
|
1060
|
+
}
|
|
1061
|
+
catch {
|
|
1062
|
+
return [];
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
962
1065
|
// ─── Vector Search ───
|
|
963
1066
|
getAllEmbeddings(ownerFilter) {
|
|
964
1067
|
let sql = `SELECT e.chunk_id, e.vector, e.dimensions FROM embeddings e
|
|
@@ -1097,6 +1200,8 @@ class SqliteStore {
|
|
|
1097
1200
|
"skill_embeddings",
|
|
1098
1201
|
"skill_versions",
|
|
1099
1202
|
"skills",
|
|
1203
|
+
"local_shared_memories",
|
|
1204
|
+
"local_shared_tasks",
|
|
1100
1205
|
"embeddings",
|
|
1101
1206
|
"chunks",
|
|
1102
1207
|
"tasks",
|
|
@@ -1519,6 +1624,61 @@ class SqliteStore {
|
|
|
1519
1624
|
const rows = this.db.prepare('SELECT task_id, hub_task_id, visibility, group_id, synced_chunks FROM local_shared_tasks').all();
|
|
1520
1625
|
return rows.map(r => ({ taskId: r.task_id, hubTaskId: r.hub_task_id, visibility: r.visibility, groupId: r.group_id, syncedChunks: r.synced_chunks }));
|
|
1521
1626
|
}
|
|
1627
|
+
// ─── Local Shared Memories (client-side tracking) ───
|
|
1628
|
+
markMemorySharedLocally(chunkId) {
|
|
1629
|
+
const chunk = this.getChunk(chunkId);
|
|
1630
|
+
if (!chunk)
|
|
1631
|
+
return { ok: false, reason: "not_found" };
|
|
1632
|
+
if (chunk.owner === "public") {
|
|
1633
|
+
const existing = this.getLocalSharedMemory(chunkId);
|
|
1634
|
+
return {
|
|
1635
|
+
ok: true,
|
|
1636
|
+
owner: "public",
|
|
1637
|
+
originalOwner: existing?.originalOwner ?? undefined,
|
|
1638
|
+
sharedAt: existing?.sharedAt ?? undefined,
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1641
|
+
const sharedAt = Date.now();
|
|
1642
|
+
this.db.transaction(() => {
|
|
1643
|
+
this.db.prepare(`
|
|
1644
|
+
INSERT INTO local_shared_memories (chunk_id, original_owner, shared_at)
|
|
1645
|
+
VALUES (?, ?, ?)
|
|
1646
|
+
ON CONFLICT(chunk_id) DO UPDATE SET
|
|
1647
|
+
original_owner = excluded.original_owner,
|
|
1648
|
+
shared_at = excluded.shared_at
|
|
1649
|
+
`).run(chunkId, chunk.owner, sharedAt);
|
|
1650
|
+
this.updateChunk(chunkId, { owner: "public" });
|
|
1651
|
+
})();
|
|
1652
|
+
return { ok: true, owner: "public", originalOwner: chunk.owner, sharedAt };
|
|
1653
|
+
}
|
|
1654
|
+
unmarkMemorySharedLocally(chunkId, fallbackOwner) {
|
|
1655
|
+
const chunk = this.getChunk(chunkId);
|
|
1656
|
+
if (!chunk)
|
|
1657
|
+
return { ok: false, reason: "not_found" };
|
|
1658
|
+
if (chunk.owner !== "public") {
|
|
1659
|
+
return { ok: true, owner: chunk.owner };
|
|
1660
|
+
}
|
|
1661
|
+
const existing = this.getLocalSharedMemory(chunkId);
|
|
1662
|
+
const restoreOwner = existing?.originalOwner ?? fallbackOwner;
|
|
1663
|
+
if (!restoreOwner || restoreOwner === "public") {
|
|
1664
|
+
return { ok: false, reason: "original_owner_missing" };
|
|
1665
|
+
}
|
|
1666
|
+
this.db.transaction(() => {
|
|
1667
|
+
this.updateChunk(chunkId, { owner: restoreOwner });
|
|
1668
|
+
this.db.prepare("DELETE FROM local_shared_memories WHERE chunk_id = ?").run(chunkId);
|
|
1669
|
+
})();
|
|
1670
|
+
return { ok: true, owner: restoreOwner, originalOwner: restoreOwner };
|
|
1671
|
+
}
|
|
1672
|
+
getLocalSharedMemory(chunkId) {
|
|
1673
|
+
const row = this.db.prepare("SELECT chunk_id, original_owner, shared_at FROM local_shared_memories WHERE chunk_id = ?").get(chunkId);
|
|
1674
|
+
if (!row)
|
|
1675
|
+
return null;
|
|
1676
|
+
return {
|
|
1677
|
+
chunkId: row.chunk_id,
|
|
1678
|
+
originalOwner: row.original_owner,
|
|
1679
|
+
sharedAt: row.shared_at,
|
|
1680
|
+
};
|
|
1681
|
+
}
|
|
1522
1682
|
// ─── Hub Users / Groups ───
|
|
1523
1683
|
upsertHubUser(user) {
|
|
1524
1684
|
this.db.prepare(`
|
|
@@ -1538,65 +1698,49 @@ class SqliteStore {
|
|
|
1538
1698
|
const row = this.db.prepare('SELECT * FROM hub_users WHERE id = ?').get(userId);
|
|
1539
1699
|
if (!row)
|
|
1540
1700
|
return null;
|
|
1541
|
-
return
|
|
1701
|
+
return rowToHubUser(row);
|
|
1542
1702
|
}
|
|
1543
1703
|
listHubUsers(status) {
|
|
1544
1704
|
const rows = status
|
|
1545
1705
|
? this.db.prepare('SELECT * FROM hub_users WHERE status = ? ORDER BY created_at').all(status)
|
|
1546
1706
|
: this.db.prepare('SELECT * FROM hub_users ORDER BY created_at').all();
|
|
1547
|
-
return rows.map(
|
|
1548
|
-
}
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
}
|
|
1559
|
-
listHubGroups() {
|
|
1560
|
-
const rows = this.db.prepare('SELECT * FROM hub_groups ORDER BY name').all();
|
|
1561
|
-
return rows.map(rowToHubGroup);
|
|
1562
|
-
}
|
|
1563
|
-
addHubGroupMember(groupId, userId, joinedAt = Date.now()) {
|
|
1564
|
-
this.db.prepare(`
|
|
1565
|
-
INSERT INTO hub_group_members (group_id, user_id, joined_at)
|
|
1566
|
-
VALUES (?, ?, ?)
|
|
1567
|
-
ON CONFLICT(group_id, user_id) DO UPDATE SET joined_at = excluded.joined_at
|
|
1568
|
-
`).run(groupId, userId, joinedAt);
|
|
1569
|
-
}
|
|
1570
|
-
getHubGroupById(groupId) {
|
|
1571
|
-
const row = this.db.prepare('SELECT * FROM hub_groups WHERE id = ?').get(groupId);
|
|
1572
|
-
return row ? rowToHubGroup(row) : undefined;
|
|
1573
|
-
}
|
|
1574
|
-
deleteHubGroup(groupId) {
|
|
1575
|
-
const result = this.db.prepare('DELETE FROM hub_groups WHERE id = ?').run(groupId);
|
|
1707
|
+
return rows.map(rowToHubUser);
|
|
1708
|
+
}
|
|
1709
|
+
deleteHubUser(userId, cleanResources = false) {
|
|
1710
|
+
if (cleanResources) {
|
|
1711
|
+
this.db.prepare('DELETE FROM hub_tasks WHERE source_user_id = ?').run(userId);
|
|
1712
|
+
this.db.prepare('DELETE FROM hub_skills WHERE source_user_id = ?').run(userId);
|
|
1713
|
+
this.db.prepare('DELETE FROM hub_memories WHERE source_user_id = ?').run(userId);
|
|
1714
|
+
const result = this.db.prepare('DELETE FROM hub_users WHERE id = ?').run(userId);
|
|
1715
|
+
return result.changes > 0;
|
|
1716
|
+
}
|
|
1717
|
+
const result = this.db.prepare("UPDATE hub_users SET status = 'removed', token_hash = '' WHERE id = ?").run(userId);
|
|
1576
1718
|
return result.changes > 0;
|
|
1577
1719
|
}
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1720
|
+
updateHubUserActivity(userId, ip, timestamp) {
|
|
1721
|
+
this.db.prepare('UPDATE hub_users SET last_ip = ?, last_active_at = ? WHERE id = ?').run(ip, timestamp ?? Date.now(), userId);
|
|
1722
|
+
}
|
|
1723
|
+
getHubUserContributions() {
|
|
1724
|
+
const result = {};
|
|
1725
|
+
const memRows = this.db.prepare('SELECT source_user_id, COUNT(*) as cnt FROM hub_memories GROUP BY source_user_id').all();
|
|
1726
|
+
const taskRows = this.db.prepare('SELECT source_user_id, COUNT(*) as cnt FROM hub_tasks GROUP BY source_user_id').all();
|
|
1727
|
+
const skillRows = this.db.prepare('SELECT source_user_id, COUNT(*) as cnt FROM hub_skills GROUP BY source_user_id').all();
|
|
1728
|
+
for (const r of memRows) {
|
|
1729
|
+
if (!result[r.source_user_id])
|
|
1730
|
+
result[r.source_user_id] = { memoryCount: 0, taskCount: 0, skillCount: 0 };
|
|
1731
|
+
result[r.source_user_id].memoryCount = r.cnt;
|
|
1732
|
+
}
|
|
1733
|
+
for (const r of taskRows) {
|
|
1734
|
+
if (!result[r.source_user_id])
|
|
1735
|
+
result[r.source_user_id] = { memoryCount: 0, taskCount: 0, skillCount: 0 };
|
|
1736
|
+
result[r.source_user_id].taskCount = r.cnt;
|
|
1737
|
+
}
|
|
1738
|
+
for (const r of skillRows) {
|
|
1739
|
+
if (!result[r.source_user_id])
|
|
1740
|
+
result[r.source_user_id] = { memoryCount: 0, taskCount: 0, skillCount: 0 };
|
|
1741
|
+
result[r.source_user_id].skillCount = r.cnt;
|
|
1742
|
+
}
|
|
1743
|
+
return result;
|
|
1600
1744
|
}
|
|
1601
1745
|
// ─── Hub Shared Data ───
|
|
1602
1746
|
upsertHubTask(task) {
|
|
@@ -1616,6 +1760,10 @@ class SqliteStore {
|
|
|
1616
1760
|
const row = this.db.prepare('SELECT * FROM hub_tasks WHERE source_user_id = ? AND source_task_id = ?').get(sourceUserId, sourceTaskId);
|
|
1617
1761
|
return row ? rowToHubTask(row) : null;
|
|
1618
1762
|
}
|
|
1763
|
+
getHubTaskById(taskId) {
|
|
1764
|
+
const row = this.db.prepare('SELECT * FROM hub_tasks WHERE id = ?').get(taskId);
|
|
1765
|
+
return row ? rowToHubTask(row) : null;
|
|
1766
|
+
}
|
|
1619
1767
|
upsertHubChunk(chunk) {
|
|
1620
1768
|
if (!chunk.sourceTaskId)
|
|
1621
1769
|
throw new Error("sourceTaskId is required for hub chunk upserts");
|
|
@@ -1692,24 +1840,16 @@ class SqliteStore {
|
|
|
1692
1840
|
const limit = options?.maxResults ?? 10;
|
|
1693
1841
|
const userId = options?.userId ?? "";
|
|
1694
1842
|
const rows = this.db.prepare(`
|
|
1695
|
-
SELECT hc.id, hc.content, hc.summary, hc.role, hc.created_at, ht.title as task_title, ht.visibility,
|
|
1843
|
+
SELECT hc.id, hc.content, hc.summary, hc.role, hc.created_at, ht.title as task_title, ht.visibility, '' as group_name, hu.username as owner_name,
|
|
1696
1844
|
bm25(hub_chunks_fts) as rank
|
|
1697
1845
|
FROM hub_chunks_fts f
|
|
1698
1846
|
JOIN hub_chunks hc ON hc.rowid = f.rowid
|
|
1699
1847
|
JOIN hub_tasks ht ON ht.id = hc.hub_task_id
|
|
1700
|
-
LEFT JOIN hub_groups hg ON hg.id = ht.group_id
|
|
1701
1848
|
LEFT JOIN hub_users hu ON hu.id = ht.source_user_id
|
|
1702
1849
|
WHERE hub_chunks_fts MATCH ?
|
|
1703
|
-
AND (
|
|
1704
|
-
ht.visibility = 'public'
|
|
1705
|
-
OR EXISTS (
|
|
1706
|
-
SELECT 1 FROM hub_group_members gm
|
|
1707
|
-
WHERE gm.group_id = ht.group_id AND gm.user_id = ?
|
|
1708
|
-
)
|
|
1709
|
-
)
|
|
1710
1850
|
ORDER BY rank
|
|
1711
1851
|
LIMIT ?
|
|
1712
|
-
`).all(sanitizeFtsQuery(query),
|
|
1852
|
+
`).all(sanitizeFtsQuery(query), limit);
|
|
1713
1853
|
return rows.map((row, idx) => ({ hit: row, rank: idx + 1 }));
|
|
1714
1854
|
}
|
|
1715
1855
|
upsertHubEmbedding(chunkId, vector) {
|
|
@@ -1732,12 +1872,7 @@ class SqliteStore {
|
|
|
1732
1872
|
FROM hub_embeddings he
|
|
1733
1873
|
JOIN hub_chunks hc ON hc.id = he.chunk_id
|
|
1734
1874
|
JOIN hub_tasks ht ON ht.id = hc.hub_task_id
|
|
1735
|
-
|
|
1736
|
-
OR EXISTS (
|
|
1737
|
-
SELECT 1 FROM hub_group_members gm
|
|
1738
|
-
WHERE gm.group_id = ht.group_id AND gm.user_id = ?
|
|
1739
|
-
)
|
|
1740
|
-
`).all(userId);
|
|
1875
|
+
`).all();
|
|
1741
1876
|
return rows.map(r => ({
|
|
1742
1877
|
chunkId: r.chunk_id,
|
|
1743
1878
|
vector: new Float32Array(r.vector.buffer, r.vector.byteOffset, r.dimensions),
|
|
@@ -1745,22 +1880,14 @@ class SqliteStore {
|
|
|
1745
1880
|
}
|
|
1746
1881
|
getVisibleHubSearchHitByChunkId(chunkId, userId) {
|
|
1747
1882
|
const row = this.db.prepare(`
|
|
1748
|
-
SELECT hc.id, hc.content, hc.summary, hc.role, hc.created_at, ht.title as task_title, ht.visibility,
|
|
1883
|
+
SELECT hc.id, hc.content, hc.summary, hc.role, hc.created_at, ht.title as task_title, ht.visibility, '' as group_name, hu.username as owner_name,
|
|
1749
1884
|
0 as rank
|
|
1750
1885
|
FROM hub_chunks hc
|
|
1751
1886
|
JOIN hub_tasks ht ON ht.id = hc.hub_task_id
|
|
1752
|
-
LEFT JOIN hub_groups hg ON hg.id = ht.group_id
|
|
1753
1887
|
LEFT JOIN hub_users hu ON hu.id = ht.source_user_id
|
|
1754
1888
|
WHERE hc.id = ?
|
|
1755
|
-
AND (
|
|
1756
|
-
ht.visibility = 'public'
|
|
1757
|
-
OR EXISTS (
|
|
1758
|
-
SELECT 1 FROM hub_group_members gm
|
|
1759
|
-
WHERE gm.group_id = ht.group_id AND gm.user_id = ?
|
|
1760
|
-
)
|
|
1761
|
-
)
|
|
1762
1889
|
LIMIT 1
|
|
1763
|
-
`).get(chunkId
|
|
1890
|
+
`).get(chunkId);
|
|
1764
1891
|
return row ?? null;
|
|
1765
1892
|
}
|
|
1766
1893
|
getHubChunkById(chunkId) {
|
|
@@ -1774,39 +1901,25 @@ class SqliteStore {
|
|
|
1774
1901
|
let rows;
|
|
1775
1902
|
if (sanitized) {
|
|
1776
1903
|
rows = this.db.prepare(`
|
|
1777
|
-
SELECT hs.id, hs.name, hs.description, hs.version, hs.visibility,
|
|
1904
|
+
SELECT hs.id, hs.name, hs.description, hs.version, hs.visibility, '' AS group_name, hu.username AS owner_name, hu.status AS owner_status, hs.quality_score,
|
|
1778
1905
|
bm25(hub_skills_fts) as rank
|
|
1779
1906
|
FROM hub_skills_fts f
|
|
1780
1907
|
JOIN hub_skills hs ON hs.rowid = f.rowid
|
|
1781
|
-
LEFT JOIN hub_groups hg ON hg.id = hs.group_id
|
|
1782
1908
|
LEFT JOIN hub_users hu ON hu.id = hs.source_user_id
|
|
1783
1909
|
WHERE hub_skills_fts MATCH ?
|
|
1784
|
-
AND (
|
|
1785
|
-
hs.visibility = 'public'
|
|
1786
|
-
OR EXISTS (
|
|
1787
|
-
SELECT 1 FROM hub_group_members gm
|
|
1788
|
-
WHERE gm.group_id = hs.group_id AND gm.user_id = ?
|
|
1789
|
-
)
|
|
1790
|
-
)
|
|
1791
1910
|
ORDER BY rank
|
|
1792
1911
|
LIMIT ?
|
|
1793
|
-
`).all(sanitized,
|
|
1912
|
+
`).all(sanitized, limit);
|
|
1794
1913
|
}
|
|
1795
1914
|
else {
|
|
1796
1915
|
rows = this.db.prepare(`
|
|
1797
|
-
SELECT hs.id, hs.name, hs.description, hs.version, hs.visibility,
|
|
1916
|
+
SELECT hs.id, hs.name, hs.description, hs.version, hs.visibility, '' AS group_name, hu.username AS owner_name, hu.status AS owner_status, hs.quality_score,
|
|
1798
1917
|
0 as rank
|
|
1799
1918
|
FROM hub_skills hs
|
|
1800
|
-
LEFT JOIN hub_groups hg ON hg.id = hs.group_id
|
|
1801
1919
|
LEFT JOIN hub_users hu ON hu.id = hs.source_user_id
|
|
1802
|
-
WHERE hs.visibility = 'public'
|
|
1803
|
-
OR EXISTS (
|
|
1804
|
-
SELECT 1 FROM hub_group_members gm
|
|
1805
|
-
WHERE gm.group_id = hs.group_id AND gm.user_id = ?
|
|
1806
|
-
)
|
|
1807
1920
|
ORDER BY hs.updated_at DESC
|
|
1808
1921
|
LIMIT ?
|
|
1809
|
-
`).all(
|
|
1922
|
+
`).all(limit);
|
|
1810
1923
|
}
|
|
1811
1924
|
return rows.map((row, idx) => ({ hit: row, rank: idx + 1 }));
|
|
1812
1925
|
}
|
|
@@ -1815,81 +1928,71 @@ class SqliteStore {
|
|
|
1815
1928
|
}
|
|
1816
1929
|
listVisibleHubTasks(userId, limit = 40) {
|
|
1817
1930
|
const rows = this.db.prepare(`
|
|
1818
|
-
SELECT t.*, u.username AS owner_name,
|
|
1931
|
+
SELECT t.*, u.username AS owner_name, u.status AS owner_status, NULL AS group_name,
|
|
1819
1932
|
(SELECT COUNT(*) FROM hub_chunks c WHERE c.hub_task_id = t.id) AS chunk_count
|
|
1820
1933
|
FROM hub_tasks t
|
|
1821
1934
|
LEFT JOIN hub_users u ON u.id = t.source_user_id
|
|
1822
|
-
LEFT JOIN hub_groups g ON g.id = t.group_id
|
|
1823
|
-
WHERE t.visibility = 'public'
|
|
1824
|
-
OR EXISTS (
|
|
1825
|
-
SELECT 1 FROM hub_group_members gm
|
|
1826
|
-
WHERE gm.group_id = t.group_id AND gm.user_id = ?
|
|
1827
|
-
)
|
|
1828
1935
|
ORDER BY t.updated_at DESC
|
|
1829
1936
|
LIMIT ?
|
|
1830
|
-
`).all(
|
|
1937
|
+
`).all(limit);
|
|
1831
1938
|
return rows.map(r => ({
|
|
1832
1939
|
id: r.id, sourceTaskId: r.source_task_id, sourceUserId: r.source_user_id,
|
|
1833
1940
|
title: r.title, summary: r.summary, groupId: r.group_id, groupName: r.group_name ?? null,
|
|
1834
|
-
visibility: r.visibility, ownerName: r.owner_name ?? "unknown", chunkCount: r.chunk_count ?? 0,
|
|
1941
|
+
visibility: r.visibility, ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", chunkCount: r.chunk_count ?? 0,
|
|
1835
1942
|
createdAt: r.created_at, updatedAt: r.updated_at,
|
|
1836
1943
|
}));
|
|
1837
1944
|
}
|
|
1838
1945
|
listAllHubTasks() {
|
|
1839
1946
|
const rows = this.db.prepare(`
|
|
1840
|
-
SELECT t.*, u.username AS owner_name,
|
|
1947
|
+
SELECT t.*, u.username AS owner_name, u.status AS owner_status,
|
|
1841
1948
|
(SELECT COUNT(*) FROM hub_chunks c WHERE c.hub_task_id = t.id) AS chunk_count
|
|
1842
1949
|
FROM hub_tasks t
|
|
1843
1950
|
LEFT JOIN hub_users u ON u.id = t.source_user_id
|
|
1844
|
-
LEFT JOIN hub_groups g ON g.id = t.group_id
|
|
1845
1951
|
ORDER BY t.updated_at DESC
|
|
1846
1952
|
`).all();
|
|
1847
1953
|
return rows.map(r => ({
|
|
1848
1954
|
id: r.id, sourceTaskId: r.source_task_id, sourceUserId: r.source_user_id,
|
|
1849
|
-
title: r.title, summary: r.summary, groupId: r.group_id, groupName:
|
|
1850
|
-
visibility: r.visibility, ownerName: r.owner_name ?? "unknown", chunkCount: r.chunk_count ?? 0,
|
|
1955
|
+
title: r.title, summary: r.summary, groupId: r.group_id, groupName: null,
|
|
1956
|
+
visibility: r.visibility, ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", chunkCount: r.chunk_count ?? 0,
|
|
1851
1957
|
createdAt: r.created_at, updatedAt: r.updated_at,
|
|
1852
1958
|
}));
|
|
1853
1959
|
}
|
|
1960
|
+
listHubChunksByTaskId(hubTaskId) {
|
|
1961
|
+
const rows = this.db.prepare('SELECT * FROM hub_chunks WHERE hub_task_id = ? ORDER BY created_at ASC').all(hubTaskId);
|
|
1962
|
+
return rows.map(rowToHubChunk);
|
|
1963
|
+
}
|
|
1854
1964
|
deleteHubTaskById(taskId) {
|
|
1855
1965
|
const info = this.db.prepare('DELETE FROM hub_tasks WHERE id = ?').run(taskId);
|
|
1856
1966
|
return info.changes > 0;
|
|
1857
1967
|
}
|
|
1858
1968
|
listVisibleHubSkills(userId, limit = 40) {
|
|
1859
1969
|
const rows = this.db.prepare(`
|
|
1860
|
-
SELECT s.*, u.username AS owner_name,
|
|
1970
|
+
SELECT s.*, u.username AS owner_name, u.status AS owner_status, NULL AS group_name
|
|
1861
1971
|
FROM hub_skills s
|
|
1862
1972
|
LEFT JOIN hub_users u ON u.id = s.source_user_id
|
|
1863
|
-
LEFT JOIN hub_groups g ON g.id = s.group_id
|
|
1864
|
-
WHERE s.visibility = 'public'
|
|
1865
|
-
OR EXISTS (
|
|
1866
|
-
SELECT 1 FROM hub_group_members gm
|
|
1867
|
-
WHERE gm.group_id = s.group_id AND gm.user_id = ?
|
|
1868
|
-
)
|
|
1869
1973
|
ORDER BY s.updated_at DESC
|
|
1870
1974
|
LIMIT ?
|
|
1871
|
-
`).all(
|
|
1975
|
+
`).all(limit);
|
|
1872
1976
|
return rows.map(r => ({
|
|
1873
1977
|
id: r.id, sourceSkillId: r.source_skill_id, sourceUserId: r.source_user_id,
|
|
1874
1978
|
name: r.name, description: r.description, version: r.version,
|
|
1875
1979
|
groupId: r.group_id, groupName: r.group_name ?? null, visibility: r.visibility,
|
|
1876
|
-
ownerName: r.owner_name ?? "unknown", qualityScore: r.quality_score,
|
|
1980
|
+
ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", qualityScore: r.quality_score,
|
|
1877
1981
|
createdAt: r.created_at, updatedAt: r.updated_at,
|
|
1878
1982
|
}));
|
|
1879
1983
|
}
|
|
1880
1984
|
listAllHubSkills() {
|
|
1881
1985
|
const rows = this.db.prepare(`
|
|
1882
|
-
SELECT s.*, u.username AS owner_name,
|
|
1986
|
+
SELECT s.*, u.username AS owner_name, u.status AS owner_status
|
|
1883
1987
|
FROM hub_skills s
|
|
1884
1988
|
LEFT JOIN hub_users u ON u.id = s.source_user_id
|
|
1885
|
-
LEFT JOIN hub_groups g ON g.id = s.group_id
|
|
1886
1989
|
ORDER BY s.updated_at DESC
|
|
1887
1990
|
`).all();
|
|
1888
1991
|
return rows.map(r => ({
|
|
1889
1992
|
id: r.id, sourceSkillId: r.source_skill_id, sourceUserId: r.source_user_id,
|
|
1890
1993
|
name: r.name, description: r.description, version: r.version,
|
|
1891
|
-
groupId: r.group_id, groupName:
|
|
1892
|
-
ownerName: r.owner_name ?? "unknown", qualityScore: r.quality_score,
|
|
1994
|
+
groupId: r.group_id, groupName: null, visibility: r.visibility,
|
|
1995
|
+
ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", qualityScore: r.quality_score,
|
|
1893
1996
|
createdAt: r.created_at, updatedAt: r.updated_at,
|
|
1894
1997
|
}));
|
|
1895
1998
|
}
|
|
@@ -1928,6 +2031,37 @@ class SqliteStore {
|
|
|
1928
2031
|
const info = this.db.prepare('DELETE FROM hub_memories WHERE id = ?').run(memoryId);
|
|
1929
2032
|
return info.changes > 0;
|
|
1930
2033
|
}
|
|
2034
|
+
// ─── Hub Notifications ───
|
|
2035
|
+
insertHubNotification(n) {
|
|
2036
|
+
this.db.prepare('INSERT INTO hub_notifications (id, user_id, type, resource, title, message, read, created_at) VALUES (?, ?, ?, ?, ?, ?, 0, ?)').run(n.id, n.userId, n.type, n.resource, n.title, n.message ?? '', Date.now());
|
|
2037
|
+
}
|
|
2038
|
+
hasRecentHubNotification(userId, type, resource, windowMs = 300_000) {
|
|
2039
|
+
const since = Date.now() - windowMs;
|
|
2040
|
+
const row = this.db.prepare('SELECT COUNT(*) AS cnt FROM hub_notifications WHERE user_id = ? AND type = ? AND resource = ? AND created_at > ?').get(userId, type, resource, since);
|
|
2041
|
+
return row.cnt > 0;
|
|
2042
|
+
}
|
|
2043
|
+
listHubNotifications(userId, opts) {
|
|
2044
|
+
const where = opts?.unreadOnly ? 'WHERE user_id = ? AND read = 0' : 'WHERE user_id = ?';
|
|
2045
|
+
const limit = opts?.limit ?? 50;
|
|
2046
|
+
const rows = this.db.prepare(`SELECT * FROM hub_notifications ${where} ORDER BY created_at DESC LIMIT ?`).all(userId, limit);
|
|
2047
|
+
return rows.map(r => ({ id: r.id, userId: r.user_id, type: r.type, resource: r.resource, title: r.title, message: r.message, read: !!r.read, createdAt: r.created_at }));
|
|
2048
|
+
}
|
|
2049
|
+
countUnreadHubNotifications(userId) {
|
|
2050
|
+
const row = this.db.prepare('SELECT COUNT(*) AS cnt FROM hub_notifications WHERE user_id = ? AND read = 0').get(userId);
|
|
2051
|
+
return row.cnt;
|
|
2052
|
+
}
|
|
2053
|
+
markHubNotificationsRead(userId, ids) {
|
|
2054
|
+
if (ids && ids.length > 0) {
|
|
2055
|
+
const placeholders = ids.map(() => '?').join(',');
|
|
2056
|
+
this.db.prepare(`UPDATE hub_notifications SET read = 1 WHERE user_id = ? AND id IN (${placeholders})`).run(userId, ...ids);
|
|
2057
|
+
}
|
|
2058
|
+
else {
|
|
2059
|
+
this.db.prepare('UPDATE hub_notifications SET read = 1 WHERE user_id = ?').run(userId);
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
clearHubNotifications(userId) {
|
|
2063
|
+
this.db.prepare('DELETE FROM hub_notifications WHERE user_id = ?').run(userId);
|
|
2064
|
+
}
|
|
1931
2065
|
upsertHubMemoryEmbedding(memoryId, vector) {
|
|
1932
2066
|
const buf = Buffer.from(vector.buffer, vector.byteOffset, vector.byteLength);
|
|
1933
2067
|
this.db.prepare(`
|
|
@@ -1949,23 +2083,15 @@ class SqliteStore {
|
|
|
1949
2083
|
if (!sanitized)
|
|
1950
2084
|
return [];
|
|
1951
2085
|
const rows = this.db.prepare(`
|
|
1952
|
-
SELECT hm.id, hm.content, hm.summary, hm.role, hm.created_at, hm.visibility,
|
|
2086
|
+
SELECT hm.id, hm.content, hm.summary, hm.role, hm.created_at, hm.visibility, '' as group_name, hu.username as owner_name,
|
|
1953
2087
|
bm25(hub_memories_fts) as rank
|
|
1954
2088
|
FROM hub_memories_fts f
|
|
1955
2089
|
JOIN hub_memories hm ON hm.rowid = f.rowid
|
|
1956
|
-
LEFT JOIN hub_groups hg ON hg.id = hm.group_id
|
|
1957
2090
|
LEFT JOIN hub_users hu ON hu.id = hm.source_user_id
|
|
1958
2091
|
WHERE hub_memories_fts MATCH ?
|
|
1959
|
-
AND (
|
|
1960
|
-
hm.visibility = 'public'
|
|
1961
|
-
OR EXISTS (
|
|
1962
|
-
SELECT 1 FROM hub_group_members gm
|
|
1963
|
-
WHERE gm.group_id = hm.group_id AND gm.user_id = ?
|
|
1964
|
-
)
|
|
1965
|
-
)
|
|
1966
2092
|
ORDER BY rank
|
|
1967
2093
|
LIMIT ?
|
|
1968
|
-
`).all(sanitized,
|
|
2094
|
+
`).all(sanitized, limit);
|
|
1969
2095
|
return rows.map((row, idx) => ({ hit: row, rank: idx + 1 }));
|
|
1970
2096
|
}
|
|
1971
2097
|
getVisibleHubMemoryEmbeddings(userId) {
|
|
@@ -1973,12 +2099,7 @@ class SqliteStore {
|
|
|
1973
2099
|
SELECT hme.memory_id, hme.vector, hme.dimensions
|
|
1974
2100
|
FROM hub_memory_embeddings hme
|
|
1975
2101
|
JOIN hub_memories hm ON hm.id = hme.memory_id
|
|
1976
|
-
|
|
1977
|
-
OR EXISTS (
|
|
1978
|
-
SELECT 1 FROM hub_group_members gm
|
|
1979
|
-
WHERE gm.group_id = hm.group_id AND gm.user_id = ?
|
|
1980
|
-
)
|
|
1981
|
-
`).all(userId);
|
|
2102
|
+
`).all();
|
|
1982
2103
|
return rows.map(r => ({
|
|
1983
2104
|
memoryId: r.memory_id,
|
|
1984
2105
|
vector: new Float32Array(r.vector.buffer, r.vector.byteOffset, r.dimensions),
|
|
@@ -1986,57 +2107,42 @@ class SqliteStore {
|
|
|
1986
2107
|
}
|
|
1987
2108
|
getVisibleHubSearchHitByMemoryId(memoryId, userId) {
|
|
1988
2109
|
const row = this.db.prepare(`
|
|
1989
|
-
SELECT hm.id, hm.content, hm.summary, hm.role, hm.created_at, hm.visibility,
|
|
2110
|
+
SELECT hm.id, hm.content, hm.summary, hm.role, hm.created_at, hm.visibility, '' as group_name, hu.username as owner_name,
|
|
1990
2111
|
0 as rank
|
|
1991
2112
|
FROM hub_memories hm
|
|
1992
|
-
LEFT JOIN hub_groups hg ON hg.id = hm.group_id
|
|
1993
2113
|
LEFT JOIN hub_users hu ON hu.id = hm.source_user_id
|
|
1994
2114
|
WHERE hm.id = ?
|
|
1995
|
-
AND (
|
|
1996
|
-
hm.visibility = 'public'
|
|
1997
|
-
OR EXISTS (
|
|
1998
|
-
SELECT 1 FROM hub_group_members gm
|
|
1999
|
-
WHERE gm.group_id = hm.group_id AND gm.user_id = ?
|
|
2000
|
-
)
|
|
2001
|
-
)
|
|
2002
2115
|
LIMIT 1
|
|
2003
|
-
`).get(memoryId
|
|
2116
|
+
`).get(memoryId);
|
|
2004
2117
|
return row ?? null;
|
|
2005
2118
|
}
|
|
2006
2119
|
listVisibleHubMemories(userId, limit = 40) {
|
|
2007
2120
|
const rows = this.db.prepare(`
|
|
2008
|
-
SELECT m.*, u.username AS owner_name,
|
|
2121
|
+
SELECT m.*, u.username AS owner_name, u.status AS owner_status, NULL AS group_name
|
|
2009
2122
|
FROM hub_memories m
|
|
2010
2123
|
LEFT JOIN hub_users u ON u.id = m.source_user_id
|
|
2011
|
-
LEFT JOIN hub_groups g ON g.id = m.group_id
|
|
2012
|
-
WHERE m.visibility = 'public'
|
|
2013
|
-
OR EXISTS (
|
|
2014
|
-
SELECT 1 FROM hub_group_members gm
|
|
2015
|
-
WHERE gm.group_id = m.group_id AND gm.user_id = ?
|
|
2016
|
-
)
|
|
2017
2124
|
ORDER BY m.updated_at DESC
|
|
2018
2125
|
LIMIT ?
|
|
2019
|
-
`).all(
|
|
2126
|
+
`).all(limit);
|
|
2020
2127
|
return rows.map(r => ({
|
|
2021
2128
|
id: r.id, sourceChunkId: r.source_chunk_id, sourceUserId: r.source_user_id,
|
|
2022
|
-
role: r.role, summary: r.summary, kind: r.kind,
|
|
2129
|
+
role: r.role, content: r.content ?? "", summary: r.summary, kind: r.kind,
|
|
2023
2130
|
groupId: r.group_id, groupName: r.group_name ?? null, visibility: r.visibility,
|
|
2024
|
-
ownerName: r.owner_name ?? "unknown", createdAt: r.created_at, updatedAt: r.updated_at,
|
|
2131
|
+
ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", createdAt: r.created_at, updatedAt: r.updated_at,
|
|
2025
2132
|
}));
|
|
2026
2133
|
}
|
|
2027
2134
|
listAllHubMemories() {
|
|
2028
2135
|
const rows = this.db.prepare(`
|
|
2029
|
-
SELECT m.*, u.username AS owner_name,
|
|
2136
|
+
SELECT m.*, u.username AS owner_name, u.status AS owner_status
|
|
2030
2137
|
FROM hub_memories m
|
|
2031
2138
|
LEFT JOIN hub_users u ON u.id = m.source_user_id
|
|
2032
|
-
LEFT JOIN hub_groups g ON g.id = m.group_id
|
|
2033
2139
|
ORDER BY m.updated_at DESC
|
|
2034
2140
|
`).all();
|
|
2035
2141
|
return rows.map(r => ({
|
|
2036
2142
|
id: r.id, sourceChunkId: r.source_chunk_id, sourceUserId: r.source_user_id,
|
|
2037
|
-
role: r.role, summary: r.summary, kind: r.kind,
|
|
2038
|
-
groupId: r.group_id, groupName:
|
|
2039
|
-
ownerName: r.owner_name ?? "unknown", createdAt: r.created_at, updatedAt: r.updated_at,
|
|
2143
|
+
role: r.role, content: r.content ?? "", summary: r.summary, kind: r.kind,
|
|
2144
|
+
groupId: r.group_id, groupName: null, visibility: r.visibility,
|
|
2145
|
+
ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", createdAt: r.created_at, updatedAt: r.updated_at,
|
|
2040
2146
|
}));
|
|
2041
2147
|
}
|
|
2042
2148
|
resolveCanonicalHubTaskId(taskId, sourceUserId, sourceTaskId) {
|
|
@@ -2061,12 +2167,6 @@ class SqliteStore {
|
|
|
2061
2167
|
}
|
|
2062
2168
|
throw new Error(`source skill not found for skillId=${skillId}`);
|
|
2063
2169
|
}
|
|
2064
|
-
attachGroupsToHubUser(user) {
|
|
2065
|
-
return {
|
|
2066
|
-
...user,
|
|
2067
|
-
groups: this.getGroupsForHubUser(user.id),
|
|
2068
|
-
};
|
|
2069
|
-
}
|
|
2070
2170
|
getSessionOwnerMap(sessionKeys) {
|
|
2071
2171
|
const result = new Map();
|
|
2072
2172
|
if (sessionKeys.length === 0)
|
|
@@ -2189,14 +2289,8 @@ function rowToHubUser(row) {
|
|
|
2189
2289
|
tokenHash: row.token_hash,
|
|
2190
2290
|
createdAt: row.created_at,
|
|
2191
2291
|
approvedAt: row.approved_at,
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
function rowToHubGroup(row) {
|
|
2195
|
-
return {
|
|
2196
|
-
id: row.id,
|
|
2197
|
-
name: row.name,
|
|
2198
|
-
description: row.description,
|
|
2199
|
-
createdAt: row.created_at,
|
|
2292
|
+
lastIp: row.last_ip || "",
|
|
2293
|
+
lastActiveAt: row.last_active_at ?? null,
|
|
2200
2294
|
};
|
|
2201
2295
|
}
|
|
2202
2296
|
function rowToHubTask(row) {
|