@memtensor/memos-local-openclaw-plugin 1.0.4-beta.1 → 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 +1 -2
- package/dist/client/connector.d.ts.map +1 -1
- package/dist/client/connector.js +93 -26
- 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 +0 -2
- 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 +277 -87
- package/dist/hub/server.js.map +1 -1
- package/dist/hub/user-manager.d.ts +2 -0
- package/dist/hub/user-manager.d.ts.map +1 -1
- package/dist/hub/user-manager.js +5 -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 +286 -118
- 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 +2660 -889
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts +30 -8
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +965 -193
- 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 +91 -28
- package/src/client/hub.ts +18 -0
- package/src/client/skill-sync.ts +14 -0
- package/src/config.ts +0 -2
- package/src/hub/server.ts +259 -78
- package/src/hub/user-manager.ts +7 -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 +295 -144
- 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 +2660 -889
- package/src/viewer/server.ts +888 -177
package/src/storage/sqlite.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { createHash } from "crypto";
|
|
|
3
3
|
import * as fs from "fs";
|
|
4
4
|
import * as path from "path";
|
|
5
5
|
import type { Chunk, ChunkRef, DedupStatus, Task, TaskStatus, Skill, SkillStatus, SkillVisibility, SkillVersion, TaskSkillLink, TaskSkillRelation, Logger } from "../types";
|
|
6
|
-
import type {
|
|
6
|
+
import type { SharedVisibility, UserInfo, UserRole, UserStatus } from "../sharing/types";
|
|
7
7
|
|
|
8
8
|
export class SqliteStore {
|
|
9
9
|
private db: Database.Database;
|
|
@@ -112,6 +112,8 @@ export class SqliteStore {
|
|
|
112
112
|
this.migrateSkillEmbeddingsAndFts();
|
|
113
113
|
this.migrateFtsToTrigram();
|
|
114
114
|
this.migrateHubTables();
|
|
115
|
+
this.migrateHubFtsToTrigram();
|
|
116
|
+
this.migrateLocalSharedTasksOwner();
|
|
115
117
|
this.log.debug("Database schema initialized");
|
|
116
118
|
}
|
|
117
119
|
|
|
@@ -119,6 +121,16 @@ export class SqliteStore {
|
|
|
119
121
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_chunks_dedup_created ON chunks(dedup_status, created_at DESC)");
|
|
120
122
|
}
|
|
121
123
|
|
|
124
|
+
private migrateLocalSharedTasksOwner(): void {
|
|
125
|
+
try {
|
|
126
|
+
const cols = this.db.prepare("PRAGMA table_info(local_shared_tasks)").all() as Array<{ name: string }>;
|
|
127
|
+
if (cols.length > 0 && !cols.some((c) => c.name === "original_owner")) {
|
|
128
|
+
this.db.exec("ALTER TABLE local_shared_tasks ADD COLUMN original_owner TEXT NOT NULL DEFAULT 'agent:main'");
|
|
129
|
+
this.log.info("Migrated: added original_owner column to local_shared_tasks");
|
|
130
|
+
}
|
|
131
|
+
} catch { /* table may not exist yet */ }
|
|
132
|
+
}
|
|
133
|
+
|
|
122
134
|
private migrateOwnerFields(): void {
|
|
123
135
|
const chunkCols = this.db.prepare("PRAGMA table_info(chunks)").all() as Array<{ name: string }>;
|
|
124
136
|
if (!chunkCols.some((c) => c.name === "owner")) {
|
|
@@ -273,6 +285,51 @@ export class SqliteStore {
|
|
|
273
285
|
}
|
|
274
286
|
}
|
|
275
287
|
|
|
288
|
+
private migrateHubFtsToTrigram(): void {
|
|
289
|
+
const tables: Array<{ fts: string; source: string; columns: string; triggers: string[] }> = [
|
|
290
|
+
{
|
|
291
|
+
fts: "hub_chunks_fts", source: "hub_chunks", columns: "summary, content",
|
|
292
|
+
triggers: ["hub_chunks_ai", "hub_chunks_ad", "hub_chunks_au"],
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
fts: "hub_skills_fts", source: "hub_skills", columns: "name, description",
|
|
296
|
+
triggers: ["hub_skills_ai", "hub_skills_ad", "hub_skills_au"],
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
fts: "hub_memories_fts", source: "hub_memories", columns: "summary, content",
|
|
300
|
+
triggers: ["hub_memories_ai", "hub_memories_ad", "hub_memories_au"],
|
|
301
|
+
},
|
|
302
|
+
];
|
|
303
|
+
for (const t of tables) {
|
|
304
|
+
try {
|
|
305
|
+
const row = this.db.prepare(`SELECT sql FROM sqlite_master WHERE name='${t.fts}'`).get() as { sql: string } | undefined;
|
|
306
|
+
if (!row || !row.sql) continue;
|
|
307
|
+
if (row.sql.includes("trigram")) continue;
|
|
308
|
+
this.log.info(`Migrating ${t.fts} to trigram tokenizer...`);
|
|
309
|
+
for (const tr of t.triggers) this.db.exec(`DROP TRIGGER IF EXISTS ${tr}`);
|
|
310
|
+
this.db.exec(`DROP TABLE IF EXISTS ${t.fts}`);
|
|
311
|
+
this.db.exec(`CREATE VIRTUAL TABLE ${t.fts} USING fts5(${t.columns}, content='${t.source}', content_rowid='rowid', tokenize='trigram')`);
|
|
312
|
+
this.db.exec(`
|
|
313
|
+
CREATE TRIGGER ${t.triggers[0]} AFTER INSERT ON ${t.source} BEGIN
|
|
314
|
+
INSERT INTO ${t.fts}(rowid, ${t.columns}) VALUES (new.rowid, ${t.columns.split(", ").map(c => "new." + c).join(", ")});
|
|
315
|
+
END;
|
|
316
|
+
CREATE TRIGGER ${t.triggers[1]} AFTER DELETE ON ${t.source} BEGIN
|
|
317
|
+
INSERT INTO ${t.fts}(${t.fts}, rowid, ${t.columns}) VALUES ('delete', old.rowid, ${t.columns.split(", ").map(c => "old." + c).join(", ")});
|
|
318
|
+
END;
|
|
319
|
+
CREATE TRIGGER ${t.triggers[2]} AFTER UPDATE ON ${t.source} BEGIN
|
|
320
|
+
INSERT INTO ${t.fts}(${t.fts}, rowid, ${t.columns}) VALUES ('delete', old.rowid, ${t.columns.split(", ").map(c => "old." + c).join(", ")});
|
|
321
|
+
INSERT INTO ${t.fts}(rowid, ${t.columns}) VALUES (new.rowid, ${t.columns.split(", ").map(c => "new." + c).join(", ")});
|
|
322
|
+
END
|
|
323
|
+
`);
|
|
324
|
+
this.db.exec(`INSERT INTO ${t.fts}(rowid, ${t.columns}) SELECT rowid, ${t.columns} FROM ${t.source}`);
|
|
325
|
+
const cnt = (this.db.prepare(`SELECT COUNT(*) as c FROM ${t.fts}`).get() as { c: number }).c;
|
|
326
|
+
this.log.info(`Migrated ${t.fts} to trigram: ${cnt} rows indexed`);
|
|
327
|
+
} catch (err) {
|
|
328
|
+
this.log.warn(`Failed to migrate ${t.fts} to trigram: ${err}`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
276
333
|
private migrateTaskId(): void {
|
|
277
334
|
const cols = this.db.prepare("PRAGMA table_info(chunks)").all() as Array<{ name: string }>;
|
|
278
335
|
if (!cols.some((c) => c.name === "task_id")) {
|
|
@@ -516,12 +573,13 @@ export class SqliteStore {
|
|
|
516
573
|
).run(toolName, Math.round(durationMs), success ? 1 : 0, Date.now());
|
|
517
574
|
}
|
|
518
575
|
|
|
519
|
-
getToolMetrics(minutes: number): {
|
|
576
|
+
getToolMetrics(minutes: number, fromMs?: number, toMs?: number): {
|
|
520
577
|
tools: string[];
|
|
521
578
|
series: Array<{ minute: string; [tool: string]: number | string }>;
|
|
522
579
|
aggregated: Array<{ tool: string; totalCalls: number; avgMs: number; p95Ms: number; errorCount: number }>;
|
|
523
580
|
} {
|
|
524
|
-
const since = Date.now() - minutes * 60 * 1000;
|
|
581
|
+
const since = fromMs ?? (Date.now() - minutes * 60 * 1000);
|
|
582
|
+
const until = toMs ?? Date.now();
|
|
525
583
|
|
|
526
584
|
const rows = this.db.prepare(
|
|
527
585
|
`SELECT tool_name,
|
|
@@ -529,9 +587,9 @@ export class SqliteStore {
|
|
|
529
587
|
success,
|
|
530
588
|
strftime('%Y-%m-%d %H:%M', called_at/1000, 'unixepoch', 'localtime') as minute_key
|
|
531
589
|
FROM tool_calls
|
|
532
|
-
WHERE called_at >= ?
|
|
590
|
+
WHERE called_at >= ? AND called_at <= ?
|
|
533
591
|
ORDER BY called_at`,
|
|
534
|
-
).all(since) as Array<{ tool_name: string; duration_ms: number; success: number; minute_key: string }>;
|
|
592
|
+
).all(since, until) as Array<{ tool_name: string; duration_ms: number; success: number; minute_key: string }>;
|
|
535
593
|
|
|
536
594
|
const toolSet = new Set<string>();
|
|
537
595
|
const minuteMap = new Map<string, Map<string, { total: number; count: number }>>();
|
|
@@ -683,34 +741,27 @@ export class SqliteStore {
|
|
|
683
741
|
shared_at INTEGER NOT NULL
|
|
684
742
|
);
|
|
685
743
|
|
|
744
|
+
CREATE TABLE IF NOT EXISTS local_shared_memories (
|
|
745
|
+
chunk_id TEXT PRIMARY KEY REFERENCES chunks(id) ON DELETE CASCADE,
|
|
746
|
+
original_owner TEXT NOT NULL,
|
|
747
|
+
shared_at INTEGER NOT NULL
|
|
748
|
+
);
|
|
749
|
+
|
|
686
750
|
CREATE TABLE IF NOT EXISTS hub_users (
|
|
687
|
-
id
|
|
688
|
-
username
|
|
689
|
-
device_name
|
|
690
|
-
role
|
|
691
|
-
status
|
|
692
|
-
token_hash
|
|
693
|
-
created_at
|
|
694
|
-
approved_at
|
|
751
|
+
id TEXT PRIMARY KEY,
|
|
752
|
+
username TEXT NOT NULL UNIQUE,
|
|
753
|
+
device_name TEXT NOT NULL DEFAULT '',
|
|
754
|
+
role TEXT NOT NULL,
|
|
755
|
+
status TEXT NOT NULL,
|
|
756
|
+
token_hash TEXT NOT NULL DEFAULT '',
|
|
757
|
+
created_at INTEGER NOT NULL,
|
|
758
|
+
approved_at INTEGER,
|
|
759
|
+
last_ip TEXT NOT NULL DEFAULT '',
|
|
760
|
+
last_active_at INTEGER
|
|
695
761
|
);
|
|
696
762
|
CREATE INDEX IF NOT EXISTS idx_hub_users_status ON hub_users(status);
|
|
697
763
|
CREATE INDEX IF NOT EXISTS idx_hub_users_role ON hub_users(role);
|
|
698
764
|
|
|
699
|
-
CREATE TABLE IF NOT EXISTS hub_groups (
|
|
700
|
-
id TEXT PRIMARY KEY,
|
|
701
|
-
name TEXT NOT NULL UNIQUE,
|
|
702
|
-
description TEXT NOT NULL DEFAULT '',
|
|
703
|
-
created_at INTEGER NOT NULL
|
|
704
|
-
);
|
|
705
|
-
|
|
706
|
-
CREATE TABLE IF NOT EXISTS hub_group_members (
|
|
707
|
-
group_id TEXT NOT NULL REFERENCES hub_groups(id) ON DELETE CASCADE,
|
|
708
|
-
user_id TEXT NOT NULL REFERENCES hub_users(id) ON DELETE CASCADE,
|
|
709
|
-
joined_at INTEGER NOT NULL,
|
|
710
|
-
PRIMARY KEY (group_id, user_id)
|
|
711
|
-
);
|
|
712
|
-
CREATE INDEX IF NOT EXISTS idx_hub_group_members_user ON hub_group_members(user_id);
|
|
713
|
-
|
|
714
765
|
CREATE TABLE IF NOT EXISTS hub_tasks (
|
|
715
766
|
id TEXT PRIMARY KEY,
|
|
716
767
|
source_task_id TEXT NOT NULL,
|
|
@@ -752,7 +803,7 @@ export class SqliteStore {
|
|
|
752
803
|
content,
|
|
753
804
|
content='hub_chunks',
|
|
754
805
|
content_rowid='rowid',
|
|
755
|
-
tokenize='
|
|
806
|
+
tokenize='trigram'
|
|
756
807
|
);
|
|
757
808
|
|
|
758
809
|
CREATE TRIGGER IF NOT EXISTS hub_chunks_ai AFTER INSERT ON hub_chunks BEGIN
|
|
@@ -802,7 +853,7 @@ export class SqliteStore {
|
|
|
802
853
|
description,
|
|
803
854
|
content='hub_skills',
|
|
804
855
|
content_rowid='rowid',
|
|
805
|
-
tokenize='
|
|
856
|
+
tokenize='trigram'
|
|
806
857
|
);
|
|
807
858
|
|
|
808
859
|
CREATE TRIGGER IF NOT EXISTS hub_skills_ai AFTER INSERT ON hub_skills BEGIN
|
|
@@ -852,7 +903,7 @@ export class SqliteStore {
|
|
|
852
903
|
content,
|
|
853
904
|
content='hub_memories',
|
|
854
905
|
content_rowid='rowid',
|
|
855
|
-
tokenize='
|
|
906
|
+
tokenize='trigram'
|
|
856
907
|
);
|
|
857
908
|
|
|
858
909
|
CREATE TRIGGER IF NOT EXISTS hub_memories_ai AFTER INSERT ON hub_memories BEGIN
|
|
@@ -872,6 +923,32 @@ export class SqliteStore {
|
|
|
872
923
|
VALUES (new.rowid, new.summary, new.content);
|
|
873
924
|
END;
|
|
874
925
|
`);
|
|
926
|
+
|
|
927
|
+
this.db.exec(`
|
|
928
|
+
CREATE TABLE IF NOT EXISTS hub_notifications (
|
|
929
|
+
id TEXT PRIMARY KEY,
|
|
930
|
+
user_id TEXT NOT NULL,
|
|
931
|
+
type TEXT NOT NULL,
|
|
932
|
+
resource TEXT NOT NULL,
|
|
933
|
+
title TEXT NOT NULL,
|
|
934
|
+
message TEXT NOT NULL DEFAULT '',
|
|
935
|
+
read INTEGER NOT NULL DEFAULT 0,
|
|
936
|
+
created_at INTEGER NOT NULL
|
|
937
|
+
);
|
|
938
|
+
CREATE INDEX IF NOT EXISTS idx_hub_notif_user ON hub_notifications(user_id, read, created_at DESC);
|
|
939
|
+
`);
|
|
940
|
+
|
|
941
|
+
try {
|
|
942
|
+
const cols = this.db.prepare("PRAGMA table_info(hub_users)").all() as Array<{ name: string }>;
|
|
943
|
+
if (cols.length > 0 && !cols.some(c => c.name === "last_ip")) {
|
|
944
|
+
this.db.exec("ALTER TABLE hub_users ADD COLUMN last_ip TEXT NOT NULL DEFAULT ''");
|
|
945
|
+
this.log.info("Migrated: added last_ip column to hub_users");
|
|
946
|
+
}
|
|
947
|
+
if (cols.length > 0 && !cols.some(c => c.name === "last_active_at")) {
|
|
948
|
+
this.db.exec("ALTER TABLE hub_users ADD COLUMN last_active_at INTEGER");
|
|
949
|
+
this.log.info("Migrated: added last_active_at column to hub_users");
|
|
950
|
+
}
|
|
951
|
+
} catch { /* table may not exist yet */ }
|
|
875
952
|
}
|
|
876
953
|
|
|
877
954
|
// ─── Write ───
|
|
@@ -1047,6 +1124,25 @@ export class SqliteStore {
|
|
|
1047
1124
|
}
|
|
1048
1125
|
}
|
|
1049
1126
|
|
|
1127
|
+
hubMemoryPatternSearch(patterns: string[], opts: { limit?: number } = {}): Array<{ memoryId: string; content: string; role: string; createdAt: number }> {
|
|
1128
|
+
if (patterns.length === 0) return [];
|
|
1129
|
+
const limit = opts.limit ?? 10;
|
|
1130
|
+
const conditions = patterns.map(() => "(hm.content LIKE ? OR hm.summary LIKE ?)");
|
|
1131
|
+
const params: (string | number)[] = [];
|
|
1132
|
+
for (const p of patterns) { params.push(`%${p}%`, `%${p}%`); }
|
|
1133
|
+
params.push(limit);
|
|
1134
|
+
try {
|
|
1135
|
+
const rows = this.db.prepare(`
|
|
1136
|
+
SELECT hm.id as memory_id, hm.content, hm.role, hm.created_at
|
|
1137
|
+
FROM hub_memories hm
|
|
1138
|
+
WHERE ${conditions.join(" OR ")}
|
|
1139
|
+
ORDER BY hm.created_at DESC
|
|
1140
|
+
LIMIT ?
|
|
1141
|
+
`).all(...params) as Array<{ memory_id: string; content: string; role: string; created_at: number }>;
|
|
1142
|
+
return rows.map(r => ({ memoryId: r.memory_id, content: r.content, role: r.role, createdAt: r.created_at }));
|
|
1143
|
+
} catch { return []; }
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1050
1146
|
// ─── Vector Search ───
|
|
1051
1147
|
|
|
1052
1148
|
getAllEmbeddings(ownerFilter?: string[]): Array<{ chunkId: string; vector: number[] }> {
|
|
@@ -1213,6 +1309,8 @@ export class SqliteStore {
|
|
|
1213
1309
|
"skill_embeddings",
|
|
1214
1310
|
"skill_versions",
|
|
1215
1311
|
"skills",
|
|
1312
|
+
"local_shared_memories",
|
|
1313
|
+
"local_shared_tasks",
|
|
1216
1314
|
"embeddings",
|
|
1217
1315
|
"chunks",
|
|
1218
1316
|
"tasks",
|
|
@@ -1684,6 +1782,67 @@ export class SqliteStore {
|
|
|
1684
1782
|
return rows.map(r => ({ taskId: r.task_id, hubTaskId: r.hub_task_id, visibility: r.visibility, groupId: r.group_id, syncedChunks: r.synced_chunks }));
|
|
1685
1783
|
}
|
|
1686
1784
|
|
|
1785
|
+
// ─── Local Shared Memories (client-side tracking) ───
|
|
1786
|
+
|
|
1787
|
+
markMemorySharedLocally(chunkId: string): { ok: boolean; owner?: string; originalOwner?: string; sharedAt?: number; reason?: string } {
|
|
1788
|
+
const chunk = this.getChunk(chunkId);
|
|
1789
|
+
if (!chunk) return { ok: false, reason: "not_found" };
|
|
1790
|
+
if (chunk.owner === "public") {
|
|
1791
|
+
const existing = this.getLocalSharedMemory(chunkId);
|
|
1792
|
+
return {
|
|
1793
|
+
ok: true,
|
|
1794
|
+
owner: "public",
|
|
1795
|
+
originalOwner: existing?.originalOwner ?? undefined,
|
|
1796
|
+
sharedAt: existing?.sharedAt ?? undefined,
|
|
1797
|
+
};
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
const sharedAt = Date.now();
|
|
1801
|
+
this.db.transaction(() => {
|
|
1802
|
+
this.db.prepare(`
|
|
1803
|
+
INSERT INTO local_shared_memories (chunk_id, original_owner, shared_at)
|
|
1804
|
+
VALUES (?, ?, ?)
|
|
1805
|
+
ON CONFLICT(chunk_id) DO UPDATE SET
|
|
1806
|
+
original_owner = excluded.original_owner,
|
|
1807
|
+
shared_at = excluded.shared_at
|
|
1808
|
+
`).run(chunkId, chunk.owner, sharedAt);
|
|
1809
|
+
this.updateChunk(chunkId, { owner: "public" });
|
|
1810
|
+
})();
|
|
1811
|
+
|
|
1812
|
+
return { ok: true, owner: "public", originalOwner: chunk.owner, sharedAt };
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
unmarkMemorySharedLocally(chunkId: string, fallbackOwner?: string): { ok: boolean; owner?: string; originalOwner?: string; reason?: string } {
|
|
1816
|
+
const chunk = this.getChunk(chunkId);
|
|
1817
|
+
if (!chunk) return { ok: false, reason: "not_found" };
|
|
1818
|
+
if (chunk.owner !== "public") {
|
|
1819
|
+
return { ok: true, owner: chunk.owner };
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
const existing = this.getLocalSharedMemory(chunkId);
|
|
1823
|
+
const restoreOwner = existing?.originalOwner ?? fallbackOwner;
|
|
1824
|
+
if (!restoreOwner || restoreOwner === "public") {
|
|
1825
|
+
return { ok: false, reason: "original_owner_missing" };
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
this.db.transaction(() => {
|
|
1829
|
+
this.updateChunk(chunkId, { owner: restoreOwner });
|
|
1830
|
+
this.db.prepare("DELETE FROM local_shared_memories WHERE chunk_id = ?").run(chunkId);
|
|
1831
|
+
})();
|
|
1832
|
+
|
|
1833
|
+
return { ok: true, owner: restoreOwner, originalOwner: restoreOwner };
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
getLocalSharedMemory(chunkId: string): { chunkId: string; originalOwner: string; sharedAt: number } | null {
|
|
1837
|
+
const row = this.db.prepare("SELECT chunk_id, original_owner, shared_at FROM local_shared_memories WHERE chunk_id = ?").get(chunkId) as any;
|
|
1838
|
+
if (!row) return null;
|
|
1839
|
+
return {
|
|
1840
|
+
chunkId: row.chunk_id,
|
|
1841
|
+
originalOwner: row.original_owner,
|
|
1842
|
+
sharedAt: row.shared_at,
|
|
1843
|
+
};
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1687
1846
|
// ─── Hub Users / Groups ───
|
|
1688
1847
|
|
|
1689
1848
|
upsertHubUser(user: HubUserRecord): void {
|
|
@@ -1704,74 +1863,41 @@ export class SqliteStore {
|
|
|
1704
1863
|
getHubUser(userId: string): HubUserRecord | null {
|
|
1705
1864
|
const row = this.db.prepare('SELECT * FROM hub_users WHERE id = ?').get(userId) as HubUserRow | undefined;
|
|
1706
1865
|
if (!row) return null;
|
|
1707
|
-
return
|
|
1866
|
+
return rowToHubUser(row);
|
|
1708
1867
|
}
|
|
1709
1868
|
|
|
1710
1869
|
listHubUsers(status?: UserStatus): HubUserRecord[] {
|
|
1711
1870
|
const rows = status
|
|
1712
1871
|
? this.db.prepare('SELECT * FROM hub_users WHERE status = ? ORDER BY created_at').all(status) as HubUserRow[]
|
|
1713
1872
|
: this.db.prepare('SELECT * FROM hub_users ORDER BY created_at').all() as HubUserRow[];
|
|
1714
|
-
return rows.map(
|
|
1873
|
+
return rows.map(rowToHubUser);
|
|
1715
1874
|
}
|
|
1716
1875
|
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
}
|
|
1727
|
-
|
|
1728
|
-
listHubGroups(): HubGroupRecord[] {
|
|
1729
|
-
const rows = this.db.prepare('SELECT * FROM hub_groups ORDER BY name').all() as HubGroupRow[];
|
|
1730
|
-
return rows.map(rowToHubGroup);
|
|
1731
|
-
}
|
|
1732
|
-
|
|
1733
|
-
addHubGroupMember(groupId: string, userId: string, joinedAt = Date.now()): void {
|
|
1734
|
-
this.db.prepare(`
|
|
1735
|
-
INSERT INTO hub_group_members (group_id, user_id, joined_at)
|
|
1736
|
-
VALUES (?, ?, ?)
|
|
1737
|
-
ON CONFLICT(group_id, user_id) DO UPDATE SET joined_at = excluded.joined_at
|
|
1738
|
-
`).run(groupId, userId, joinedAt);
|
|
1739
|
-
}
|
|
1740
|
-
|
|
1741
|
-
getHubGroupById(groupId: string): HubGroupRecord | undefined {
|
|
1742
|
-
const row = this.db.prepare('SELECT * FROM hub_groups WHERE id = ?').get(groupId) as HubGroupRow | undefined;
|
|
1743
|
-
return row ? rowToHubGroup(row) : undefined;
|
|
1744
|
-
}
|
|
1745
|
-
|
|
1746
|
-
deleteHubGroup(groupId: string): boolean {
|
|
1747
|
-
const result = this.db.prepare('DELETE FROM hub_groups WHERE id = ?').run(groupId);
|
|
1876
|
+
deleteHubUser(userId: string, cleanResources = false): boolean {
|
|
1877
|
+
if (cleanResources) {
|
|
1878
|
+
this.db.prepare('DELETE FROM hub_tasks WHERE source_user_id = ?').run(userId);
|
|
1879
|
+
this.db.prepare('DELETE FROM hub_skills WHERE source_user_id = ?').run(userId);
|
|
1880
|
+
this.db.prepare('DELETE FROM hub_memories WHERE source_user_id = ?').run(userId);
|
|
1881
|
+
const result = this.db.prepare('DELETE FROM hub_users WHERE id = ?').run(userId);
|
|
1882
|
+
return result.changes > 0;
|
|
1883
|
+
}
|
|
1884
|
+
const result = this.db.prepare("UPDATE hub_users SET status = 'removed', token_hash = '' WHERE id = ?").run(userId);
|
|
1748
1885
|
return result.changes > 0;
|
|
1749
1886
|
}
|
|
1750
1887
|
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
SELECT gm.user_id, hu.username, gm.joined_at
|
|
1754
|
-
FROM hub_group_members gm
|
|
1755
|
-
JOIN hub_users hu ON hu.id = gm.user_id
|
|
1756
|
-
WHERE gm.group_id = ?
|
|
1757
|
-
ORDER BY gm.joined_at
|
|
1758
|
-
`).all(groupId) as Array<{ user_id: string; username: string; joined_at: number }>;
|
|
1759
|
-
return rows.map(r => ({ userId: r.user_id, username: r.username, joinedAt: r.joined_at }));
|
|
1760
|
-
}
|
|
1761
|
-
|
|
1762
|
-
removeHubGroupMember(groupId: string, userId: string): void {
|
|
1763
|
-
this.db.prepare('DELETE FROM hub_group_members WHERE group_id = ? AND user_id = ?').run(groupId, userId);
|
|
1888
|
+
updateHubUserActivity(userId: string, ip: string, timestamp?: number): void {
|
|
1889
|
+
this.db.prepare('UPDATE hub_users SET last_ip = ?, last_active_at = ? WHERE id = ?').run(ip, timestamp ?? Date.now(), userId);
|
|
1764
1890
|
}
|
|
1765
1891
|
|
|
1766
|
-
|
|
1767
|
-
const
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
return
|
|
1892
|
+
getHubUserContributions(): Record<string, { memoryCount: number; taskCount: number; skillCount: number }> {
|
|
1893
|
+
const result: Record<string, { memoryCount: number; taskCount: number; skillCount: number }> = {};
|
|
1894
|
+
const memRows = this.db.prepare('SELECT source_user_id, COUNT(*) as cnt FROM hub_memories GROUP BY source_user_id').all() as Array<{ source_user_id: string; cnt: number }>;
|
|
1895
|
+
const taskRows = this.db.prepare('SELECT source_user_id, COUNT(*) as cnt FROM hub_tasks GROUP BY source_user_id').all() as Array<{ source_user_id: string; cnt: number }>;
|
|
1896
|
+
const skillRows = this.db.prepare('SELECT source_user_id, COUNT(*) as cnt FROM hub_skills GROUP BY source_user_id').all() as Array<{ source_user_id: string; cnt: number }>;
|
|
1897
|
+
for (const r of memRows) { if (!result[r.source_user_id]) result[r.source_user_id] = { memoryCount: 0, taskCount: 0, skillCount: 0 }; result[r.source_user_id].memoryCount = r.cnt; }
|
|
1898
|
+
for (const r of taskRows) { if (!result[r.source_user_id]) result[r.source_user_id] = { memoryCount: 0, taskCount: 0, skillCount: 0 }; result[r.source_user_id].taskCount = r.cnt; }
|
|
1899
|
+
for (const r of skillRows) { if (!result[r.source_user_id]) result[r.source_user_id] = { memoryCount: 0, taskCount: 0, skillCount: 0 }; result[r.source_user_id].skillCount = r.cnt; }
|
|
1900
|
+
return result;
|
|
1775
1901
|
}
|
|
1776
1902
|
|
|
1777
1903
|
// ─── Hub Shared Data ───
|
|
@@ -1795,6 +1921,11 @@ export class SqliteStore {
|
|
|
1795
1921
|
return row ? rowToHubTask(row) : null;
|
|
1796
1922
|
}
|
|
1797
1923
|
|
|
1924
|
+
getHubTaskById(taskId: string): HubTaskRecord | null {
|
|
1925
|
+
const row = this.db.prepare('SELECT * FROM hub_tasks WHERE id = ?').get(taskId) as HubTaskRow | undefined;
|
|
1926
|
+
return row ? rowToHubTask(row) : null;
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1798
1929
|
upsertHubChunk(chunk: HubChunkUpsertInput): void {
|
|
1799
1930
|
if (!chunk.sourceTaskId) throw new Error("sourceTaskId is required for hub chunk upserts");
|
|
1800
1931
|
const taskId = this.resolveCanonicalHubTaskId(chunk.hubTaskId, chunk.sourceUserId, chunk.sourceTaskId);
|
|
@@ -1940,7 +2071,7 @@ export class SqliteStore {
|
|
|
1940
2071
|
let rows: HubSkillSearchRow[];
|
|
1941
2072
|
if (sanitized) {
|
|
1942
2073
|
rows = this.db.prepare(`
|
|
1943
|
-
SELECT hs.id, hs.name, hs.description, hs.version, hs.visibility, '' AS group_name, hu.username AS owner_name, hs.quality_score,
|
|
2074
|
+
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,
|
|
1944
2075
|
bm25(hub_skills_fts) as rank
|
|
1945
2076
|
FROM hub_skills_fts f
|
|
1946
2077
|
JOIN hub_skills hs ON hs.rowid = f.rowid
|
|
@@ -1951,7 +2082,7 @@ export class SqliteStore {
|
|
|
1951
2082
|
`).all(sanitized, limit) as HubSkillSearchRow[];
|
|
1952
2083
|
} else {
|
|
1953
2084
|
rows = this.db.prepare(`
|
|
1954
|
-
SELECT hs.id, hs.name, hs.description, hs.version, hs.visibility, '' AS group_name, hu.username AS owner_name, hs.quality_score,
|
|
2085
|
+
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,
|
|
1955
2086
|
0 as rank
|
|
1956
2087
|
FROM hub_skills hs
|
|
1957
2088
|
LEFT JOIN hub_users hu ON hu.id = hs.source_user_id
|
|
@@ -1966,9 +2097,9 @@ export class SqliteStore {
|
|
|
1966
2097
|
this.db.prepare('DELETE FROM hub_skills WHERE source_user_id = ? AND source_skill_id = ?').run(sourceUserId, sourceSkillId);
|
|
1967
2098
|
}
|
|
1968
2099
|
|
|
1969
|
-
listVisibleHubTasks(userId: string, limit = 40): Array<{ id: string; sourceTaskId: string; sourceUserId: string; title: string; summary: string; groupId: string | null; groupName: string | null; visibility: string; ownerName: string; chunkCount: number; createdAt: number; updatedAt: number }> {
|
|
2100
|
+
listVisibleHubTasks(userId: string, limit = 40): Array<{ id: string; sourceTaskId: string; sourceUserId: string; title: string; summary: string; groupId: string | null; groupName: string | null; visibility: string; ownerName: string; ownerStatus: string; chunkCount: number; createdAt: number; updatedAt: number }> {
|
|
1970
2101
|
const rows = this.db.prepare(`
|
|
1971
|
-
SELECT t.*, u.username AS owner_name, NULL AS group_name,
|
|
2102
|
+
SELECT t.*, u.username AS owner_name, u.status AS owner_status, NULL AS group_name,
|
|
1972
2103
|
(SELECT COUNT(*) FROM hub_chunks c WHERE c.hub_task_id = t.id) AS chunk_count
|
|
1973
2104
|
FROM hub_tasks t
|
|
1974
2105
|
LEFT JOIN hub_users u ON u.id = t.source_user_id
|
|
@@ -1978,36 +2109,40 @@ export class SqliteStore {
|
|
|
1978
2109
|
return rows.map(r => ({
|
|
1979
2110
|
id: r.id, sourceTaskId: r.source_task_id, sourceUserId: r.source_user_id,
|
|
1980
2111
|
title: r.title, summary: r.summary, groupId: r.group_id, groupName: r.group_name ?? null,
|
|
1981
|
-
visibility: r.visibility, ownerName: r.owner_name ?? "unknown", chunkCount: r.chunk_count ?? 0,
|
|
2112
|
+
visibility: r.visibility, ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", chunkCount: r.chunk_count ?? 0,
|
|
1982
2113
|
createdAt: r.created_at, updatedAt: r.updated_at,
|
|
1983
2114
|
}));
|
|
1984
2115
|
}
|
|
1985
2116
|
|
|
1986
|
-
listAllHubTasks(): Array<{ id: string; sourceTaskId: string; sourceUserId: string; title: string; summary: string; groupId: string | null; groupName: string | null; visibility: string; ownerName: string; chunkCount: number; createdAt: number; updatedAt: number }> {
|
|
2117
|
+
listAllHubTasks(): Array<{ id: string; sourceTaskId: string; sourceUserId: string; title: string; summary: string; groupId: string | null; groupName: string | null; visibility: string; ownerName: string; ownerStatus: string; chunkCount: number; createdAt: number; updatedAt: number }> {
|
|
1987
2118
|
const rows = this.db.prepare(`
|
|
1988
|
-
SELECT t.*, u.username AS owner_name,
|
|
2119
|
+
SELECT t.*, u.username AS owner_name, u.status AS owner_status,
|
|
1989
2120
|
(SELECT COUNT(*) FROM hub_chunks c WHERE c.hub_task_id = t.id) AS chunk_count
|
|
1990
2121
|
FROM hub_tasks t
|
|
1991
2122
|
LEFT JOIN hub_users u ON u.id = t.source_user_id
|
|
1992
|
-
LEFT JOIN hub_groups g ON g.id = t.group_id
|
|
1993
2123
|
ORDER BY t.updated_at DESC
|
|
1994
2124
|
`).all() as any[];
|
|
1995
2125
|
return rows.map(r => ({
|
|
1996
2126
|
id: r.id, sourceTaskId: r.source_task_id, sourceUserId: r.source_user_id,
|
|
1997
|
-
title: r.title, summary: r.summary, groupId: r.group_id, groupName:
|
|
1998
|
-
visibility: r.visibility, ownerName: r.owner_name ?? "unknown", chunkCount: r.chunk_count ?? 0,
|
|
2127
|
+
title: r.title, summary: r.summary, groupId: r.group_id, groupName: null as string | null,
|
|
2128
|
+
visibility: r.visibility, ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", chunkCount: r.chunk_count ?? 0,
|
|
1999
2129
|
createdAt: r.created_at, updatedAt: r.updated_at,
|
|
2000
2130
|
}));
|
|
2001
2131
|
}
|
|
2002
2132
|
|
|
2133
|
+
listHubChunksByTaskId(hubTaskId: string): HubChunkRecord[] {
|
|
2134
|
+
const rows = this.db.prepare('SELECT * FROM hub_chunks WHERE hub_task_id = ? ORDER BY created_at ASC').all(hubTaskId) as HubChunkRow[];
|
|
2135
|
+
return rows.map(rowToHubChunk);
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2003
2138
|
deleteHubTaskById(taskId: string): boolean {
|
|
2004
2139
|
const info = this.db.prepare('DELETE FROM hub_tasks WHERE id = ?').run(taskId);
|
|
2005
2140
|
return info.changes > 0;
|
|
2006
2141
|
}
|
|
2007
2142
|
|
|
2008
|
-
listVisibleHubSkills(userId: string, limit = 40): Array<{ id: string; sourceSkillId: string; sourceUserId: string; name: string; description: string; version: number; groupId: string | null; groupName: string | null; visibility: string; ownerName: string; qualityScore: number | null; createdAt: number; updatedAt: number }> {
|
|
2143
|
+
listVisibleHubSkills(userId: string, limit = 40): Array<{ id: string; sourceSkillId: string; sourceUserId: string; name: string; description: string; version: number; groupId: string | null; groupName: string | null; visibility: string; ownerName: string; ownerStatus: string; qualityScore: number | null; createdAt: number; updatedAt: number }> {
|
|
2009
2144
|
const rows = this.db.prepare(`
|
|
2010
|
-
SELECT s.*, u.username AS owner_name, NULL AS group_name
|
|
2145
|
+
SELECT s.*, u.username AS owner_name, u.status AS owner_status, NULL AS group_name
|
|
2011
2146
|
FROM hub_skills s
|
|
2012
2147
|
LEFT JOIN hub_users u ON u.id = s.source_user_id
|
|
2013
2148
|
ORDER BY s.updated_at DESC
|
|
@@ -2017,24 +2152,23 @@ export class SqliteStore {
|
|
|
2017
2152
|
id: r.id, sourceSkillId: r.source_skill_id, sourceUserId: r.source_user_id,
|
|
2018
2153
|
name: r.name, description: r.description, version: r.version,
|
|
2019
2154
|
groupId: r.group_id, groupName: r.group_name ?? null, visibility: r.visibility,
|
|
2020
|
-
ownerName: r.owner_name ?? "unknown", qualityScore: r.quality_score,
|
|
2155
|
+
ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", qualityScore: r.quality_score,
|
|
2021
2156
|
createdAt: r.created_at, updatedAt: r.updated_at,
|
|
2022
2157
|
}));
|
|
2023
2158
|
}
|
|
2024
2159
|
|
|
2025
|
-
listAllHubSkills(): Array<{ id: string; sourceSkillId: string; sourceUserId: string; name: string; description: string; version: number; groupId: string | null; groupName: string | null; visibility: string; ownerName: string; qualityScore: number | null; createdAt: number; updatedAt: number }> {
|
|
2160
|
+
listAllHubSkills(): Array<{ id: string; sourceSkillId: string; sourceUserId: string; name: string; description: string; version: number; groupId: string | null; groupName: string | null; visibility: string; ownerName: string; ownerStatus: string; qualityScore: number | null; createdAt: number; updatedAt: number }> {
|
|
2026
2161
|
const rows = this.db.prepare(`
|
|
2027
|
-
SELECT s.*, u.username AS owner_name,
|
|
2162
|
+
SELECT s.*, u.username AS owner_name, u.status AS owner_status
|
|
2028
2163
|
FROM hub_skills s
|
|
2029
2164
|
LEFT JOIN hub_users u ON u.id = s.source_user_id
|
|
2030
|
-
LEFT JOIN hub_groups g ON g.id = s.group_id
|
|
2031
2165
|
ORDER BY s.updated_at DESC
|
|
2032
2166
|
`).all() as any[];
|
|
2033
2167
|
return rows.map(r => ({
|
|
2034
2168
|
id: r.id, sourceSkillId: r.source_skill_id, sourceUserId: r.source_user_id,
|
|
2035
2169
|
name: r.name, description: r.description, version: r.version,
|
|
2036
|
-
groupId: r.group_id, groupName:
|
|
2037
|
-
ownerName: r.owner_name ?? "unknown", qualityScore: r.quality_score,
|
|
2170
|
+
groupId: r.group_id, groupName: null as string | null, visibility: r.visibility,
|
|
2171
|
+
ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", qualityScore: r.quality_score,
|
|
2038
2172
|
createdAt: r.created_at, updatedAt: r.updated_at,
|
|
2039
2173
|
}));
|
|
2040
2174
|
}
|
|
@@ -2081,6 +2215,47 @@ export class SqliteStore {
|
|
|
2081
2215
|
return info.changes > 0;
|
|
2082
2216
|
}
|
|
2083
2217
|
|
|
2218
|
+
// ─── Hub Notifications ───
|
|
2219
|
+
|
|
2220
|
+
insertHubNotification(n: { id: string; userId: string; type: string; resource: string; title: string; message?: string }): void {
|
|
2221
|
+
this.db.prepare(
|
|
2222
|
+
'INSERT INTO hub_notifications (id, user_id, type, resource, title, message, read, created_at) VALUES (?, ?, ?, ?, ?, ?, 0, ?)'
|
|
2223
|
+
).run(n.id, n.userId, n.type, n.resource, n.title, n.message ?? '', Date.now());
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
hasRecentHubNotification(userId: string, type: string, resource: string, windowMs: number = 300_000): boolean {
|
|
2227
|
+
const since = Date.now() - windowMs;
|
|
2228
|
+
const row = this.db.prepare(
|
|
2229
|
+
'SELECT COUNT(*) AS cnt FROM hub_notifications WHERE user_id = ? AND type = ? AND resource = ? AND created_at > ?'
|
|
2230
|
+
).get(userId, type, resource, since) as { cnt: number };
|
|
2231
|
+
return row.cnt > 0;
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
listHubNotifications(userId: string, opts?: { unreadOnly?: boolean; limit?: number }): Array<{ id: string; userId: string; type: string; resource: string; title: string; message: string; read: boolean; createdAt: number }> {
|
|
2235
|
+
const where = opts?.unreadOnly ? 'WHERE user_id = ? AND read = 0' : 'WHERE user_id = ?';
|
|
2236
|
+
const limit = opts?.limit ?? 50;
|
|
2237
|
+
const rows = this.db.prepare(`SELECT * FROM hub_notifications ${where} ORDER BY created_at DESC LIMIT ?`).all(userId, limit) as any[];
|
|
2238
|
+
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 }));
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
countUnreadHubNotifications(userId: string): number {
|
|
2242
|
+
const row = this.db.prepare('SELECT COUNT(*) AS cnt FROM hub_notifications WHERE user_id = ? AND read = 0').get(userId) as { cnt: number };
|
|
2243
|
+
return row.cnt;
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
markHubNotificationsRead(userId: string, ids?: string[]): void {
|
|
2247
|
+
if (ids && ids.length > 0) {
|
|
2248
|
+
const placeholders = ids.map(() => '?').join(',');
|
|
2249
|
+
this.db.prepare(`UPDATE hub_notifications SET read = 1 WHERE user_id = ? AND id IN (${placeholders})`).run(userId, ...ids);
|
|
2250
|
+
} else {
|
|
2251
|
+
this.db.prepare('UPDATE hub_notifications SET read = 1 WHERE user_id = ?').run(userId);
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
clearHubNotifications(userId: string): void {
|
|
2256
|
+
this.db.prepare('DELETE FROM hub_notifications WHERE user_id = ?').run(userId);
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2084
2259
|
upsertHubMemoryEmbedding(memoryId: string, vector: Float32Array): void {
|
|
2085
2260
|
const buf = Buffer.from(vector.buffer, vector.byteOffset, vector.byteLength);
|
|
2086
2261
|
this.db.prepare(`
|
|
@@ -2138,9 +2313,9 @@ export class SqliteStore {
|
|
|
2138
2313
|
return row ?? null;
|
|
2139
2314
|
}
|
|
2140
2315
|
|
|
2141
|
-
listVisibleHubMemories(userId: string, limit = 40): Array<{ id: string; sourceChunkId: string; sourceUserId: string; role: string; summary: string; kind: string; groupId: string | null; groupName: string | null; visibility: string; ownerName: string; createdAt: number; updatedAt: number }> {
|
|
2316
|
+
listVisibleHubMemories(userId: string, limit = 40): Array<{ id: string; sourceChunkId: string; sourceUserId: string; role: string; content: string; summary: string; kind: string; groupId: string | null; groupName: string | null; visibility: string; ownerName: string; ownerStatus: string; createdAt: number; updatedAt: number }> {
|
|
2142
2317
|
const rows = this.db.prepare(`
|
|
2143
|
-
SELECT m.*, u.username AS owner_name, NULL AS group_name
|
|
2318
|
+
SELECT m.*, u.username AS owner_name, u.status AS owner_status, NULL AS group_name
|
|
2144
2319
|
FROM hub_memories m
|
|
2145
2320
|
LEFT JOIN hub_users u ON u.id = m.source_user_id
|
|
2146
2321
|
ORDER BY m.updated_at DESC
|
|
@@ -2148,25 +2323,24 @@ export class SqliteStore {
|
|
|
2148
2323
|
`).all(limit) as any[];
|
|
2149
2324
|
return rows.map(r => ({
|
|
2150
2325
|
id: r.id, sourceChunkId: r.source_chunk_id, sourceUserId: r.source_user_id,
|
|
2151
|
-
role: r.role, summary: r.summary, kind: r.kind,
|
|
2326
|
+
role: r.role, content: r.content ?? "", summary: r.summary, kind: r.kind,
|
|
2152
2327
|
groupId: r.group_id, groupName: r.group_name ?? null, visibility: r.visibility,
|
|
2153
|
-
ownerName: r.owner_name ?? "unknown", createdAt: r.created_at, updatedAt: r.updated_at,
|
|
2328
|
+
ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", createdAt: r.created_at, updatedAt: r.updated_at,
|
|
2154
2329
|
}));
|
|
2155
2330
|
}
|
|
2156
2331
|
|
|
2157
|
-
listAllHubMemories(): Array<{ id: string; sourceChunkId: string; sourceUserId: string; role: string; summary: string; kind: string; groupId: string | null; groupName: string | null; visibility: string; ownerName: string; createdAt: number; updatedAt: number }> {
|
|
2332
|
+
listAllHubMemories(): Array<{ id: string; sourceChunkId: string; sourceUserId: string; role: string; content: string; summary: string; kind: string; groupId: string | null; groupName: string | null; visibility: string; ownerName: string; ownerStatus: string; createdAt: number; updatedAt: number }> {
|
|
2158
2333
|
const rows = this.db.prepare(`
|
|
2159
|
-
SELECT m.*, u.username AS owner_name,
|
|
2334
|
+
SELECT m.*, u.username AS owner_name, u.status AS owner_status
|
|
2160
2335
|
FROM hub_memories m
|
|
2161
2336
|
LEFT JOIN hub_users u ON u.id = m.source_user_id
|
|
2162
|
-
LEFT JOIN hub_groups g ON g.id = m.group_id
|
|
2163
2337
|
ORDER BY m.updated_at DESC
|
|
2164
2338
|
`).all() as any[];
|
|
2165
2339
|
return rows.map(r => ({
|
|
2166
2340
|
id: r.id, sourceChunkId: r.source_chunk_id, sourceUserId: r.source_user_id,
|
|
2167
|
-
role: r.role, summary: r.summary, kind: r.kind,
|
|
2168
|
-
groupId: r.group_id, groupName:
|
|
2169
|
-
ownerName: r.owner_name ?? "unknown", createdAt: r.created_at, updatedAt: r.updated_at,
|
|
2341
|
+
role: r.role, content: r.content ?? "", summary: r.summary, kind: r.kind,
|
|
2342
|
+
groupId: r.group_id, groupName: null as string | null, visibility: r.visibility,
|
|
2343
|
+
ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", createdAt: r.created_at, updatedAt: r.updated_at,
|
|
2170
2344
|
}));
|
|
2171
2345
|
}
|
|
2172
2346
|
|
|
@@ -2190,13 +2364,6 @@ export class SqliteStore {
|
|
|
2190
2364
|
throw new Error(`source skill not found for skillId=${skillId}`);
|
|
2191
2365
|
}
|
|
2192
2366
|
|
|
2193
|
-
private attachGroupsToHubUser(user: HubUserRecord): HubUserRecord {
|
|
2194
|
-
return {
|
|
2195
|
-
...user,
|
|
2196
|
-
groups: this.getGroupsForHubUser(user.id),
|
|
2197
|
-
};
|
|
2198
|
-
}
|
|
2199
|
-
|
|
2200
2367
|
getSessionOwnerMap(sessionKeys: string[]): Map<string, string> {
|
|
2201
2368
|
const result = new Map<string, string>();
|
|
2202
2369
|
if (sessionKeys.length === 0) return result;
|
|
@@ -2408,6 +2575,8 @@ interface HubUserRecord extends UserInfo {
|
|
|
2408
2575
|
tokenHash: string;
|
|
2409
2576
|
createdAt: number;
|
|
2410
2577
|
approvedAt: number | null;
|
|
2578
|
+
lastIp: string;
|
|
2579
|
+
lastActiveAt: number | null;
|
|
2411
2580
|
}
|
|
2412
2581
|
|
|
2413
2582
|
interface HubUserRow {
|
|
@@ -2419,6 +2588,8 @@ interface HubUserRow {
|
|
|
2419
2588
|
token_hash: string;
|
|
2420
2589
|
created_at: number;
|
|
2421
2590
|
approved_at: number | null;
|
|
2591
|
+
last_ip: string;
|
|
2592
|
+
last_active_at: number | null;
|
|
2422
2593
|
}
|
|
2423
2594
|
|
|
2424
2595
|
function rowToHubUser(row: HubUserRow): HubUserRecord {
|
|
@@ -2432,29 +2603,8 @@ function rowToHubUser(row: HubUserRow): HubUserRecord {
|
|
|
2432
2603
|
tokenHash: row.token_hash,
|
|
2433
2604
|
createdAt: row.created_at,
|
|
2434
2605
|
approvedAt: row.approved_at,
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
interface HubGroupRecord {
|
|
2439
|
-
id: string;
|
|
2440
|
-
name: string;
|
|
2441
|
-
description: string;
|
|
2442
|
-
createdAt: number;
|
|
2443
|
-
}
|
|
2444
|
-
|
|
2445
|
-
interface HubGroupRow {
|
|
2446
|
-
id: string;
|
|
2447
|
-
name: string;
|
|
2448
|
-
description: string;
|
|
2449
|
-
created_at: number;
|
|
2450
|
-
}
|
|
2451
|
-
|
|
2452
|
-
function rowToHubGroup(row: HubGroupRow): HubGroupRecord {
|
|
2453
|
-
return {
|
|
2454
|
-
id: row.id,
|
|
2455
|
-
name: row.name,
|
|
2456
|
-
description: row.description,
|
|
2457
|
-
createdAt: row.created_at,
|
|
2606
|
+
lastIp: row.last_ip || "",
|
|
2607
|
+
lastActiveAt: row.last_active_at ?? null,
|
|
2458
2608
|
};
|
|
2459
2609
|
}
|
|
2460
2610
|
|
|
@@ -2603,6 +2753,7 @@ interface HubSkillSearchRow {
|
|
|
2603
2753
|
visibility: string;
|
|
2604
2754
|
group_name: string | null;
|
|
2605
2755
|
owner_name: string | null;
|
|
2756
|
+
owner_status: string | null;
|
|
2606
2757
|
quality_score: number | null;
|
|
2607
2758
|
}
|
|
2608
2759
|
|