@memtensor/memos-local-openclaw-plugin 1.0.4-beta.9 → 1.0.4
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 +94 -27
- package/dist/capture/index.js +3 -1
- package/dist/capture/index.js.map +1 -1
- package/dist/client/connector.d.ts +5 -0
- package/dist/client/connector.d.ts.map +1 -1
- package/dist/client/connector.js +89 -8
- package/dist/client/connector.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -1
- package/dist/config.js.map +1 -1
- package/dist/hub/server.d.ts +2 -0
- package/dist/hub/server.d.ts.map +1 -1
- package/dist/hub/server.js +240 -35
- package/dist/hub/server.js.map +1 -1
- package/dist/hub/user-manager.d.ts +9 -0
- package/dist/hub/user-manager.d.ts.map +1 -1
- package/dist/hub/user-manager.js +26 -2
- package/dist/hub/user-manager.js.map +1 -1
- package/dist/ingest/chunker.d.ts +2 -1
- package/dist/ingest/chunker.d.ts.map +1 -1
- package/dist/ingest/chunker.js +14 -10
- package/dist/ingest/chunker.js.map +1 -1
- package/dist/ingest/providers/index.js +2 -2
- package/dist/ingest/providers/index.js.map +1 -1
- package/dist/recall/engine.d.ts.map +1 -1
- package/dist/recall/engine.js +22 -4
- package/dist/recall/engine.js.map +1 -1
- package/dist/shared/llm-call.d.ts.map +1 -1
- package/dist/shared/llm-call.js +2 -1
- 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 +56 -5
- package/dist/skill/evolver.js.map +1 -1
- package/dist/skill/generator.d.ts +2 -0
- package/dist/skill/generator.d.ts.map +1 -1
- package/dist/skill/generator.js +45 -3
- package/dist/skill/generator.js.map +1 -1
- package/dist/skill/installer.d.ts +26 -0
- package/dist/skill/installer.d.ts.map +1 -1
- package/dist/skill/installer.js +80 -4
- package/dist/skill/installer.js.map +1 -1
- package/dist/skill/upgrader.d.ts +2 -0
- package/dist/skill/upgrader.d.ts.map +1 -1
- package/dist/skill/upgrader.js +139 -1
- package/dist/skill/upgrader.js.map +1 -1
- package/dist/skill/validator.d.ts +3 -0
- package/dist/skill/validator.d.ts.map +1 -1
- package/dist/skill/validator.js +75 -0
- package/dist/skill/validator.js.map +1 -1
- package/dist/storage/sqlite.d.ts +57 -0
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +290 -35
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +27 -8
- package/dist/telemetry.js.map +1 -1
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -1
- package/dist/viewer/html.d.ts.map +1 -1
- package/dist/viewer/html.js +564 -225
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts +9 -0
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +357 -108
- package/dist/viewer/server.js.map +1 -1
- package/index.ts +411 -52
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -1
- package/prebuilds/darwin-arm64/better_sqlite3.node +0 -0
- package/prebuilds/darwin-x64/better_sqlite3.node +0 -0
- package/prebuilds/linux-x64/better_sqlite3.node +0 -0
- package/prebuilds/win32-x64/better_sqlite3.node +0 -0
- package/src/capture/index.ts +4 -1
- package/src/client/connector.ts +92 -8
- package/src/config.ts +2 -1
- package/src/hub/server.ts +235 -35
- package/src/hub/user-manager.ts +42 -6
- package/src/ingest/chunker.ts +19 -13
- package/src/ingest/providers/index.ts +2 -2
- package/src/recall/engine.ts +20 -4
- package/src/shared/llm-call.ts +2 -1
- package/src/sharing/types.ts +1 -1
- package/src/skill/evolver.ts +58 -6
- package/src/skill/generator.ts +44 -5
- package/src/skill/installer.ts +107 -4
- package/src/skill/upgrader.ts +139 -1
- package/src/skill/validator.ts +79 -0
- package/src/storage/sqlite.ts +318 -40
- package/src/telemetry.ts +27 -9
- package/src/types.ts +11 -0
- package/src/viewer/html.ts +564 -225
- package/src/viewer/server.ts +333 -105
- package/telemetry.credentials.json +5 -0
package/src/storage/sqlite.ts
CHANGED
|
@@ -112,7 +112,10 @@ export class SqliteStore {
|
|
|
112
112
|
this.migrateSkillEmbeddingsAndFts();
|
|
113
113
|
this.migrateFtsToTrigram();
|
|
114
114
|
this.migrateHubTables();
|
|
115
|
+
this.migrateHubFtsToTrigram();
|
|
115
116
|
this.migrateLocalSharedTasksOwner();
|
|
117
|
+
this.migrateHubUserIdentityFields();
|
|
118
|
+
this.migrateClientHubConnectionIdentityFields();
|
|
116
119
|
this.log.debug("Database schema initialized");
|
|
117
120
|
}
|
|
118
121
|
|
|
@@ -130,6 +133,49 @@ export class SqliteStore {
|
|
|
130
133
|
} catch { /* table may not exist yet */ }
|
|
131
134
|
}
|
|
132
135
|
|
|
136
|
+
private migrateHubUserIdentityFields(): void {
|
|
137
|
+
try {
|
|
138
|
+
const cols = this.db.prepare("PRAGMA table_info(hub_users)").all() as Array<{ name: string }>;
|
|
139
|
+
if (cols.length === 0) return;
|
|
140
|
+
if (!cols.some(c => c.name === "identity_key")) {
|
|
141
|
+
this.db.exec("ALTER TABLE hub_users ADD COLUMN identity_key TEXT NOT NULL DEFAULT ''");
|
|
142
|
+
this.db.exec("CREATE INDEX IF NOT EXISTS idx_hub_users_identity_key ON hub_users(identity_key)");
|
|
143
|
+
this.log.info("Migrated: added identity_key to hub_users");
|
|
144
|
+
}
|
|
145
|
+
if (!cols.some(c => c.name === "left_at")) {
|
|
146
|
+
this.db.exec("ALTER TABLE hub_users ADD COLUMN left_at INTEGER");
|
|
147
|
+
this.log.info("Migrated: added left_at to hub_users");
|
|
148
|
+
}
|
|
149
|
+
if (!cols.some(c => c.name === "removed_at")) {
|
|
150
|
+
this.db.exec("ALTER TABLE hub_users ADD COLUMN removed_at INTEGER");
|
|
151
|
+
this.log.info("Migrated: added removed_at to hub_users");
|
|
152
|
+
}
|
|
153
|
+
if (!cols.some(c => c.name === "rejected_at")) {
|
|
154
|
+
this.db.exec("ALTER TABLE hub_users ADD COLUMN rejected_at INTEGER");
|
|
155
|
+
this.log.info("Migrated: added rejected_at to hub_users");
|
|
156
|
+
}
|
|
157
|
+
if (!cols.some(c => c.name === "rejoin_requested_at")) {
|
|
158
|
+
this.db.exec("ALTER TABLE hub_users ADD COLUMN rejoin_requested_at INTEGER");
|
|
159
|
+
this.log.info("Migrated: added rejoin_requested_at to hub_users");
|
|
160
|
+
}
|
|
161
|
+
} catch { /* table may not exist yet */ }
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private migrateClientHubConnectionIdentityFields(): void {
|
|
165
|
+
try {
|
|
166
|
+
const cols = this.db.prepare("PRAGMA table_info(client_hub_connection)").all() as Array<{ name: string }>;
|
|
167
|
+
if (cols.length === 0) return;
|
|
168
|
+
if (!cols.some(c => c.name === "identity_key")) {
|
|
169
|
+
this.db.exec("ALTER TABLE client_hub_connection ADD COLUMN identity_key TEXT NOT NULL DEFAULT ''");
|
|
170
|
+
this.log.info("Migrated: added identity_key to client_hub_connection");
|
|
171
|
+
}
|
|
172
|
+
if (!cols.some(c => c.name === "last_known_status")) {
|
|
173
|
+
this.db.exec("ALTER TABLE client_hub_connection ADD COLUMN last_known_status TEXT NOT NULL DEFAULT ''");
|
|
174
|
+
this.log.info("Migrated: added last_known_status to client_hub_connection");
|
|
175
|
+
}
|
|
176
|
+
} catch { /* table may not exist yet */ }
|
|
177
|
+
}
|
|
178
|
+
|
|
133
179
|
private migrateOwnerFields(): void {
|
|
134
180
|
const chunkCols = this.db.prepare("PRAGMA table_info(chunks)").all() as Array<{ name: string }>;
|
|
135
181
|
if (!chunkCols.some((c) => c.name === "owner")) {
|
|
@@ -284,6 +330,51 @@ export class SqliteStore {
|
|
|
284
330
|
}
|
|
285
331
|
}
|
|
286
332
|
|
|
333
|
+
private migrateHubFtsToTrigram(): void {
|
|
334
|
+
const tables: Array<{ fts: string; source: string; columns: string; triggers: string[] }> = [
|
|
335
|
+
{
|
|
336
|
+
fts: "hub_chunks_fts", source: "hub_chunks", columns: "summary, content",
|
|
337
|
+
triggers: ["hub_chunks_ai", "hub_chunks_ad", "hub_chunks_au"],
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
fts: "hub_skills_fts", source: "hub_skills", columns: "name, description",
|
|
341
|
+
triggers: ["hub_skills_ai", "hub_skills_ad", "hub_skills_au"],
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
fts: "hub_memories_fts", source: "hub_memories", columns: "summary, content",
|
|
345
|
+
triggers: ["hub_memories_ai", "hub_memories_ad", "hub_memories_au"],
|
|
346
|
+
},
|
|
347
|
+
];
|
|
348
|
+
for (const t of tables) {
|
|
349
|
+
try {
|
|
350
|
+
const row = this.db.prepare(`SELECT sql FROM sqlite_master WHERE name='${t.fts}'`).get() as { sql: string } | undefined;
|
|
351
|
+
if (!row || !row.sql) continue;
|
|
352
|
+
if (row.sql.includes("trigram")) continue;
|
|
353
|
+
this.log.info(`Migrating ${t.fts} to trigram tokenizer...`);
|
|
354
|
+
for (const tr of t.triggers) this.db.exec(`DROP TRIGGER IF EXISTS ${tr}`);
|
|
355
|
+
this.db.exec(`DROP TABLE IF EXISTS ${t.fts}`);
|
|
356
|
+
this.db.exec(`CREATE VIRTUAL TABLE ${t.fts} USING fts5(${t.columns}, content='${t.source}', content_rowid='rowid', tokenize='trigram')`);
|
|
357
|
+
this.db.exec(`
|
|
358
|
+
CREATE TRIGGER ${t.triggers[0]} AFTER INSERT ON ${t.source} BEGIN
|
|
359
|
+
INSERT INTO ${t.fts}(rowid, ${t.columns}) VALUES (new.rowid, ${t.columns.split(", ").map(c => "new." + c).join(", ")});
|
|
360
|
+
END;
|
|
361
|
+
CREATE TRIGGER ${t.triggers[1]} AFTER DELETE ON ${t.source} BEGIN
|
|
362
|
+
INSERT INTO ${t.fts}(${t.fts}, rowid, ${t.columns}) VALUES ('delete', old.rowid, ${t.columns.split(", ").map(c => "old." + c).join(", ")});
|
|
363
|
+
END;
|
|
364
|
+
CREATE TRIGGER ${t.triggers[2]} AFTER UPDATE ON ${t.source} BEGIN
|
|
365
|
+
INSERT INTO ${t.fts}(${t.fts}, rowid, ${t.columns}) VALUES ('delete', old.rowid, ${t.columns.split(", ").map(c => "old." + c).join(", ")});
|
|
366
|
+
INSERT INTO ${t.fts}(rowid, ${t.columns}) VALUES (new.rowid, ${t.columns.split(", ").map(c => "new." + c).join(", ")});
|
|
367
|
+
END
|
|
368
|
+
`);
|
|
369
|
+
this.db.exec(`INSERT INTO ${t.fts}(rowid, ${t.columns}) SELECT rowid, ${t.columns} FROM ${t.source}`);
|
|
370
|
+
const cnt = (this.db.prepare(`SELECT COUNT(*) as c FROM ${t.fts}`).get() as { c: number }).c;
|
|
371
|
+
this.log.info(`Migrated ${t.fts} to trigram: ${cnt} rows indexed`);
|
|
372
|
+
} catch (err) {
|
|
373
|
+
this.log.warn(`Failed to migrate ${t.fts} to trigram: ${err}`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
287
378
|
private migrateTaskId(): void {
|
|
288
379
|
const cols = this.db.prepare("PRAGMA table_info(chunks)").all() as Array<{ name: string }>;
|
|
289
380
|
if (!cols.some((c) => c.name === "task_id")) {
|
|
@@ -701,6 +792,15 @@ export class SqliteStore {
|
|
|
701
792
|
shared_at INTEGER NOT NULL
|
|
702
793
|
);
|
|
703
794
|
|
|
795
|
+
-- Client: team share UI metadata only (no hub_memories row — avoids local FTS/embed recall duplication)
|
|
796
|
+
CREATE TABLE IF NOT EXISTS team_shared_chunks (
|
|
797
|
+
chunk_id TEXT PRIMARY KEY REFERENCES chunks(id) ON DELETE CASCADE,
|
|
798
|
+
hub_memory_id TEXT NOT NULL DEFAULT '',
|
|
799
|
+
visibility TEXT NOT NULL DEFAULT 'public',
|
|
800
|
+
group_id TEXT,
|
|
801
|
+
shared_at INTEGER NOT NULL
|
|
802
|
+
);
|
|
803
|
+
|
|
704
804
|
CREATE TABLE IF NOT EXISTS hub_users (
|
|
705
805
|
id TEXT PRIMARY KEY,
|
|
706
806
|
username TEXT NOT NULL UNIQUE,
|
|
@@ -716,6 +816,20 @@ export class SqliteStore {
|
|
|
716
816
|
CREATE INDEX IF NOT EXISTS idx_hub_users_status ON hub_users(status);
|
|
717
817
|
CREATE INDEX IF NOT EXISTS idx_hub_users_role ON hub_users(role);
|
|
718
818
|
|
|
819
|
+
CREATE TABLE IF NOT EXISTS hub_groups (
|
|
820
|
+
id TEXT PRIMARY KEY,
|
|
821
|
+
name TEXT NOT NULL,
|
|
822
|
+
description TEXT NOT NULL DEFAULT '',
|
|
823
|
+
created_at INTEGER NOT NULL
|
|
824
|
+
);
|
|
825
|
+
|
|
826
|
+
CREATE TABLE IF NOT EXISTS hub_group_members (
|
|
827
|
+
group_id TEXT NOT NULL REFERENCES hub_groups(id) ON DELETE CASCADE,
|
|
828
|
+
user_id TEXT NOT NULL REFERENCES hub_users(id) ON DELETE CASCADE,
|
|
829
|
+
joined_at INTEGER NOT NULL,
|
|
830
|
+
PRIMARY KEY (group_id, user_id)
|
|
831
|
+
);
|
|
832
|
+
|
|
719
833
|
CREATE TABLE IF NOT EXISTS hub_tasks (
|
|
720
834
|
id TEXT PRIMARY KEY,
|
|
721
835
|
source_task_id TEXT NOT NULL,
|
|
@@ -757,7 +871,7 @@ export class SqliteStore {
|
|
|
757
871
|
content,
|
|
758
872
|
content='hub_chunks',
|
|
759
873
|
content_rowid='rowid',
|
|
760
|
-
tokenize='
|
|
874
|
+
tokenize='trigram'
|
|
761
875
|
);
|
|
762
876
|
|
|
763
877
|
CREATE TRIGGER IF NOT EXISTS hub_chunks_ai AFTER INSERT ON hub_chunks BEGIN
|
|
@@ -807,7 +921,7 @@ export class SqliteStore {
|
|
|
807
921
|
description,
|
|
808
922
|
content='hub_skills',
|
|
809
923
|
content_rowid='rowid',
|
|
810
|
-
tokenize='
|
|
924
|
+
tokenize='trigram'
|
|
811
925
|
);
|
|
812
926
|
|
|
813
927
|
CREATE TRIGGER IF NOT EXISTS hub_skills_ai AFTER INSERT ON hub_skills BEGIN
|
|
@@ -857,7 +971,7 @@ export class SqliteStore {
|
|
|
857
971
|
content,
|
|
858
972
|
content='hub_memories',
|
|
859
973
|
content_rowid='rowid',
|
|
860
|
-
tokenize='
|
|
974
|
+
tokenize='trigram'
|
|
861
975
|
);
|
|
862
976
|
|
|
863
977
|
CREATE TRIGGER IF NOT EXISTS hub_memories_ai AFTER INSERT ON hub_memories BEGIN
|
|
@@ -1078,6 +1192,25 @@ export class SqliteStore {
|
|
|
1078
1192
|
}
|
|
1079
1193
|
}
|
|
1080
1194
|
|
|
1195
|
+
hubMemoryPatternSearch(patterns: string[], opts: { limit?: number } = {}): Array<{ memoryId: string; content: string; role: string; createdAt: number }> {
|
|
1196
|
+
if (patterns.length === 0) return [];
|
|
1197
|
+
const limit = opts.limit ?? 10;
|
|
1198
|
+
const conditions = patterns.map(() => "(hm.content LIKE ? OR hm.summary LIKE ?)");
|
|
1199
|
+
const params: (string | number)[] = [];
|
|
1200
|
+
for (const p of patterns) { params.push(`%${p}%`, `%${p}%`); }
|
|
1201
|
+
params.push(limit);
|
|
1202
|
+
try {
|
|
1203
|
+
const rows = this.db.prepare(`
|
|
1204
|
+
SELECT hm.id as memory_id, hm.content, hm.role, hm.created_at
|
|
1205
|
+
FROM hub_memories hm
|
|
1206
|
+
WHERE ${conditions.join(" OR ")}
|
|
1207
|
+
ORDER BY hm.created_at DESC
|
|
1208
|
+
LIMIT ?
|
|
1209
|
+
`).all(...params) as Array<{ memory_id: string; content: string; role: string; created_at: number }>;
|
|
1210
|
+
return rows.map(r => ({ memoryId: r.memory_id, content: r.content, role: r.role, createdAt: r.created_at }));
|
|
1211
|
+
} catch { return []; }
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1081
1214
|
// ─── Vector Search ───
|
|
1082
1215
|
|
|
1083
1216
|
getAllEmbeddings(ownerFilter?: string[]): Array<{ chunkId: string; vector: number[] }> {
|
|
@@ -1245,6 +1378,7 @@ export class SqliteStore {
|
|
|
1245
1378
|
"skill_versions",
|
|
1246
1379
|
"skills",
|
|
1247
1380
|
"local_shared_memories",
|
|
1381
|
+
"team_shared_chunks",
|
|
1248
1382
|
"local_shared_tasks",
|
|
1249
1383
|
"embeddings",
|
|
1250
1384
|
"chunks",
|
|
@@ -1366,7 +1500,10 @@ export class SqliteStore {
|
|
|
1366
1500
|
const conditions: string[] = [];
|
|
1367
1501
|
const params: unknown[] = [];
|
|
1368
1502
|
if (opts.status) { conditions.push("status = ?"); params.push(opts.status); }
|
|
1369
|
-
if (opts.owner) {
|
|
1503
|
+
if (opts.owner) {
|
|
1504
|
+
conditions.push("(owner = ? OR (owner = 'public' AND id IN (SELECT task_id FROM local_shared_tasks WHERE original_owner = ?)))");
|
|
1505
|
+
params.push(opts.owner, opts.owner);
|
|
1506
|
+
}
|
|
1370
1507
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1371
1508
|
|
|
1372
1509
|
const countRow = this.db.prepare(`SELECT COUNT(*) as c FROM tasks ${whereClause}`).get(...params) as { c: number };
|
|
@@ -1666,16 +1803,18 @@ export class SqliteStore {
|
|
|
1666
1803
|
|
|
1667
1804
|
setClientHubConnection(conn: ClientHubConnection): void {
|
|
1668
1805
|
this.db.prepare(`
|
|
1669
|
-
INSERT INTO client_hub_connection (id, hub_url, user_id, username, user_token, role, connected_at)
|
|
1670
|
-
VALUES (1, ?, ?, ?, ?, ?, ?)
|
|
1806
|
+
INSERT INTO client_hub_connection (id, hub_url, user_id, username, user_token, role, connected_at, identity_key, last_known_status)
|
|
1807
|
+
VALUES (1, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1671
1808
|
ON CONFLICT(id) DO UPDATE SET
|
|
1672
1809
|
hub_url = excluded.hub_url,
|
|
1673
1810
|
user_id = excluded.user_id,
|
|
1674
1811
|
username = excluded.username,
|
|
1675
1812
|
user_token = excluded.user_token,
|
|
1676
1813
|
role = excluded.role,
|
|
1677
|
-
connected_at = excluded.connected_at
|
|
1678
|
-
|
|
1814
|
+
connected_at = excluded.connected_at,
|
|
1815
|
+
identity_key = excluded.identity_key,
|
|
1816
|
+
last_known_status = excluded.last_known_status
|
|
1817
|
+
`).run(conn.hubUrl, conn.userId, conn.username, conn.userToken, conn.role, conn.connectedAt, conn.identityKey ?? "", conn.lastKnownStatus ?? "");
|
|
1679
1818
|
}
|
|
1680
1819
|
|
|
1681
1820
|
getClientHubConnection(): ClientHubConnection | null {
|
|
@@ -1782,8 +1921,8 @@ export class SqliteStore {
|
|
|
1782
1921
|
|
|
1783
1922
|
upsertHubUser(user: HubUserRecord): void {
|
|
1784
1923
|
this.db.prepare(`
|
|
1785
|
-
INSERT INTO hub_users (id, username, device_name, role, status, token_hash, created_at, approved_at)
|
|
1786
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
1924
|
+
INSERT INTO hub_users (id, username, device_name, role, status, token_hash, created_at, approved_at, identity_key, left_at, removed_at, rejected_at, rejoin_requested_at)
|
|
1925
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1787
1926
|
ON CONFLICT(id) DO UPDATE SET
|
|
1788
1927
|
username = excluded.username,
|
|
1789
1928
|
device_name = excluded.device_name,
|
|
@@ -1791,21 +1930,32 @@ export class SqliteStore {
|
|
|
1791
1930
|
status = excluded.status,
|
|
1792
1931
|
token_hash = excluded.token_hash,
|
|
1793
1932
|
created_at = excluded.created_at,
|
|
1794
|
-
approved_at = excluded.approved_at
|
|
1795
|
-
|
|
1933
|
+
approved_at = excluded.approved_at,
|
|
1934
|
+
identity_key = excluded.identity_key,
|
|
1935
|
+
left_at = excluded.left_at,
|
|
1936
|
+
removed_at = excluded.removed_at,
|
|
1937
|
+
rejected_at = excluded.rejected_at,
|
|
1938
|
+
rejoin_requested_at = excluded.rejoin_requested_at
|
|
1939
|
+
`).run(user.id, user.username, user.deviceName ?? "", user.role, user.status, user.tokenHash, user.createdAt, user.approvedAt, user.identityKey ?? "", user.leftAt ?? null, user.removedAt ?? null, user.rejectedAt ?? null, user.rejoinRequestedAt ?? null);
|
|
1796
1940
|
}
|
|
1797
1941
|
|
|
1798
1942
|
getHubUser(userId: string): HubUserRecord | null {
|
|
1799
1943
|
const row = this.db.prepare('SELECT * FROM hub_users WHERE id = ?').get(userId) as HubUserRow | undefined;
|
|
1800
1944
|
if (!row) return null;
|
|
1801
|
-
|
|
1945
|
+
const user = rowToHubUser(row);
|
|
1946
|
+
user.groups = this.getGroupsForHubUser(userId);
|
|
1947
|
+
return user;
|
|
1802
1948
|
}
|
|
1803
1949
|
|
|
1804
1950
|
listHubUsers(status?: UserStatus): HubUserRecord[] {
|
|
1805
1951
|
const rows = status
|
|
1806
1952
|
? this.db.prepare('SELECT * FROM hub_users WHERE status = ? ORDER BY created_at').all(status) as HubUserRow[]
|
|
1807
1953
|
: this.db.prepare('SELECT * FROM hub_users ORDER BY created_at').all() as HubUserRow[];
|
|
1808
|
-
return rows.map(
|
|
1954
|
+
return rows.map(r => {
|
|
1955
|
+
const user = rowToHubUser(r);
|
|
1956
|
+
user.groups = this.getGroupsForHubUser(r.id);
|
|
1957
|
+
return user;
|
|
1958
|
+
});
|
|
1809
1959
|
}
|
|
1810
1960
|
|
|
1811
1961
|
deleteHubUser(userId: string, cleanResources = false): boolean {
|
|
@@ -1813,8 +1963,21 @@ export class SqliteStore {
|
|
|
1813
1963
|
this.db.prepare('DELETE FROM hub_tasks WHERE source_user_id = ?').run(userId);
|
|
1814
1964
|
this.db.prepare('DELETE FROM hub_skills WHERE source_user_id = ?').run(userId);
|
|
1815
1965
|
this.db.prepare('DELETE FROM hub_memories WHERE source_user_id = ?').run(userId);
|
|
1966
|
+
const result = this.db.prepare('DELETE FROM hub_users WHERE id = ?').run(userId);
|
|
1967
|
+
return result.changes > 0;
|
|
1816
1968
|
}
|
|
1817
|
-
const result = this.db.prepare('
|
|
1969
|
+
const result = this.db.prepare("UPDATE hub_users SET status = 'removed', token_hash = '', removed_at = ? WHERE id = ?").run(Date.now(), userId);
|
|
1970
|
+
return result.changes > 0;
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
findHubUserByIdentityKey(identityKey: string): HubUserRecord | null {
|
|
1974
|
+
if (!identityKey) return null;
|
|
1975
|
+
const row = this.db.prepare('SELECT * FROM hub_users WHERE identity_key = ?').get(identityKey) as HubUserRow | undefined;
|
|
1976
|
+
return row ? rowToHubUser(row) : null;
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
markHubUserLeft(userId: string): boolean {
|
|
1980
|
+
const result = this.db.prepare("UPDATE hub_users SET status = 'left', token_hash = '', left_at = ? WHERE id = ?").run(Date.now(), userId);
|
|
1818
1981
|
return result.changes > 0;
|
|
1819
1982
|
}
|
|
1820
1983
|
|
|
@@ -1822,6 +1985,35 @@ export class SqliteStore {
|
|
|
1822
1985
|
this.db.prepare('UPDATE hub_users SET last_ip = ?, last_active_at = ? WHERE id = ?').run(ip, timestamp ?? Date.now(), userId);
|
|
1823
1986
|
}
|
|
1824
1987
|
|
|
1988
|
+
// ─── Hub Groups ───
|
|
1989
|
+
|
|
1990
|
+
upsertHubGroup(group: { id: string; name: string; description?: string; createdAt: number }): void {
|
|
1991
|
+
this.db.prepare(`
|
|
1992
|
+
INSERT INTO hub_groups (id, name, description, created_at)
|
|
1993
|
+
VALUES (?, ?, ?, ?)
|
|
1994
|
+
ON CONFLICT(id) DO UPDATE SET name = excluded.name, description = excluded.description
|
|
1995
|
+
`).run(group.id, group.name, group.description ?? "", group.createdAt);
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
addHubGroupMember(groupId: string, userId: string, joinedAt: number): void {
|
|
1999
|
+
this.db.prepare(`
|
|
2000
|
+
INSERT OR IGNORE INTO hub_group_members (group_id, user_id, joined_at)
|
|
2001
|
+
VALUES (?, ?, ?)
|
|
2002
|
+
`).run(groupId, userId, joinedAt);
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
removeHubGroupMember(groupId: string, userId: string): void {
|
|
2006
|
+
this.db.prepare('DELETE FROM hub_group_members WHERE group_id = ? AND user_id = ?').run(groupId, userId);
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
getGroupsForHubUser(userId: string): Array<{ id: string; name: string; description: string }> {
|
|
2010
|
+
return this.db.prepare(`
|
|
2011
|
+
SELECT g.id, g.name, g.description FROM hub_groups g
|
|
2012
|
+
JOIN hub_group_members m ON m.group_id = g.id
|
|
2013
|
+
WHERE m.user_id = ?
|
|
2014
|
+
`).all(userId) as Array<{ id: string; name: string; description: string }>;
|
|
2015
|
+
}
|
|
2016
|
+
|
|
1825
2017
|
getHubUserContributions(): Record<string, { memoryCount: number; taskCount: number; skillCount: number }> {
|
|
1826
2018
|
const result: Record<string, { memoryCount: number; taskCount: number; skillCount: number }> = {};
|
|
1827
2019
|
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 }>;
|
|
@@ -1934,20 +2126,37 @@ export class SqliteStore {
|
|
|
1934
2126
|
return out;
|
|
1935
2127
|
}
|
|
1936
2128
|
|
|
2129
|
+
getVisibleHubSkillEmbeddings(): Array<{ skillId: string; vector: Float32Array }> {
|
|
2130
|
+
const rows = this.db.prepare(`
|
|
2131
|
+
SELECT hse.skill_id, hse.vector, hse.dimensions
|
|
2132
|
+
FROM hub_skill_embeddings hse
|
|
2133
|
+
JOIN hub_skills hs ON hs.id = hse.skill_id
|
|
2134
|
+
`).all() as Array<{ skill_id: string; vector: Buffer; dimensions: number }>;
|
|
2135
|
+
return rows.map(r => ({
|
|
2136
|
+
skillId: r.skill_id,
|
|
2137
|
+
vector: new Float32Array(r.vector.buffer, r.vector.byteOffset, r.dimensions),
|
|
2138
|
+
}));
|
|
2139
|
+
}
|
|
2140
|
+
|
|
1937
2141
|
searchHubChunks(query: string, options?: { userId?: string; maxResults?: number }): Array<{ hit: HubSearchRow; rank: number }> {
|
|
1938
2142
|
const limit = options?.maxResults ?? 10;
|
|
1939
2143
|
const userId = options?.userId ?? "";
|
|
1940
2144
|
const rows = this.db.prepare(`
|
|
1941
|
-
SELECT hc.id, hc.content, hc.summary, hc.role, hc.created_at, ht.title as task_title, ht.visibility,
|
|
2145
|
+
SELECT hc.id, hc.content, hc.summary, hc.role, hc.created_at, ht.title as task_title, ht.visibility,
|
|
2146
|
+
COALESCE(hg.name, '') as group_name, hu.username as owner_name,
|
|
1942
2147
|
bm25(hub_chunks_fts) as rank
|
|
1943
2148
|
FROM hub_chunks_fts f
|
|
1944
2149
|
JOIN hub_chunks hc ON hc.rowid = f.rowid
|
|
1945
2150
|
JOIN hub_tasks ht ON ht.id = hc.hub_task_id
|
|
1946
2151
|
LEFT JOIN hub_users hu ON hu.id = ht.source_user_id
|
|
2152
|
+
LEFT JOIN hub_groups hg ON hg.id = ht.group_id
|
|
1947
2153
|
WHERE hub_chunks_fts MATCH ?
|
|
2154
|
+
AND (ht.visibility = 'public'
|
|
2155
|
+
OR ht.source_user_id = ?
|
|
2156
|
+
OR EXISTS (SELECT 1 FROM hub_group_members gm WHERE gm.group_id = ht.group_id AND gm.user_id = ?))
|
|
1948
2157
|
ORDER BY rank
|
|
1949
2158
|
LIMIT ?
|
|
1950
|
-
`).all(sanitizeFtsQuery(query), limit) as HubSearchRow[];
|
|
2159
|
+
`).all(sanitizeFtsQuery(query), userId, userId, limit) as HubSearchRow[];
|
|
1951
2160
|
return rows.map((row, idx) => ({ hit: row, rank: idx + 1 }));
|
|
1952
2161
|
}
|
|
1953
2162
|
|
|
@@ -1972,7 +2181,10 @@ export class SqliteStore {
|
|
|
1972
2181
|
FROM hub_embeddings he
|
|
1973
2182
|
JOIN hub_chunks hc ON hc.id = he.chunk_id
|
|
1974
2183
|
JOIN hub_tasks ht ON ht.id = hc.hub_task_id
|
|
1975
|
-
|
|
2184
|
+
WHERE ht.visibility = 'public'
|
|
2185
|
+
OR ht.source_user_id = ?
|
|
2186
|
+
OR EXISTS (SELECT 1 FROM hub_group_members gm WHERE gm.group_id = ht.group_id AND gm.user_id = ?)
|
|
2187
|
+
`).all(userId, userId) as Array<{ chunk_id: string; vector: Buffer; dimensions: number }>;
|
|
1976
2188
|
return rows.map(r => ({
|
|
1977
2189
|
chunkId: r.chunk_id,
|
|
1978
2190
|
vector: new Float32Array(r.vector.buffer, r.vector.byteOffset, r.dimensions),
|
|
@@ -1981,14 +2193,19 @@ export class SqliteStore {
|
|
|
1981
2193
|
|
|
1982
2194
|
getVisibleHubSearchHitByChunkId(chunkId: string, userId: string): HubSearchRow | null {
|
|
1983
2195
|
const row = this.db.prepare(`
|
|
1984
|
-
SELECT hc.id, hc.content, hc.summary, hc.role, hc.created_at, ht.title as task_title, ht.visibility,
|
|
2196
|
+
SELECT hc.id, hc.content, hc.summary, hc.role, hc.created_at, ht.title as task_title, ht.visibility,
|
|
2197
|
+
COALESCE(hg.name, '') as group_name, hu.username as owner_name,
|
|
1985
2198
|
0 as rank
|
|
1986
2199
|
FROM hub_chunks hc
|
|
1987
2200
|
JOIN hub_tasks ht ON ht.id = hc.hub_task_id
|
|
1988
2201
|
LEFT JOIN hub_users hu ON hu.id = ht.source_user_id
|
|
2202
|
+
LEFT JOIN hub_groups hg ON hg.id = ht.group_id
|
|
1989
2203
|
WHERE hc.id = ?
|
|
2204
|
+
AND (ht.visibility = 'public'
|
|
2205
|
+
OR ht.source_user_id = ?
|
|
2206
|
+
OR EXISTS (SELECT 1 FROM hub_group_members gm WHERE gm.group_id = ht.group_id AND gm.user_id = ?))
|
|
1990
2207
|
LIMIT 1
|
|
1991
|
-
`).get(chunkId) as HubSearchRow | undefined;
|
|
2208
|
+
`).get(chunkId, userId, userId) as HubSearchRow | undefined;
|
|
1992
2209
|
return row ?? null;
|
|
1993
2210
|
}
|
|
1994
2211
|
|
|
@@ -2004,7 +2221,7 @@ export class SqliteStore {
|
|
|
2004
2221
|
let rows: HubSkillSearchRow[];
|
|
2005
2222
|
if (sanitized) {
|
|
2006
2223
|
rows = this.db.prepare(`
|
|
2007
|
-
SELECT hs.id, hs.name, hs.description, hs.version, hs.visibility, '' AS group_name, hu.username AS owner_name, hs.quality_score,
|
|
2224
|
+
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,
|
|
2008
2225
|
bm25(hub_skills_fts) as rank
|
|
2009
2226
|
FROM hub_skills_fts f
|
|
2010
2227
|
JOIN hub_skills hs ON hs.rowid = f.rowid
|
|
@@ -2015,7 +2232,7 @@ export class SqliteStore {
|
|
|
2015
2232
|
`).all(sanitized, limit) as HubSkillSearchRow[];
|
|
2016
2233
|
} else {
|
|
2017
2234
|
rows = this.db.prepare(`
|
|
2018
|
-
SELECT hs.id, hs.name, hs.description, hs.version, hs.visibility, '' AS group_name, hu.username AS owner_name, hs.quality_score,
|
|
2235
|
+
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,
|
|
2019
2236
|
0 as rank
|
|
2020
2237
|
FROM hub_skills hs
|
|
2021
2238
|
LEFT JOIN hub_users hu ON hu.id = hs.source_user_id
|
|
@@ -2030,9 +2247,9 @@ export class SqliteStore {
|
|
|
2030
2247
|
this.db.prepare('DELETE FROM hub_skills WHERE source_user_id = ? AND source_skill_id = ?').run(sourceUserId, sourceSkillId);
|
|
2031
2248
|
}
|
|
2032
2249
|
|
|
2033
|
-
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 }> {
|
|
2250
|
+
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 }> {
|
|
2034
2251
|
const rows = this.db.prepare(`
|
|
2035
|
-
SELECT t.*, u.username AS owner_name, NULL AS group_name,
|
|
2252
|
+
SELECT t.*, u.username AS owner_name, u.status AS owner_status, NULL AS group_name,
|
|
2036
2253
|
(SELECT COUNT(*) FROM hub_chunks c WHERE c.hub_task_id = t.id) AS chunk_count
|
|
2037
2254
|
FROM hub_tasks t
|
|
2038
2255
|
LEFT JOIN hub_users u ON u.id = t.source_user_id
|
|
@@ -2042,14 +2259,14 @@ export class SqliteStore {
|
|
|
2042
2259
|
return rows.map(r => ({
|
|
2043
2260
|
id: r.id, sourceTaskId: r.source_task_id, sourceUserId: r.source_user_id,
|
|
2044
2261
|
title: r.title, summary: r.summary, groupId: r.group_id, groupName: r.group_name ?? null,
|
|
2045
|
-
visibility: r.visibility, ownerName: r.owner_name ?? "unknown", chunkCount: r.chunk_count ?? 0,
|
|
2262
|
+
visibility: r.visibility, ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", chunkCount: r.chunk_count ?? 0,
|
|
2046
2263
|
createdAt: r.created_at, updatedAt: r.updated_at,
|
|
2047
2264
|
}));
|
|
2048
2265
|
}
|
|
2049
2266
|
|
|
2050
|
-
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 }> {
|
|
2267
|
+
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 }> {
|
|
2051
2268
|
const rows = this.db.prepare(`
|
|
2052
|
-
SELECT t.*, u.username AS owner_name,
|
|
2269
|
+
SELECT t.*, u.username AS owner_name, u.status AS owner_status,
|
|
2053
2270
|
(SELECT COUNT(*) FROM hub_chunks c WHERE c.hub_task_id = t.id) AS chunk_count
|
|
2054
2271
|
FROM hub_tasks t
|
|
2055
2272
|
LEFT JOIN hub_users u ON u.id = t.source_user_id
|
|
@@ -2058,7 +2275,7 @@ export class SqliteStore {
|
|
|
2058
2275
|
return rows.map(r => ({
|
|
2059
2276
|
id: r.id, sourceTaskId: r.source_task_id, sourceUserId: r.source_user_id,
|
|
2060
2277
|
title: r.title, summary: r.summary, groupId: r.group_id, groupName: null as string | null,
|
|
2061
|
-
visibility: r.visibility, ownerName: r.owner_name ?? "unknown", chunkCount: r.chunk_count ?? 0,
|
|
2278
|
+
visibility: r.visibility, ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", chunkCount: r.chunk_count ?? 0,
|
|
2062
2279
|
createdAt: r.created_at, updatedAt: r.updated_at,
|
|
2063
2280
|
}));
|
|
2064
2281
|
}
|
|
@@ -2073,9 +2290,9 @@ export class SqliteStore {
|
|
|
2073
2290
|
return info.changes > 0;
|
|
2074
2291
|
}
|
|
2075
2292
|
|
|
2076
|
-
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 }> {
|
|
2293
|
+
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 }> {
|
|
2077
2294
|
const rows = this.db.prepare(`
|
|
2078
|
-
SELECT s.*, u.username AS owner_name, NULL AS group_name
|
|
2295
|
+
SELECT s.*, u.username AS owner_name, u.status AS owner_status, NULL AS group_name
|
|
2079
2296
|
FROM hub_skills s
|
|
2080
2297
|
LEFT JOIN hub_users u ON u.id = s.source_user_id
|
|
2081
2298
|
ORDER BY s.updated_at DESC
|
|
@@ -2085,14 +2302,14 @@ export class SqliteStore {
|
|
|
2085
2302
|
id: r.id, sourceSkillId: r.source_skill_id, sourceUserId: r.source_user_id,
|
|
2086
2303
|
name: r.name, description: r.description, version: r.version,
|
|
2087
2304
|
groupId: r.group_id, groupName: r.group_name ?? null, visibility: r.visibility,
|
|
2088
|
-
ownerName: r.owner_name ?? "unknown", qualityScore: r.quality_score,
|
|
2305
|
+
ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", qualityScore: r.quality_score,
|
|
2089
2306
|
createdAt: r.created_at, updatedAt: r.updated_at,
|
|
2090
2307
|
}));
|
|
2091
2308
|
}
|
|
2092
2309
|
|
|
2093
|
-
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 }> {
|
|
2310
|
+
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 }> {
|
|
2094
2311
|
const rows = this.db.prepare(`
|
|
2095
|
-
SELECT s.*, u.username AS owner_name
|
|
2312
|
+
SELECT s.*, u.username AS owner_name, u.status AS owner_status
|
|
2096
2313
|
FROM hub_skills s
|
|
2097
2314
|
LEFT JOIN hub_users u ON u.id = s.source_user_id
|
|
2098
2315
|
ORDER BY s.updated_at DESC
|
|
@@ -2101,7 +2318,7 @@ export class SqliteStore {
|
|
|
2101
2318
|
id: r.id, sourceSkillId: r.source_skill_id, sourceUserId: r.source_user_id,
|
|
2102
2319
|
name: r.name, description: r.description, version: r.version,
|
|
2103
2320
|
groupId: r.group_id, groupName: null as string | null, visibility: r.visibility,
|
|
2104
|
-
ownerName: r.owner_name ?? "unknown", qualityScore: r.quality_score,
|
|
2321
|
+
ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", qualityScore: r.quality_score,
|
|
2105
2322
|
createdAt: r.created_at, updatedAt: r.updated_at,
|
|
2106
2323
|
}));
|
|
2107
2324
|
}
|
|
@@ -2148,6 +2365,45 @@ export class SqliteStore {
|
|
|
2148
2365
|
return info.changes > 0;
|
|
2149
2366
|
}
|
|
2150
2367
|
|
|
2368
|
+
// ─── Team share metadata (Client role — UI only, not used for local recall / FTS) ───
|
|
2369
|
+
|
|
2370
|
+
upsertTeamSharedChunk(
|
|
2371
|
+
chunkId: string,
|
|
2372
|
+
row: { hubMemoryId?: string; visibility?: string; groupId?: string | null },
|
|
2373
|
+
): void {
|
|
2374
|
+
const now = Date.now();
|
|
2375
|
+
const vis = row.visibility === "group" ? "group" : "public";
|
|
2376
|
+
const gid = vis === "group" ? (row.groupId ?? null) : null;
|
|
2377
|
+
this.db.prepare(`
|
|
2378
|
+
INSERT INTO team_shared_chunks (chunk_id, hub_memory_id, visibility, group_id, shared_at)
|
|
2379
|
+
VALUES (?, ?, ?, ?, ?)
|
|
2380
|
+
ON CONFLICT(chunk_id) DO UPDATE SET
|
|
2381
|
+
hub_memory_id = excluded.hub_memory_id,
|
|
2382
|
+
visibility = excluded.visibility,
|
|
2383
|
+
group_id = excluded.group_id,
|
|
2384
|
+
shared_at = excluded.shared_at
|
|
2385
|
+
`).run(chunkId, row.hubMemoryId ?? "", vis, gid, now);
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
getTeamSharedChunk(chunkId: string): { chunkId: string; hubMemoryId: string; visibility: string; groupId: string | null; sharedAt: number } | null {
|
|
2389
|
+
const r = this.db.prepare("SELECT chunk_id, hub_memory_id, visibility, group_id, shared_at FROM team_shared_chunks WHERE chunk_id = ?").get(chunkId) as {
|
|
2390
|
+
chunk_id: string; hub_memory_id: string; visibility: string; group_id: string | null; shared_at: number;
|
|
2391
|
+
} | undefined;
|
|
2392
|
+
if (!r) return null;
|
|
2393
|
+
return {
|
|
2394
|
+
chunkId: r.chunk_id,
|
|
2395
|
+
hubMemoryId: r.hub_memory_id,
|
|
2396
|
+
visibility: r.visibility,
|
|
2397
|
+
groupId: r.group_id,
|
|
2398
|
+
sharedAt: r.shared_at,
|
|
2399
|
+
};
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
deleteTeamSharedChunk(chunkId: string): boolean {
|
|
2403
|
+
const info = this.db.prepare("DELETE FROM team_shared_chunks WHERE chunk_id = ?").run(chunkId);
|
|
2404
|
+
return info.changes > 0;
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2151
2407
|
// ─── Hub Notifications ───
|
|
2152
2408
|
|
|
2153
2409
|
insertHubNotification(n: { id: string; userId: string; type: string; resource: string; title: string; message?: string }): void {
|
|
@@ -2246,9 +2502,9 @@ export class SqliteStore {
|
|
|
2246
2502
|
return row ?? null;
|
|
2247
2503
|
}
|
|
2248
2504
|
|
|
2249
|
-
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; createdAt: number; updatedAt: number }> {
|
|
2505
|
+
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 }> {
|
|
2250
2506
|
const rows = this.db.prepare(`
|
|
2251
|
-
SELECT m.*, u.username AS owner_name, NULL AS group_name
|
|
2507
|
+
SELECT m.*, u.username AS owner_name, u.status AS owner_status, NULL AS group_name
|
|
2252
2508
|
FROM hub_memories m
|
|
2253
2509
|
LEFT JOIN hub_users u ON u.id = m.source_user_id
|
|
2254
2510
|
ORDER BY m.updated_at DESC
|
|
@@ -2258,13 +2514,13 @@ export class SqliteStore {
|
|
|
2258
2514
|
id: r.id, sourceChunkId: r.source_chunk_id, sourceUserId: r.source_user_id,
|
|
2259
2515
|
role: r.role, content: r.content ?? "", summary: r.summary, kind: r.kind,
|
|
2260
2516
|
groupId: r.group_id, groupName: r.group_name ?? null, visibility: r.visibility,
|
|
2261
|
-
ownerName: r.owner_name ?? "unknown", createdAt: r.created_at, updatedAt: r.updated_at,
|
|
2517
|
+
ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", createdAt: r.created_at, updatedAt: r.updated_at,
|
|
2262
2518
|
}));
|
|
2263
2519
|
}
|
|
2264
2520
|
|
|
2265
|
-
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; createdAt: number; updatedAt: number }> {
|
|
2521
|
+
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 }> {
|
|
2266
2522
|
const rows = this.db.prepare(`
|
|
2267
|
-
SELECT m.*, u.username AS owner_name
|
|
2523
|
+
SELECT m.*, u.username AS owner_name, u.status AS owner_status
|
|
2268
2524
|
FROM hub_memories m
|
|
2269
2525
|
LEFT JOIN hub_users u ON u.id = m.source_user_id
|
|
2270
2526
|
ORDER BY m.updated_at DESC
|
|
@@ -2273,7 +2529,7 @@ export class SqliteStore {
|
|
|
2273
2529
|
id: r.id, sourceChunkId: r.source_chunk_id, sourceUserId: r.source_user_id,
|
|
2274
2530
|
role: r.role, content: r.content ?? "", summary: r.summary, kind: r.kind,
|
|
2275
2531
|
groupId: r.group_id, groupName: null as string | null, visibility: r.visibility,
|
|
2276
|
-
ownerName: r.owner_name ?? "unknown", createdAt: r.created_at, updatedAt: r.updated_at,
|
|
2532
|
+
ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", createdAt: r.created_at, updatedAt: r.updated_at,
|
|
2277
2533
|
}));
|
|
2278
2534
|
}
|
|
2279
2535
|
|
|
@@ -2482,6 +2738,8 @@ interface ClientHubConnection {
|
|
|
2482
2738
|
userToken: string;
|
|
2483
2739
|
role: UserRole;
|
|
2484
2740
|
connectedAt: number;
|
|
2741
|
+
identityKey?: string;
|
|
2742
|
+
lastKnownStatus?: string;
|
|
2485
2743
|
}
|
|
2486
2744
|
|
|
2487
2745
|
interface ClientHubConnectionRow {
|
|
@@ -2491,6 +2749,8 @@ interface ClientHubConnectionRow {
|
|
|
2491
2749
|
user_token: string;
|
|
2492
2750
|
role: string;
|
|
2493
2751
|
connected_at: number;
|
|
2752
|
+
identity_key?: string;
|
|
2753
|
+
last_known_status?: string;
|
|
2494
2754
|
}
|
|
2495
2755
|
|
|
2496
2756
|
function rowToClientHubConnection(row: ClientHubConnectionRow): ClientHubConnection {
|
|
@@ -2501,6 +2761,8 @@ function rowToClientHubConnection(row: ClientHubConnectionRow): ClientHubConnect
|
|
|
2501
2761
|
userToken: row.user_token,
|
|
2502
2762
|
role: row.role as UserRole,
|
|
2503
2763
|
connectedAt: row.connected_at,
|
|
2764
|
+
identityKey: row.identity_key || "",
|
|
2765
|
+
lastKnownStatus: row.last_known_status || "",
|
|
2504
2766
|
};
|
|
2505
2767
|
}
|
|
2506
2768
|
|
|
@@ -2510,6 +2772,11 @@ interface HubUserRecord extends UserInfo {
|
|
|
2510
2772
|
approvedAt: number | null;
|
|
2511
2773
|
lastIp: string;
|
|
2512
2774
|
lastActiveAt: number | null;
|
|
2775
|
+
identityKey?: string;
|
|
2776
|
+
leftAt?: number | null;
|
|
2777
|
+
removedAt?: number | null;
|
|
2778
|
+
rejectedAt?: number | null;
|
|
2779
|
+
rejoinRequestedAt?: number | null;
|
|
2513
2780
|
}
|
|
2514
2781
|
|
|
2515
2782
|
interface HubUserRow {
|
|
@@ -2523,6 +2790,11 @@ interface HubUserRow {
|
|
|
2523
2790
|
approved_at: number | null;
|
|
2524
2791
|
last_ip: string;
|
|
2525
2792
|
last_active_at: number | null;
|
|
2793
|
+
identity_key?: string;
|
|
2794
|
+
left_at?: number | null;
|
|
2795
|
+
removed_at?: number | null;
|
|
2796
|
+
rejected_at?: number | null;
|
|
2797
|
+
rejoin_requested_at?: number | null;
|
|
2526
2798
|
}
|
|
2527
2799
|
|
|
2528
2800
|
function rowToHubUser(row: HubUserRow): HubUserRecord {
|
|
@@ -2538,6 +2810,11 @@ function rowToHubUser(row: HubUserRow): HubUserRecord {
|
|
|
2538
2810
|
approvedAt: row.approved_at,
|
|
2539
2811
|
lastIp: row.last_ip || "",
|
|
2540
2812
|
lastActiveAt: row.last_active_at ?? null,
|
|
2813
|
+
identityKey: row.identity_key || "",
|
|
2814
|
+
leftAt: row.left_at ?? null,
|
|
2815
|
+
removedAt: row.removed_at ?? null,
|
|
2816
|
+
rejectedAt: row.rejected_at ?? null,
|
|
2817
|
+
rejoinRequestedAt: row.rejoin_requested_at ?? null,
|
|
2541
2818
|
};
|
|
2542
2819
|
}
|
|
2543
2820
|
|
|
@@ -2686,6 +2963,7 @@ interface HubSkillSearchRow {
|
|
|
2686
2963
|
visibility: string;
|
|
2687
2964
|
group_name: string | null;
|
|
2688
2965
|
owner_name: string | null;
|
|
2966
|
+
owner_status: string | null;
|
|
2689
2967
|
quality_score: number | null;
|
|
2690
2968
|
}
|
|
2691
2969
|
|