@memtensor/memos-local-openclaw-plugin 1.0.4-beta.8 → 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 +132 -10
- 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 +251 -38
- 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 +96 -1
- 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 +58 -0
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +295 -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 +796 -289
- package/dist/viewer/html.js.map +1 -1
- package/dist/viewer/server.d.ts +11 -0
- package/dist/viewer/server.d.ts.map +1 -1
- package/dist/viewer/server.js +456 -92
- 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 +136 -10
- package/src/config.ts +2 -1
- package/src/hub/server.ts +246 -38
- 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 +89 -1
- 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 +326 -40
- package/src/telemetry.ts +27 -9
- package/src/types.ts +11 -0
- package/src/viewer/html.ts +796 -289
- package/src/viewer/server.ts +430 -89
- package/telemetry.credentials.json +5 -0
package/dist/storage/sqlite.js
CHANGED
|
@@ -146,7 +146,10 @@ class SqliteStore {
|
|
|
146
146
|
this.migrateSkillEmbeddingsAndFts();
|
|
147
147
|
this.migrateFtsToTrigram();
|
|
148
148
|
this.migrateHubTables();
|
|
149
|
+
this.migrateHubFtsToTrigram();
|
|
149
150
|
this.migrateLocalSharedTasksOwner();
|
|
151
|
+
this.migrateHubUserIdentityFields();
|
|
152
|
+
this.migrateClientHubConnectionIdentityFields();
|
|
150
153
|
this.log.debug("Database schema initialized");
|
|
151
154
|
}
|
|
152
155
|
migrateChunksIndexesForRecall() {
|
|
@@ -162,6 +165,51 @@ class SqliteStore {
|
|
|
162
165
|
}
|
|
163
166
|
catch { /* table may not exist yet */ }
|
|
164
167
|
}
|
|
168
|
+
migrateHubUserIdentityFields() {
|
|
169
|
+
try {
|
|
170
|
+
const cols = this.db.prepare("PRAGMA table_info(hub_users)").all();
|
|
171
|
+
if (cols.length === 0)
|
|
172
|
+
return;
|
|
173
|
+
if (!cols.some(c => c.name === "identity_key")) {
|
|
174
|
+
this.db.exec("ALTER TABLE hub_users ADD COLUMN identity_key TEXT NOT NULL DEFAULT ''");
|
|
175
|
+
this.db.exec("CREATE INDEX IF NOT EXISTS idx_hub_users_identity_key ON hub_users(identity_key)");
|
|
176
|
+
this.log.info("Migrated: added identity_key to hub_users");
|
|
177
|
+
}
|
|
178
|
+
if (!cols.some(c => c.name === "left_at")) {
|
|
179
|
+
this.db.exec("ALTER TABLE hub_users ADD COLUMN left_at INTEGER");
|
|
180
|
+
this.log.info("Migrated: added left_at to hub_users");
|
|
181
|
+
}
|
|
182
|
+
if (!cols.some(c => c.name === "removed_at")) {
|
|
183
|
+
this.db.exec("ALTER TABLE hub_users ADD COLUMN removed_at INTEGER");
|
|
184
|
+
this.log.info("Migrated: added removed_at to hub_users");
|
|
185
|
+
}
|
|
186
|
+
if (!cols.some(c => c.name === "rejected_at")) {
|
|
187
|
+
this.db.exec("ALTER TABLE hub_users ADD COLUMN rejected_at INTEGER");
|
|
188
|
+
this.log.info("Migrated: added rejected_at to hub_users");
|
|
189
|
+
}
|
|
190
|
+
if (!cols.some(c => c.name === "rejoin_requested_at")) {
|
|
191
|
+
this.db.exec("ALTER TABLE hub_users ADD COLUMN rejoin_requested_at INTEGER");
|
|
192
|
+
this.log.info("Migrated: added rejoin_requested_at to hub_users");
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
catch { /* table may not exist yet */ }
|
|
196
|
+
}
|
|
197
|
+
migrateClientHubConnectionIdentityFields() {
|
|
198
|
+
try {
|
|
199
|
+
const cols = this.db.prepare("PRAGMA table_info(client_hub_connection)").all();
|
|
200
|
+
if (cols.length === 0)
|
|
201
|
+
return;
|
|
202
|
+
if (!cols.some(c => c.name === "identity_key")) {
|
|
203
|
+
this.db.exec("ALTER TABLE client_hub_connection ADD COLUMN identity_key TEXT NOT NULL DEFAULT ''");
|
|
204
|
+
this.log.info("Migrated: added identity_key to client_hub_connection");
|
|
205
|
+
}
|
|
206
|
+
if (!cols.some(c => c.name === "last_known_status")) {
|
|
207
|
+
this.db.exec("ALTER TABLE client_hub_connection ADD COLUMN last_known_status TEXT NOT NULL DEFAULT ''");
|
|
208
|
+
this.log.info("Migrated: added last_known_status to client_hub_connection");
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
catch { /* table may not exist yet */ }
|
|
212
|
+
}
|
|
165
213
|
migrateOwnerFields() {
|
|
166
214
|
const chunkCols = this.db.prepare("PRAGMA table_info(chunks)").all();
|
|
167
215
|
if (!chunkCols.some((c) => c.name === "owner")) {
|
|
@@ -309,6 +357,54 @@ class SqliteStore {
|
|
|
309
357
|
this.log.warn(`Failed to migrate skills_fts to trigram: ${err}`);
|
|
310
358
|
}
|
|
311
359
|
}
|
|
360
|
+
migrateHubFtsToTrigram() {
|
|
361
|
+
const tables = [
|
|
362
|
+
{
|
|
363
|
+
fts: "hub_chunks_fts", source: "hub_chunks", columns: "summary, content",
|
|
364
|
+
triggers: ["hub_chunks_ai", "hub_chunks_ad", "hub_chunks_au"],
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
fts: "hub_skills_fts", source: "hub_skills", columns: "name, description",
|
|
368
|
+
triggers: ["hub_skills_ai", "hub_skills_ad", "hub_skills_au"],
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
fts: "hub_memories_fts", source: "hub_memories", columns: "summary, content",
|
|
372
|
+
triggers: ["hub_memories_ai", "hub_memories_ad", "hub_memories_au"],
|
|
373
|
+
},
|
|
374
|
+
];
|
|
375
|
+
for (const t of tables) {
|
|
376
|
+
try {
|
|
377
|
+
const row = this.db.prepare(`SELECT sql FROM sqlite_master WHERE name='${t.fts}'`).get();
|
|
378
|
+
if (!row || !row.sql)
|
|
379
|
+
continue;
|
|
380
|
+
if (row.sql.includes("trigram"))
|
|
381
|
+
continue;
|
|
382
|
+
this.log.info(`Migrating ${t.fts} to trigram tokenizer...`);
|
|
383
|
+
for (const tr of t.triggers)
|
|
384
|
+
this.db.exec(`DROP TRIGGER IF EXISTS ${tr}`);
|
|
385
|
+
this.db.exec(`DROP TABLE IF EXISTS ${t.fts}`);
|
|
386
|
+
this.db.exec(`CREATE VIRTUAL TABLE ${t.fts} USING fts5(${t.columns}, content='${t.source}', content_rowid='rowid', tokenize='trigram')`);
|
|
387
|
+
this.db.exec(`
|
|
388
|
+
CREATE TRIGGER ${t.triggers[0]} AFTER INSERT ON ${t.source} BEGIN
|
|
389
|
+
INSERT INTO ${t.fts}(rowid, ${t.columns}) VALUES (new.rowid, ${t.columns.split(", ").map(c => "new." + c).join(", ")});
|
|
390
|
+
END;
|
|
391
|
+
CREATE TRIGGER ${t.triggers[1]} AFTER DELETE ON ${t.source} BEGIN
|
|
392
|
+
INSERT INTO ${t.fts}(${t.fts}, rowid, ${t.columns}) VALUES ('delete', old.rowid, ${t.columns.split(", ").map(c => "old." + c).join(", ")});
|
|
393
|
+
END;
|
|
394
|
+
CREATE TRIGGER ${t.triggers[2]} AFTER UPDATE ON ${t.source} BEGIN
|
|
395
|
+
INSERT INTO ${t.fts}(${t.fts}, rowid, ${t.columns}) VALUES ('delete', old.rowid, ${t.columns.split(", ").map(c => "old." + c).join(", ")});
|
|
396
|
+
INSERT INTO ${t.fts}(rowid, ${t.columns}) VALUES (new.rowid, ${t.columns.split(", ").map(c => "new." + c).join(", ")});
|
|
397
|
+
END
|
|
398
|
+
`);
|
|
399
|
+
this.db.exec(`INSERT INTO ${t.fts}(rowid, ${t.columns}) SELECT rowid, ${t.columns} FROM ${t.source}`);
|
|
400
|
+
const cnt = this.db.prepare(`SELECT COUNT(*) as c FROM ${t.fts}`).get().c;
|
|
401
|
+
this.log.info(`Migrated ${t.fts} to trigram: ${cnt} rows indexed`);
|
|
402
|
+
}
|
|
403
|
+
catch (err) {
|
|
404
|
+
this.log.warn(`Failed to migrate ${t.fts} to trigram: ${err}`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
312
408
|
migrateTaskId() {
|
|
313
409
|
const cols = this.db.prepare("PRAGMA table_info(chunks)").all();
|
|
314
410
|
if (!cols.some((c) => c.name === "task_id")) {
|
|
@@ -662,6 +758,15 @@ class SqliteStore {
|
|
|
662
758
|
shared_at INTEGER NOT NULL
|
|
663
759
|
);
|
|
664
760
|
|
|
761
|
+
-- Client: team share UI metadata only (no hub_memories row — avoids local FTS/embed recall duplication)
|
|
762
|
+
CREATE TABLE IF NOT EXISTS team_shared_chunks (
|
|
763
|
+
chunk_id TEXT PRIMARY KEY REFERENCES chunks(id) ON DELETE CASCADE,
|
|
764
|
+
hub_memory_id TEXT NOT NULL DEFAULT '',
|
|
765
|
+
visibility TEXT NOT NULL DEFAULT 'public',
|
|
766
|
+
group_id TEXT,
|
|
767
|
+
shared_at INTEGER NOT NULL
|
|
768
|
+
);
|
|
769
|
+
|
|
665
770
|
CREATE TABLE IF NOT EXISTS hub_users (
|
|
666
771
|
id TEXT PRIMARY KEY,
|
|
667
772
|
username TEXT NOT NULL UNIQUE,
|
|
@@ -677,6 +782,20 @@ class SqliteStore {
|
|
|
677
782
|
CREATE INDEX IF NOT EXISTS idx_hub_users_status ON hub_users(status);
|
|
678
783
|
CREATE INDEX IF NOT EXISTS idx_hub_users_role ON hub_users(role);
|
|
679
784
|
|
|
785
|
+
CREATE TABLE IF NOT EXISTS hub_groups (
|
|
786
|
+
id TEXT PRIMARY KEY,
|
|
787
|
+
name TEXT NOT NULL,
|
|
788
|
+
description TEXT NOT NULL DEFAULT '',
|
|
789
|
+
created_at INTEGER NOT NULL
|
|
790
|
+
);
|
|
791
|
+
|
|
792
|
+
CREATE TABLE IF NOT EXISTS hub_group_members (
|
|
793
|
+
group_id TEXT NOT NULL REFERENCES hub_groups(id) ON DELETE CASCADE,
|
|
794
|
+
user_id TEXT NOT NULL REFERENCES hub_users(id) ON DELETE CASCADE,
|
|
795
|
+
joined_at INTEGER NOT NULL,
|
|
796
|
+
PRIMARY KEY (group_id, user_id)
|
|
797
|
+
);
|
|
798
|
+
|
|
680
799
|
CREATE TABLE IF NOT EXISTS hub_tasks (
|
|
681
800
|
id TEXT PRIMARY KEY,
|
|
682
801
|
source_task_id TEXT NOT NULL,
|
|
@@ -718,7 +837,7 @@ class SqliteStore {
|
|
|
718
837
|
content,
|
|
719
838
|
content='hub_chunks',
|
|
720
839
|
content_rowid='rowid',
|
|
721
|
-
tokenize='
|
|
840
|
+
tokenize='trigram'
|
|
722
841
|
);
|
|
723
842
|
|
|
724
843
|
CREATE TRIGGER IF NOT EXISTS hub_chunks_ai AFTER INSERT ON hub_chunks BEGIN
|
|
@@ -768,7 +887,7 @@ class SqliteStore {
|
|
|
768
887
|
description,
|
|
769
888
|
content='hub_skills',
|
|
770
889
|
content_rowid='rowid',
|
|
771
|
-
tokenize='
|
|
890
|
+
tokenize='trigram'
|
|
772
891
|
);
|
|
773
892
|
|
|
774
893
|
CREATE TRIGGER IF NOT EXISTS hub_skills_ai AFTER INSERT ON hub_skills BEGIN
|
|
@@ -818,7 +937,7 @@ class SqliteStore {
|
|
|
818
937
|
content,
|
|
819
938
|
content='hub_memories',
|
|
820
939
|
content_rowid='rowid',
|
|
821
|
-
tokenize='
|
|
940
|
+
tokenize='trigram'
|
|
822
941
|
);
|
|
823
942
|
|
|
824
943
|
CREATE TRIGGER IF NOT EXISTS hub_memories_ai AFTER INSERT ON hub_memories BEGIN
|
|
@@ -989,6 +1108,30 @@ class SqliteStore {
|
|
|
989
1108
|
return [];
|
|
990
1109
|
}
|
|
991
1110
|
}
|
|
1111
|
+
hubMemoryPatternSearch(patterns, opts = {}) {
|
|
1112
|
+
if (patterns.length === 0)
|
|
1113
|
+
return [];
|
|
1114
|
+
const limit = opts.limit ?? 10;
|
|
1115
|
+
const conditions = patterns.map(() => "(hm.content LIKE ? OR hm.summary LIKE ?)");
|
|
1116
|
+
const params = [];
|
|
1117
|
+
for (const p of patterns) {
|
|
1118
|
+
params.push(`%${p}%`, `%${p}%`);
|
|
1119
|
+
}
|
|
1120
|
+
params.push(limit);
|
|
1121
|
+
try {
|
|
1122
|
+
const rows = this.db.prepare(`
|
|
1123
|
+
SELECT hm.id as memory_id, hm.content, hm.role, hm.created_at
|
|
1124
|
+
FROM hub_memories hm
|
|
1125
|
+
WHERE ${conditions.join(" OR ")}
|
|
1126
|
+
ORDER BY hm.created_at DESC
|
|
1127
|
+
LIMIT ?
|
|
1128
|
+
`).all(...params);
|
|
1129
|
+
return rows.map(r => ({ memoryId: r.memory_id, content: r.content, role: r.role, createdAt: r.created_at }));
|
|
1130
|
+
}
|
|
1131
|
+
catch {
|
|
1132
|
+
return [];
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
992
1135
|
// ─── Vector Search ───
|
|
993
1136
|
getAllEmbeddings(ownerFilter) {
|
|
994
1137
|
let sql = `SELECT e.chunk_id, e.vector, e.dimensions FROM embeddings e
|
|
@@ -1128,6 +1271,7 @@ class SqliteStore {
|
|
|
1128
1271
|
"skill_versions",
|
|
1129
1272
|
"skills",
|
|
1130
1273
|
"local_shared_memories",
|
|
1274
|
+
"team_shared_chunks",
|
|
1131
1275
|
"local_shared_tasks",
|
|
1132
1276
|
"embeddings",
|
|
1133
1277
|
"chunks",
|
|
@@ -1240,8 +1384,8 @@ class SqliteStore {
|
|
|
1240
1384
|
params.push(opts.status);
|
|
1241
1385
|
}
|
|
1242
1386
|
if (opts.owner) {
|
|
1243
|
-
conditions.push("owner = ?");
|
|
1244
|
-
params.push(opts.owner);
|
|
1387
|
+
conditions.push("(owner = ? OR (owner = 'public' AND id IN (SELECT task_id FROM local_shared_tasks WHERE original_owner = ?)))");
|
|
1388
|
+
params.push(opts.owner, opts.owner);
|
|
1245
1389
|
}
|
|
1246
1390
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1247
1391
|
const countRow = this.db.prepare(`SELECT COUNT(*) as c FROM tasks ${whereClause}`).get(...params);
|
|
@@ -1507,16 +1651,18 @@ class SqliteStore {
|
|
|
1507
1651
|
// ─── Hub / Client connection ───
|
|
1508
1652
|
setClientHubConnection(conn) {
|
|
1509
1653
|
this.db.prepare(`
|
|
1510
|
-
INSERT INTO client_hub_connection (id, hub_url, user_id, username, user_token, role, connected_at)
|
|
1511
|
-
VALUES (1, ?, ?, ?, ?, ?, ?)
|
|
1654
|
+
INSERT INTO client_hub_connection (id, hub_url, user_id, username, user_token, role, connected_at, identity_key, last_known_status)
|
|
1655
|
+
VALUES (1, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1512
1656
|
ON CONFLICT(id) DO UPDATE SET
|
|
1513
1657
|
hub_url = excluded.hub_url,
|
|
1514
1658
|
user_id = excluded.user_id,
|
|
1515
1659
|
username = excluded.username,
|
|
1516
1660
|
user_token = excluded.user_token,
|
|
1517
1661
|
role = excluded.role,
|
|
1518
|
-
connected_at = excluded.connected_at
|
|
1519
|
-
|
|
1662
|
+
connected_at = excluded.connected_at,
|
|
1663
|
+
identity_key = excluded.identity_key,
|
|
1664
|
+
last_known_status = excluded.last_known_status
|
|
1665
|
+
`).run(conn.hubUrl, conn.userId, conn.username, conn.userToken, conn.role, conn.connectedAt, conn.identityKey ?? "", conn.lastKnownStatus ?? "");
|
|
1520
1666
|
}
|
|
1521
1667
|
getClientHubConnection() {
|
|
1522
1668
|
const row = this.db.prepare('SELECT * FROM client_hub_connection WHERE id = 1').get();
|
|
@@ -1609,8 +1755,8 @@ class SqliteStore {
|
|
|
1609
1755
|
// ─── Hub Users / Groups ───
|
|
1610
1756
|
upsertHubUser(user) {
|
|
1611
1757
|
this.db.prepare(`
|
|
1612
|
-
INSERT INTO hub_users (id, username, device_name, role, status, token_hash, created_at, approved_at)
|
|
1613
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
1758
|
+
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)
|
|
1759
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1614
1760
|
ON CONFLICT(id) DO UPDATE SET
|
|
1615
1761
|
username = excluded.username,
|
|
1616
1762
|
device_name = excluded.device_name,
|
|
@@ -1618,33 +1764,80 @@ class SqliteStore {
|
|
|
1618
1764
|
status = excluded.status,
|
|
1619
1765
|
token_hash = excluded.token_hash,
|
|
1620
1766
|
created_at = excluded.created_at,
|
|
1621
|
-
approved_at = excluded.approved_at
|
|
1622
|
-
|
|
1767
|
+
approved_at = excluded.approved_at,
|
|
1768
|
+
identity_key = excluded.identity_key,
|
|
1769
|
+
left_at = excluded.left_at,
|
|
1770
|
+
removed_at = excluded.removed_at,
|
|
1771
|
+
rejected_at = excluded.rejected_at,
|
|
1772
|
+
rejoin_requested_at = excluded.rejoin_requested_at
|
|
1773
|
+
`).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);
|
|
1623
1774
|
}
|
|
1624
1775
|
getHubUser(userId) {
|
|
1625
1776
|
const row = this.db.prepare('SELECT * FROM hub_users WHERE id = ?').get(userId);
|
|
1626
1777
|
if (!row)
|
|
1627
1778
|
return null;
|
|
1628
|
-
|
|
1779
|
+
const user = rowToHubUser(row);
|
|
1780
|
+
user.groups = this.getGroupsForHubUser(userId);
|
|
1781
|
+
return user;
|
|
1629
1782
|
}
|
|
1630
1783
|
listHubUsers(status) {
|
|
1631
1784
|
const rows = status
|
|
1632
1785
|
? this.db.prepare('SELECT * FROM hub_users WHERE status = ? ORDER BY created_at').all(status)
|
|
1633
1786
|
: this.db.prepare('SELECT * FROM hub_users ORDER BY created_at').all();
|
|
1634
|
-
return rows.map(
|
|
1787
|
+
return rows.map(r => {
|
|
1788
|
+
const user = rowToHubUser(r);
|
|
1789
|
+
user.groups = this.getGroupsForHubUser(r.id);
|
|
1790
|
+
return user;
|
|
1791
|
+
});
|
|
1635
1792
|
}
|
|
1636
1793
|
deleteHubUser(userId, cleanResources = false) {
|
|
1637
1794
|
if (cleanResources) {
|
|
1638
1795
|
this.db.prepare('DELETE FROM hub_tasks WHERE source_user_id = ?').run(userId);
|
|
1639
1796
|
this.db.prepare('DELETE FROM hub_skills WHERE source_user_id = ?').run(userId);
|
|
1640
1797
|
this.db.prepare('DELETE FROM hub_memories WHERE source_user_id = ?').run(userId);
|
|
1798
|
+
const result = this.db.prepare('DELETE FROM hub_users WHERE id = ?').run(userId);
|
|
1799
|
+
return result.changes > 0;
|
|
1641
1800
|
}
|
|
1642
|
-
const result = this.db.prepare('
|
|
1801
|
+
const result = this.db.prepare("UPDATE hub_users SET status = 'removed', token_hash = '', removed_at = ? WHERE id = ?").run(Date.now(), userId);
|
|
1802
|
+
return result.changes > 0;
|
|
1803
|
+
}
|
|
1804
|
+
findHubUserByIdentityKey(identityKey) {
|
|
1805
|
+
if (!identityKey)
|
|
1806
|
+
return null;
|
|
1807
|
+
const row = this.db.prepare('SELECT * FROM hub_users WHERE identity_key = ?').get(identityKey);
|
|
1808
|
+
return row ? rowToHubUser(row) : null;
|
|
1809
|
+
}
|
|
1810
|
+
markHubUserLeft(userId) {
|
|
1811
|
+
const result = this.db.prepare("UPDATE hub_users SET status = 'left', token_hash = '', left_at = ? WHERE id = ?").run(Date.now(), userId);
|
|
1643
1812
|
return result.changes > 0;
|
|
1644
1813
|
}
|
|
1645
1814
|
updateHubUserActivity(userId, ip, timestamp) {
|
|
1646
1815
|
this.db.prepare('UPDATE hub_users SET last_ip = ?, last_active_at = ? WHERE id = ?').run(ip, timestamp ?? Date.now(), userId);
|
|
1647
1816
|
}
|
|
1817
|
+
// ─── Hub Groups ───
|
|
1818
|
+
upsertHubGroup(group) {
|
|
1819
|
+
this.db.prepare(`
|
|
1820
|
+
INSERT INTO hub_groups (id, name, description, created_at)
|
|
1821
|
+
VALUES (?, ?, ?, ?)
|
|
1822
|
+
ON CONFLICT(id) DO UPDATE SET name = excluded.name, description = excluded.description
|
|
1823
|
+
`).run(group.id, group.name, group.description ?? "", group.createdAt);
|
|
1824
|
+
}
|
|
1825
|
+
addHubGroupMember(groupId, userId, joinedAt) {
|
|
1826
|
+
this.db.prepare(`
|
|
1827
|
+
INSERT OR IGNORE INTO hub_group_members (group_id, user_id, joined_at)
|
|
1828
|
+
VALUES (?, ?, ?)
|
|
1829
|
+
`).run(groupId, userId, joinedAt);
|
|
1830
|
+
}
|
|
1831
|
+
removeHubGroupMember(groupId, userId) {
|
|
1832
|
+
this.db.prepare('DELETE FROM hub_group_members WHERE group_id = ? AND user_id = ?').run(groupId, userId);
|
|
1833
|
+
}
|
|
1834
|
+
getGroupsForHubUser(userId) {
|
|
1835
|
+
return this.db.prepare(`
|
|
1836
|
+
SELECT g.id, g.name, g.description FROM hub_groups g
|
|
1837
|
+
JOIN hub_group_members m ON m.group_id = g.id
|
|
1838
|
+
WHERE m.user_id = ?
|
|
1839
|
+
`).all(userId);
|
|
1840
|
+
}
|
|
1648
1841
|
getHubUserContributions() {
|
|
1649
1842
|
const result = {};
|
|
1650
1843
|
const memRows = this.db.prepare('SELECT source_user_id, COUNT(*) as cnt FROM hub_memories GROUP BY source_user_id').all();
|
|
@@ -1761,20 +1954,36 @@ class SqliteStore {
|
|
|
1761
1954
|
out.push(row.vector.readFloatLE(i * 4));
|
|
1762
1955
|
return out;
|
|
1763
1956
|
}
|
|
1957
|
+
getVisibleHubSkillEmbeddings() {
|
|
1958
|
+
const rows = this.db.prepare(`
|
|
1959
|
+
SELECT hse.skill_id, hse.vector, hse.dimensions
|
|
1960
|
+
FROM hub_skill_embeddings hse
|
|
1961
|
+
JOIN hub_skills hs ON hs.id = hse.skill_id
|
|
1962
|
+
`).all();
|
|
1963
|
+
return rows.map(r => ({
|
|
1964
|
+
skillId: r.skill_id,
|
|
1965
|
+
vector: new Float32Array(r.vector.buffer, r.vector.byteOffset, r.dimensions),
|
|
1966
|
+
}));
|
|
1967
|
+
}
|
|
1764
1968
|
searchHubChunks(query, options) {
|
|
1765
1969
|
const limit = options?.maxResults ?? 10;
|
|
1766
1970
|
const userId = options?.userId ?? "";
|
|
1767
1971
|
const rows = this.db.prepare(`
|
|
1768
|
-
SELECT hc.id, hc.content, hc.summary, hc.role, hc.created_at, ht.title as task_title, ht.visibility,
|
|
1972
|
+
SELECT hc.id, hc.content, hc.summary, hc.role, hc.created_at, ht.title as task_title, ht.visibility,
|
|
1973
|
+
COALESCE(hg.name, '') as group_name, hu.username as owner_name,
|
|
1769
1974
|
bm25(hub_chunks_fts) as rank
|
|
1770
1975
|
FROM hub_chunks_fts f
|
|
1771
1976
|
JOIN hub_chunks hc ON hc.rowid = f.rowid
|
|
1772
1977
|
JOIN hub_tasks ht ON ht.id = hc.hub_task_id
|
|
1773
1978
|
LEFT JOIN hub_users hu ON hu.id = ht.source_user_id
|
|
1979
|
+
LEFT JOIN hub_groups hg ON hg.id = ht.group_id
|
|
1774
1980
|
WHERE hub_chunks_fts MATCH ?
|
|
1981
|
+
AND (ht.visibility = 'public'
|
|
1982
|
+
OR ht.source_user_id = ?
|
|
1983
|
+
OR EXISTS (SELECT 1 FROM hub_group_members gm WHERE gm.group_id = ht.group_id AND gm.user_id = ?))
|
|
1775
1984
|
ORDER BY rank
|
|
1776
1985
|
LIMIT ?
|
|
1777
|
-
`).all(sanitizeFtsQuery(query), limit);
|
|
1986
|
+
`).all(sanitizeFtsQuery(query), userId, userId, limit);
|
|
1778
1987
|
return rows.map((row, idx) => ({ hit: row, rank: idx + 1 }));
|
|
1779
1988
|
}
|
|
1780
1989
|
upsertHubEmbedding(chunkId, vector) {
|
|
@@ -1797,7 +2006,10 @@ class SqliteStore {
|
|
|
1797
2006
|
FROM hub_embeddings he
|
|
1798
2007
|
JOIN hub_chunks hc ON hc.id = he.chunk_id
|
|
1799
2008
|
JOIN hub_tasks ht ON ht.id = hc.hub_task_id
|
|
1800
|
-
|
|
2009
|
+
WHERE ht.visibility = 'public'
|
|
2010
|
+
OR ht.source_user_id = ?
|
|
2011
|
+
OR EXISTS (SELECT 1 FROM hub_group_members gm WHERE gm.group_id = ht.group_id AND gm.user_id = ?)
|
|
2012
|
+
`).all(userId, userId);
|
|
1801
2013
|
return rows.map(r => ({
|
|
1802
2014
|
chunkId: r.chunk_id,
|
|
1803
2015
|
vector: new Float32Array(r.vector.buffer, r.vector.byteOffset, r.dimensions),
|
|
@@ -1805,14 +2017,19 @@ class SqliteStore {
|
|
|
1805
2017
|
}
|
|
1806
2018
|
getVisibleHubSearchHitByChunkId(chunkId, userId) {
|
|
1807
2019
|
const row = this.db.prepare(`
|
|
1808
|
-
SELECT hc.id, hc.content, hc.summary, hc.role, hc.created_at, ht.title as task_title, ht.visibility,
|
|
2020
|
+
SELECT hc.id, hc.content, hc.summary, hc.role, hc.created_at, ht.title as task_title, ht.visibility,
|
|
2021
|
+
COALESCE(hg.name, '') as group_name, hu.username as owner_name,
|
|
1809
2022
|
0 as rank
|
|
1810
2023
|
FROM hub_chunks hc
|
|
1811
2024
|
JOIN hub_tasks ht ON ht.id = hc.hub_task_id
|
|
1812
2025
|
LEFT JOIN hub_users hu ON hu.id = ht.source_user_id
|
|
2026
|
+
LEFT JOIN hub_groups hg ON hg.id = ht.group_id
|
|
1813
2027
|
WHERE hc.id = ?
|
|
2028
|
+
AND (ht.visibility = 'public'
|
|
2029
|
+
OR ht.source_user_id = ?
|
|
2030
|
+
OR EXISTS (SELECT 1 FROM hub_group_members gm WHERE gm.group_id = ht.group_id AND gm.user_id = ?))
|
|
1814
2031
|
LIMIT 1
|
|
1815
|
-
`).get(chunkId);
|
|
2032
|
+
`).get(chunkId, userId, userId);
|
|
1816
2033
|
return row ?? null;
|
|
1817
2034
|
}
|
|
1818
2035
|
getHubChunkById(chunkId) {
|
|
@@ -1826,7 +2043,7 @@ class SqliteStore {
|
|
|
1826
2043
|
let rows;
|
|
1827
2044
|
if (sanitized) {
|
|
1828
2045
|
rows = this.db.prepare(`
|
|
1829
|
-
SELECT hs.id, hs.name, hs.description, hs.version, hs.visibility, '' AS group_name, hu.username AS owner_name, hs.quality_score,
|
|
2046
|
+
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,
|
|
1830
2047
|
bm25(hub_skills_fts) as rank
|
|
1831
2048
|
FROM hub_skills_fts f
|
|
1832
2049
|
JOIN hub_skills hs ON hs.rowid = f.rowid
|
|
@@ -1838,7 +2055,7 @@ class SqliteStore {
|
|
|
1838
2055
|
}
|
|
1839
2056
|
else {
|
|
1840
2057
|
rows = this.db.prepare(`
|
|
1841
|
-
SELECT hs.id, hs.name, hs.description, hs.version, hs.visibility, '' AS group_name, hu.username AS owner_name, hs.quality_score,
|
|
2058
|
+
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,
|
|
1842
2059
|
0 as rank
|
|
1843
2060
|
FROM hub_skills hs
|
|
1844
2061
|
LEFT JOIN hub_users hu ON hu.id = hs.source_user_id
|
|
@@ -1853,7 +2070,7 @@ class SqliteStore {
|
|
|
1853
2070
|
}
|
|
1854
2071
|
listVisibleHubTasks(userId, limit = 40) {
|
|
1855
2072
|
const rows = this.db.prepare(`
|
|
1856
|
-
SELECT t.*, u.username AS owner_name, NULL AS group_name,
|
|
2073
|
+
SELECT t.*, u.username AS owner_name, u.status AS owner_status, NULL AS group_name,
|
|
1857
2074
|
(SELECT COUNT(*) FROM hub_chunks c WHERE c.hub_task_id = t.id) AS chunk_count
|
|
1858
2075
|
FROM hub_tasks t
|
|
1859
2076
|
LEFT JOIN hub_users u ON u.id = t.source_user_id
|
|
@@ -1863,13 +2080,13 @@ class SqliteStore {
|
|
|
1863
2080
|
return rows.map(r => ({
|
|
1864
2081
|
id: r.id, sourceTaskId: r.source_task_id, sourceUserId: r.source_user_id,
|
|
1865
2082
|
title: r.title, summary: r.summary, groupId: r.group_id, groupName: r.group_name ?? null,
|
|
1866
|
-
visibility: r.visibility, ownerName: r.owner_name ?? "unknown", chunkCount: r.chunk_count ?? 0,
|
|
2083
|
+
visibility: r.visibility, ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", chunkCount: r.chunk_count ?? 0,
|
|
1867
2084
|
createdAt: r.created_at, updatedAt: r.updated_at,
|
|
1868
2085
|
}));
|
|
1869
2086
|
}
|
|
1870
2087
|
listAllHubTasks() {
|
|
1871
2088
|
const rows = this.db.prepare(`
|
|
1872
|
-
SELECT t.*, u.username AS owner_name,
|
|
2089
|
+
SELECT t.*, u.username AS owner_name, u.status AS owner_status,
|
|
1873
2090
|
(SELECT COUNT(*) FROM hub_chunks c WHERE c.hub_task_id = t.id) AS chunk_count
|
|
1874
2091
|
FROM hub_tasks t
|
|
1875
2092
|
LEFT JOIN hub_users u ON u.id = t.source_user_id
|
|
@@ -1878,7 +2095,7 @@ class SqliteStore {
|
|
|
1878
2095
|
return rows.map(r => ({
|
|
1879
2096
|
id: r.id, sourceTaskId: r.source_task_id, sourceUserId: r.source_user_id,
|
|
1880
2097
|
title: r.title, summary: r.summary, groupId: r.group_id, groupName: null,
|
|
1881
|
-
visibility: r.visibility, ownerName: r.owner_name ?? "unknown", chunkCount: r.chunk_count ?? 0,
|
|
2098
|
+
visibility: r.visibility, ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", chunkCount: r.chunk_count ?? 0,
|
|
1882
2099
|
createdAt: r.created_at, updatedAt: r.updated_at,
|
|
1883
2100
|
}));
|
|
1884
2101
|
}
|
|
@@ -1892,7 +2109,7 @@ class SqliteStore {
|
|
|
1892
2109
|
}
|
|
1893
2110
|
listVisibleHubSkills(userId, limit = 40) {
|
|
1894
2111
|
const rows = this.db.prepare(`
|
|
1895
|
-
SELECT s.*, u.username AS owner_name, NULL AS group_name
|
|
2112
|
+
SELECT s.*, u.username AS owner_name, u.status AS owner_status, NULL AS group_name
|
|
1896
2113
|
FROM hub_skills s
|
|
1897
2114
|
LEFT JOIN hub_users u ON u.id = s.source_user_id
|
|
1898
2115
|
ORDER BY s.updated_at DESC
|
|
@@ -1902,13 +2119,13 @@ class SqliteStore {
|
|
|
1902
2119
|
id: r.id, sourceSkillId: r.source_skill_id, sourceUserId: r.source_user_id,
|
|
1903
2120
|
name: r.name, description: r.description, version: r.version,
|
|
1904
2121
|
groupId: r.group_id, groupName: r.group_name ?? null, visibility: r.visibility,
|
|
1905
|
-
ownerName: r.owner_name ?? "unknown", qualityScore: r.quality_score,
|
|
2122
|
+
ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", qualityScore: r.quality_score,
|
|
1906
2123
|
createdAt: r.created_at, updatedAt: r.updated_at,
|
|
1907
2124
|
}));
|
|
1908
2125
|
}
|
|
1909
2126
|
listAllHubSkills() {
|
|
1910
2127
|
const rows = this.db.prepare(`
|
|
1911
|
-
SELECT s.*, u.username AS owner_name
|
|
2128
|
+
SELECT s.*, u.username AS owner_name, u.status AS owner_status
|
|
1912
2129
|
FROM hub_skills s
|
|
1913
2130
|
LEFT JOIN hub_users u ON u.id = s.source_user_id
|
|
1914
2131
|
ORDER BY s.updated_at DESC
|
|
@@ -1917,7 +2134,7 @@ class SqliteStore {
|
|
|
1917
2134
|
id: r.id, sourceSkillId: r.source_skill_id, sourceUserId: r.source_user_id,
|
|
1918
2135
|
name: r.name, description: r.description, version: r.version,
|
|
1919
2136
|
groupId: r.group_id, groupName: null, visibility: r.visibility,
|
|
1920
|
-
ownerName: r.owner_name ?? "unknown", qualityScore: r.quality_score,
|
|
2137
|
+
ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", qualityScore: r.quality_score,
|
|
1921
2138
|
createdAt: r.created_at, updatedAt: r.updated_at,
|
|
1922
2139
|
}));
|
|
1923
2140
|
}
|
|
@@ -1956,10 +2173,46 @@ class SqliteStore {
|
|
|
1956
2173
|
const info = this.db.prepare('DELETE FROM hub_memories WHERE id = ?').run(memoryId);
|
|
1957
2174
|
return info.changes > 0;
|
|
1958
2175
|
}
|
|
2176
|
+
// ─── Team share metadata (Client role — UI only, not used for local recall / FTS) ───
|
|
2177
|
+
upsertTeamSharedChunk(chunkId, row) {
|
|
2178
|
+
const now = Date.now();
|
|
2179
|
+
const vis = row.visibility === "group" ? "group" : "public";
|
|
2180
|
+
const gid = vis === "group" ? (row.groupId ?? null) : null;
|
|
2181
|
+
this.db.prepare(`
|
|
2182
|
+
INSERT INTO team_shared_chunks (chunk_id, hub_memory_id, visibility, group_id, shared_at)
|
|
2183
|
+
VALUES (?, ?, ?, ?, ?)
|
|
2184
|
+
ON CONFLICT(chunk_id) DO UPDATE SET
|
|
2185
|
+
hub_memory_id = excluded.hub_memory_id,
|
|
2186
|
+
visibility = excluded.visibility,
|
|
2187
|
+
group_id = excluded.group_id,
|
|
2188
|
+
shared_at = excluded.shared_at
|
|
2189
|
+
`).run(chunkId, row.hubMemoryId ?? "", vis, gid, now);
|
|
2190
|
+
}
|
|
2191
|
+
getTeamSharedChunk(chunkId) {
|
|
2192
|
+
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);
|
|
2193
|
+
if (!r)
|
|
2194
|
+
return null;
|
|
2195
|
+
return {
|
|
2196
|
+
chunkId: r.chunk_id,
|
|
2197
|
+
hubMemoryId: r.hub_memory_id,
|
|
2198
|
+
visibility: r.visibility,
|
|
2199
|
+
groupId: r.group_id,
|
|
2200
|
+
sharedAt: r.shared_at,
|
|
2201
|
+
};
|
|
2202
|
+
}
|
|
2203
|
+
deleteTeamSharedChunk(chunkId) {
|
|
2204
|
+
const info = this.db.prepare("DELETE FROM team_shared_chunks WHERE chunk_id = ?").run(chunkId);
|
|
2205
|
+
return info.changes > 0;
|
|
2206
|
+
}
|
|
1959
2207
|
// ─── Hub Notifications ───
|
|
1960
2208
|
insertHubNotification(n) {
|
|
1961
2209
|
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());
|
|
1962
2210
|
}
|
|
2211
|
+
hasRecentHubNotification(userId, type, resource, windowMs = 300_000) {
|
|
2212
|
+
const since = Date.now() - windowMs;
|
|
2213
|
+
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);
|
|
2214
|
+
return row.cnt > 0;
|
|
2215
|
+
}
|
|
1963
2216
|
listHubNotifications(userId, opts) {
|
|
1964
2217
|
const where = opts?.unreadOnly ? 'WHERE user_id = ? AND read = 0' : 'WHERE user_id = ?';
|
|
1965
2218
|
const limit = opts?.limit ?? 50;
|
|
@@ -2038,7 +2291,7 @@ class SqliteStore {
|
|
|
2038
2291
|
}
|
|
2039
2292
|
listVisibleHubMemories(userId, limit = 40) {
|
|
2040
2293
|
const rows = this.db.prepare(`
|
|
2041
|
-
SELECT m.*, u.username AS owner_name, NULL AS group_name
|
|
2294
|
+
SELECT m.*, u.username AS owner_name, u.status AS owner_status, NULL AS group_name
|
|
2042
2295
|
FROM hub_memories m
|
|
2043
2296
|
LEFT JOIN hub_users u ON u.id = m.source_user_id
|
|
2044
2297
|
ORDER BY m.updated_at DESC
|
|
@@ -2048,12 +2301,12 @@ class SqliteStore {
|
|
|
2048
2301
|
id: r.id, sourceChunkId: r.source_chunk_id, sourceUserId: r.source_user_id,
|
|
2049
2302
|
role: r.role, content: r.content ?? "", summary: r.summary, kind: r.kind,
|
|
2050
2303
|
groupId: r.group_id, groupName: r.group_name ?? null, visibility: r.visibility,
|
|
2051
|
-
ownerName: r.owner_name ?? "unknown", createdAt: r.created_at, updatedAt: r.updated_at,
|
|
2304
|
+
ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", createdAt: r.created_at, updatedAt: r.updated_at,
|
|
2052
2305
|
}));
|
|
2053
2306
|
}
|
|
2054
2307
|
listAllHubMemories() {
|
|
2055
2308
|
const rows = this.db.prepare(`
|
|
2056
|
-
SELECT m.*, u.username AS owner_name
|
|
2309
|
+
SELECT m.*, u.username AS owner_name, u.status AS owner_status
|
|
2057
2310
|
FROM hub_memories m
|
|
2058
2311
|
LEFT JOIN hub_users u ON u.id = m.source_user_id
|
|
2059
2312
|
ORDER BY m.updated_at DESC
|
|
@@ -2062,7 +2315,7 @@ class SqliteStore {
|
|
|
2062
2315
|
id: r.id, sourceChunkId: r.source_chunk_id, sourceUserId: r.source_user_id,
|
|
2063
2316
|
role: r.role, content: r.content ?? "", summary: r.summary, kind: r.kind,
|
|
2064
2317
|
groupId: r.group_id, groupName: null, visibility: r.visibility,
|
|
2065
|
-
ownerName: r.owner_name ?? "unknown", createdAt: r.created_at, updatedAt: r.updated_at,
|
|
2318
|
+
ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", createdAt: r.created_at, updatedAt: r.updated_at,
|
|
2066
2319
|
}));
|
|
2067
2320
|
}
|
|
2068
2321
|
resolveCanonicalHubTaskId(taskId, sourceUserId, sourceTaskId) {
|
|
@@ -2196,6 +2449,8 @@ function rowToClientHubConnection(row) {
|
|
|
2196
2449
|
userToken: row.user_token,
|
|
2197
2450
|
role: row.role,
|
|
2198
2451
|
connectedAt: row.connected_at,
|
|
2452
|
+
identityKey: row.identity_key || "",
|
|
2453
|
+
lastKnownStatus: row.last_known_status || "",
|
|
2199
2454
|
};
|
|
2200
2455
|
}
|
|
2201
2456
|
function rowToHubUser(row) {
|
|
@@ -2211,6 +2466,11 @@ function rowToHubUser(row) {
|
|
|
2211
2466
|
approvedAt: row.approved_at,
|
|
2212
2467
|
lastIp: row.last_ip || "",
|
|
2213
2468
|
lastActiveAt: row.last_active_at ?? null,
|
|
2469
|
+
identityKey: row.identity_key || "",
|
|
2470
|
+
leftAt: row.left_at ?? null,
|
|
2471
|
+
removedAt: row.removed_at ?? null,
|
|
2472
|
+
rejectedAt: row.rejected_at ?? null,
|
|
2473
|
+
rejoinRequestedAt: row.rejoin_requested_at ?? null,
|
|
2214
2474
|
};
|
|
2215
2475
|
}
|
|
2216
2476
|
function rowToHubTask(row) {
|