@memtensor/memos-local-openclaw-plugin 1.0.4-beta.1 → 1.0.4-beta.11
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 +2 -2
- package/dist/client/connector.d.ts.map +1 -1
- package/dist/client/connector.js +122 -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 +8 -0
- package/dist/hub/server.d.ts.map +1 -1
- package/dist/hub/server.js +390 -106
- package/dist/hub/server.js.map +1 -1
- package/dist/hub/user-manager.d.ts +11 -0
- package/dist/hub/user-manager.d.ts.map +1 -1
- package/dist/hub/user-manager.js +31 -3
- 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 +93 -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 +4 -0
- package/dist/skill/evolver.d.ts.map +1 -1
- package/dist/skill/evolver.js +59 -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/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 +89 -20
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +374 -124
- 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 +11 -2
- 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 +2671 -879
- 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 +990 -198
- package/dist/viewer/server.js.map +1 -1
- package/index.ts +700 -56
- 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 +124 -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 +374 -97
- package/src/hub/user-manager.ts +48 -8
- package/src/index.ts +10 -2
- package/src/ingest/providers/index.ts +41 -7
- package/src/recall/engine.ts +86 -1
- package/src/shared/llm-call.ts +97 -9
- package/src/sharing/types.ts +1 -1
- package/src/skill/evolver.ts +63 -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/ensure-binding.ts +52 -0
- package/src/storage/sqlite.ts +395 -148
- package/src/telemetry.ts +172 -41
- package/src/tools/memory-search.ts +2 -1
- package/src/types.ts +12 -2
- package/src/viewer/html.ts +2671 -879
- package/src/viewer/server.ts +913 -182
package/dist/storage/sqlite.js
CHANGED
|
@@ -146,11 +146,70 @@ class SqliteStore {
|
|
|
146
146
|
this.migrateSkillEmbeddingsAndFts();
|
|
147
147
|
this.migrateFtsToTrigram();
|
|
148
148
|
this.migrateHubTables();
|
|
149
|
+
this.migrateHubFtsToTrigram();
|
|
150
|
+
this.migrateLocalSharedTasksOwner();
|
|
151
|
+
this.migrateHubUserIdentityFields();
|
|
152
|
+
this.migrateClientHubConnectionIdentityFields();
|
|
149
153
|
this.log.debug("Database schema initialized");
|
|
150
154
|
}
|
|
151
155
|
migrateChunksIndexesForRecall() {
|
|
152
156
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_chunks_dedup_created ON chunks(dedup_status, created_at DESC)");
|
|
153
157
|
}
|
|
158
|
+
migrateLocalSharedTasksOwner() {
|
|
159
|
+
try {
|
|
160
|
+
const cols = this.db.prepare("PRAGMA table_info(local_shared_tasks)").all();
|
|
161
|
+
if (cols.length > 0 && !cols.some((c) => c.name === "original_owner")) {
|
|
162
|
+
this.db.exec("ALTER TABLE local_shared_tasks ADD COLUMN original_owner TEXT NOT NULL DEFAULT 'agent:main'");
|
|
163
|
+
this.log.info("Migrated: added original_owner column to local_shared_tasks");
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch { /* table may not exist yet */ }
|
|
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
|
+
}
|
|
154
213
|
migrateOwnerFields() {
|
|
155
214
|
const chunkCols = this.db.prepare("PRAGMA table_info(chunks)").all();
|
|
156
215
|
if (!chunkCols.some((c) => c.name === "owner")) {
|
|
@@ -298,6 +357,54 @@ class SqliteStore {
|
|
|
298
357
|
this.log.warn(`Failed to migrate skills_fts to trigram: ${err}`);
|
|
299
358
|
}
|
|
300
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
|
+
}
|
|
301
408
|
migrateTaskId() {
|
|
302
409
|
const cols = this.db.prepare("PRAGMA table_info(chunks)").all();
|
|
303
410
|
if (!cols.some((c) => c.name === "task_id")) {
|
|
@@ -503,15 +610,16 @@ class SqliteStore {
|
|
|
503
610
|
recordToolCall(toolName, durationMs, success) {
|
|
504
611
|
this.db.prepare("INSERT INTO tool_calls (tool_name, duration_ms, success, called_at) VALUES (?, ?, ?, ?)").run(toolName, Math.round(durationMs), success ? 1 : 0, Date.now());
|
|
505
612
|
}
|
|
506
|
-
getToolMetrics(minutes) {
|
|
507
|
-
const since = Date.now() - minutes * 60 * 1000;
|
|
613
|
+
getToolMetrics(minutes, fromMs, toMs) {
|
|
614
|
+
const since = fromMs ?? (Date.now() - minutes * 60 * 1000);
|
|
615
|
+
const until = toMs ?? Date.now();
|
|
508
616
|
const rows = this.db.prepare(`SELECT tool_name,
|
|
509
617
|
duration_ms,
|
|
510
618
|
success,
|
|
511
619
|
strftime('%Y-%m-%d %H:%M', called_at/1000, 'unixepoch', 'localtime') as minute_key
|
|
512
620
|
FROM tool_calls
|
|
513
|
-
WHERE called_at >= ?
|
|
514
|
-
ORDER BY called_at`).all(since);
|
|
621
|
+
WHERE called_at >= ? AND called_at <= ?
|
|
622
|
+
ORDER BY called_at`).all(since, until);
|
|
515
623
|
const toolSet = new Set();
|
|
516
624
|
const minuteMap = new Map();
|
|
517
625
|
const aggMap = new Map();
|
|
@@ -644,34 +752,27 @@ class SqliteStore {
|
|
|
644
752
|
shared_at INTEGER NOT NULL
|
|
645
753
|
);
|
|
646
754
|
|
|
755
|
+
CREATE TABLE IF NOT EXISTS local_shared_memories (
|
|
756
|
+
chunk_id TEXT PRIMARY KEY REFERENCES chunks(id) ON DELETE CASCADE,
|
|
757
|
+
original_owner TEXT NOT NULL,
|
|
758
|
+
shared_at INTEGER NOT NULL
|
|
759
|
+
);
|
|
760
|
+
|
|
647
761
|
CREATE TABLE IF NOT EXISTS hub_users (
|
|
648
|
-
id
|
|
649
|
-
username
|
|
650
|
-
device_name
|
|
651
|
-
role
|
|
652
|
-
status
|
|
653
|
-
token_hash
|
|
654
|
-
created_at
|
|
655
|
-
approved_at
|
|
762
|
+
id TEXT PRIMARY KEY,
|
|
763
|
+
username TEXT NOT NULL UNIQUE,
|
|
764
|
+
device_name TEXT NOT NULL DEFAULT '',
|
|
765
|
+
role TEXT NOT NULL,
|
|
766
|
+
status TEXT NOT NULL,
|
|
767
|
+
token_hash TEXT NOT NULL DEFAULT '',
|
|
768
|
+
created_at INTEGER NOT NULL,
|
|
769
|
+
approved_at INTEGER,
|
|
770
|
+
last_ip TEXT NOT NULL DEFAULT '',
|
|
771
|
+
last_active_at INTEGER
|
|
656
772
|
);
|
|
657
773
|
CREATE INDEX IF NOT EXISTS idx_hub_users_status ON hub_users(status);
|
|
658
774
|
CREATE INDEX IF NOT EXISTS idx_hub_users_role ON hub_users(role);
|
|
659
775
|
|
|
660
|
-
CREATE TABLE IF NOT EXISTS hub_groups (
|
|
661
|
-
id TEXT PRIMARY KEY,
|
|
662
|
-
name TEXT NOT NULL UNIQUE,
|
|
663
|
-
description TEXT NOT NULL DEFAULT '',
|
|
664
|
-
created_at INTEGER NOT NULL
|
|
665
|
-
);
|
|
666
|
-
|
|
667
|
-
CREATE TABLE IF NOT EXISTS hub_group_members (
|
|
668
|
-
group_id TEXT NOT NULL REFERENCES hub_groups(id) ON DELETE CASCADE,
|
|
669
|
-
user_id TEXT NOT NULL REFERENCES hub_users(id) ON DELETE CASCADE,
|
|
670
|
-
joined_at INTEGER NOT NULL,
|
|
671
|
-
PRIMARY KEY (group_id, user_id)
|
|
672
|
-
);
|
|
673
|
-
CREATE INDEX IF NOT EXISTS idx_hub_group_members_user ON hub_group_members(user_id);
|
|
674
|
-
|
|
675
776
|
CREATE TABLE IF NOT EXISTS hub_tasks (
|
|
676
777
|
id TEXT PRIMARY KEY,
|
|
677
778
|
source_task_id TEXT NOT NULL,
|
|
@@ -713,7 +814,7 @@ class SqliteStore {
|
|
|
713
814
|
content,
|
|
714
815
|
content='hub_chunks',
|
|
715
816
|
content_rowid='rowid',
|
|
716
|
-
tokenize='
|
|
817
|
+
tokenize='trigram'
|
|
717
818
|
);
|
|
718
819
|
|
|
719
820
|
CREATE TRIGGER IF NOT EXISTS hub_chunks_ai AFTER INSERT ON hub_chunks BEGIN
|
|
@@ -763,7 +864,7 @@ class SqliteStore {
|
|
|
763
864
|
description,
|
|
764
865
|
content='hub_skills',
|
|
765
866
|
content_rowid='rowid',
|
|
766
|
-
tokenize='
|
|
867
|
+
tokenize='trigram'
|
|
767
868
|
);
|
|
768
869
|
|
|
769
870
|
CREATE TRIGGER IF NOT EXISTS hub_skills_ai AFTER INSERT ON hub_skills BEGIN
|
|
@@ -813,7 +914,7 @@ class SqliteStore {
|
|
|
813
914
|
content,
|
|
814
915
|
content='hub_memories',
|
|
815
916
|
content_rowid='rowid',
|
|
816
|
-
tokenize='
|
|
917
|
+
tokenize='trigram'
|
|
817
918
|
);
|
|
818
919
|
|
|
819
920
|
CREATE TRIGGER IF NOT EXISTS hub_memories_ai AFTER INSERT ON hub_memories BEGIN
|
|
@@ -833,6 +934,31 @@ class SqliteStore {
|
|
|
833
934
|
VALUES (new.rowid, new.summary, new.content);
|
|
834
935
|
END;
|
|
835
936
|
`);
|
|
937
|
+
this.db.exec(`
|
|
938
|
+
CREATE TABLE IF NOT EXISTS hub_notifications (
|
|
939
|
+
id TEXT PRIMARY KEY,
|
|
940
|
+
user_id TEXT NOT NULL,
|
|
941
|
+
type TEXT NOT NULL,
|
|
942
|
+
resource TEXT NOT NULL,
|
|
943
|
+
title TEXT NOT NULL,
|
|
944
|
+
message TEXT NOT NULL DEFAULT '',
|
|
945
|
+
read INTEGER NOT NULL DEFAULT 0,
|
|
946
|
+
created_at INTEGER NOT NULL
|
|
947
|
+
);
|
|
948
|
+
CREATE INDEX IF NOT EXISTS idx_hub_notif_user ON hub_notifications(user_id, read, created_at DESC);
|
|
949
|
+
`);
|
|
950
|
+
try {
|
|
951
|
+
const cols = this.db.prepare("PRAGMA table_info(hub_users)").all();
|
|
952
|
+
if (cols.length > 0 && !cols.some(c => c.name === "last_ip")) {
|
|
953
|
+
this.db.exec("ALTER TABLE hub_users ADD COLUMN last_ip TEXT NOT NULL DEFAULT ''");
|
|
954
|
+
this.log.info("Migrated: added last_ip column to hub_users");
|
|
955
|
+
}
|
|
956
|
+
if (cols.length > 0 && !cols.some(c => c.name === "last_active_at")) {
|
|
957
|
+
this.db.exec("ALTER TABLE hub_users ADD COLUMN last_active_at INTEGER");
|
|
958
|
+
this.log.info("Migrated: added last_active_at column to hub_users");
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
catch { /* table may not exist yet */ }
|
|
836
962
|
}
|
|
837
963
|
// ─── Write ───
|
|
838
964
|
insertChunk(chunk) {
|
|
@@ -959,6 +1085,30 @@ class SqliteStore {
|
|
|
959
1085
|
return [];
|
|
960
1086
|
}
|
|
961
1087
|
}
|
|
1088
|
+
hubMemoryPatternSearch(patterns, opts = {}) {
|
|
1089
|
+
if (patterns.length === 0)
|
|
1090
|
+
return [];
|
|
1091
|
+
const limit = opts.limit ?? 10;
|
|
1092
|
+
const conditions = patterns.map(() => "(hm.content LIKE ? OR hm.summary LIKE ?)");
|
|
1093
|
+
const params = [];
|
|
1094
|
+
for (const p of patterns) {
|
|
1095
|
+
params.push(`%${p}%`, `%${p}%`);
|
|
1096
|
+
}
|
|
1097
|
+
params.push(limit);
|
|
1098
|
+
try {
|
|
1099
|
+
const rows = this.db.prepare(`
|
|
1100
|
+
SELECT hm.id as memory_id, hm.content, hm.role, hm.created_at
|
|
1101
|
+
FROM hub_memories hm
|
|
1102
|
+
WHERE ${conditions.join(" OR ")}
|
|
1103
|
+
ORDER BY hm.created_at DESC
|
|
1104
|
+
LIMIT ?
|
|
1105
|
+
`).all(...params);
|
|
1106
|
+
return rows.map(r => ({ memoryId: r.memory_id, content: r.content, role: r.role, createdAt: r.created_at }));
|
|
1107
|
+
}
|
|
1108
|
+
catch {
|
|
1109
|
+
return [];
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
962
1112
|
// ─── Vector Search ───
|
|
963
1113
|
getAllEmbeddings(ownerFilter) {
|
|
964
1114
|
let sql = `SELECT e.chunk_id, e.vector, e.dimensions FROM embeddings e
|
|
@@ -1097,6 +1247,8 @@ class SqliteStore {
|
|
|
1097
1247
|
"skill_embeddings",
|
|
1098
1248
|
"skill_versions",
|
|
1099
1249
|
"skills",
|
|
1250
|
+
"local_shared_memories",
|
|
1251
|
+
"local_shared_tasks",
|
|
1100
1252
|
"embeddings",
|
|
1101
1253
|
"chunks",
|
|
1102
1254
|
"tasks",
|
|
@@ -1475,16 +1627,18 @@ class SqliteStore {
|
|
|
1475
1627
|
// ─── Hub / Client connection ───
|
|
1476
1628
|
setClientHubConnection(conn) {
|
|
1477
1629
|
this.db.prepare(`
|
|
1478
|
-
INSERT INTO client_hub_connection (id, hub_url, user_id, username, user_token, role, connected_at)
|
|
1479
|
-
VALUES (1, ?, ?, ?, ?, ?, ?)
|
|
1630
|
+
INSERT INTO client_hub_connection (id, hub_url, user_id, username, user_token, role, connected_at, identity_key, last_known_status)
|
|
1631
|
+
VALUES (1, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1480
1632
|
ON CONFLICT(id) DO UPDATE SET
|
|
1481
1633
|
hub_url = excluded.hub_url,
|
|
1482
1634
|
user_id = excluded.user_id,
|
|
1483
1635
|
username = excluded.username,
|
|
1484
1636
|
user_token = excluded.user_token,
|
|
1485
1637
|
role = excluded.role,
|
|
1486
|
-
connected_at = excluded.connected_at
|
|
1487
|
-
|
|
1638
|
+
connected_at = excluded.connected_at,
|
|
1639
|
+
identity_key = excluded.identity_key,
|
|
1640
|
+
last_known_status = excluded.last_known_status
|
|
1641
|
+
`).run(conn.hubUrl, conn.userId, conn.username, conn.userToken, conn.role, conn.connectedAt, conn.identityKey ?? "", conn.lastKnownStatus ?? "");
|
|
1488
1642
|
}
|
|
1489
1643
|
getClientHubConnection() {
|
|
1490
1644
|
const row = this.db.prepare('SELECT * FROM client_hub_connection WHERE id = 1').get();
|
|
@@ -1519,11 +1673,66 @@ class SqliteStore {
|
|
|
1519
1673
|
const rows = this.db.prepare('SELECT task_id, hub_task_id, visibility, group_id, synced_chunks FROM local_shared_tasks').all();
|
|
1520
1674
|
return rows.map(r => ({ taskId: r.task_id, hubTaskId: r.hub_task_id, visibility: r.visibility, groupId: r.group_id, syncedChunks: r.synced_chunks }));
|
|
1521
1675
|
}
|
|
1676
|
+
// ─── Local Shared Memories (client-side tracking) ───
|
|
1677
|
+
markMemorySharedLocally(chunkId) {
|
|
1678
|
+
const chunk = this.getChunk(chunkId);
|
|
1679
|
+
if (!chunk)
|
|
1680
|
+
return { ok: false, reason: "not_found" };
|
|
1681
|
+
if (chunk.owner === "public") {
|
|
1682
|
+
const existing = this.getLocalSharedMemory(chunkId);
|
|
1683
|
+
return {
|
|
1684
|
+
ok: true,
|
|
1685
|
+
owner: "public",
|
|
1686
|
+
originalOwner: existing?.originalOwner ?? undefined,
|
|
1687
|
+
sharedAt: existing?.sharedAt ?? undefined,
|
|
1688
|
+
};
|
|
1689
|
+
}
|
|
1690
|
+
const sharedAt = Date.now();
|
|
1691
|
+
this.db.transaction(() => {
|
|
1692
|
+
this.db.prepare(`
|
|
1693
|
+
INSERT INTO local_shared_memories (chunk_id, original_owner, shared_at)
|
|
1694
|
+
VALUES (?, ?, ?)
|
|
1695
|
+
ON CONFLICT(chunk_id) DO UPDATE SET
|
|
1696
|
+
original_owner = excluded.original_owner,
|
|
1697
|
+
shared_at = excluded.shared_at
|
|
1698
|
+
`).run(chunkId, chunk.owner, sharedAt);
|
|
1699
|
+
this.updateChunk(chunkId, { owner: "public" });
|
|
1700
|
+
})();
|
|
1701
|
+
return { ok: true, owner: "public", originalOwner: chunk.owner, sharedAt };
|
|
1702
|
+
}
|
|
1703
|
+
unmarkMemorySharedLocally(chunkId, fallbackOwner) {
|
|
1704
|
+
const chunk = this.getChunk(chunkId);
|
|
1705
|
+
if (!chunk)
|
|
1706
|
+
return { ok: false, reason: "not_found" };
|
|
1707
|
+
if (chunk.owner !== "public") {
|
|
1708
|
+
return { ok: true, owner: chunk.owner };
|
|
1709
|
+
}
|
|
1710
|
+
const existing = this.getLocalSharedMemory(chunkId);
|
|
1711
|
+
const restoreOwner = existing?.originalOwner ?? fallbackOwner;
|
|
1712
|
+
if (!restoreOwner || restoreOwner === "public") {
|
|
1713
|
+
return { ok: false, reason: "original_owner_missing" };
|
|
1714
|
+
}
|
|
1715
|
+
this.db.transaction(() => {
|
|
1716
|
+
this.updateChunk(chunkId, { owner: restoreOwner });
|
|
1717
|
+
this.db.prepare("DELETE FROM local_shared_memories WHERE chunk_id = ?").run(chunkId);
|
|
1718
|
+
})();
|
|
1719
|
+
return { ok: true, owner: restoreOwner, originalOwner: restoreOwner };
|
|
1720
|
+
}
|
|
1721
|
+
getLocalSharedMemory(chunkId) {
|
|
1722
|
+
const row = this.db.prepare("SELECT chunk_id, original_owner, shared_at FROM local_shared_memories WHERE chunk_id = ?").get(chunkId);
|
|
1723
|
+
if (!row)
|
|
1724
|
+
return null;
|
|
1725
|
+
return {
|
|
1726
|
+
chunkId: row.chunk_id,
|
|
1727
|
+
originalOwner: row.original_owner,
|
|
1728
|
+
sharedAt: row.shared_at,
|
|
1729
|
+
};
|
|
1730
|
+
}
|
|
1522
1731
|
// ─── Hub Users / Groups ───
|
|
1523
1732
|
upsertHubUser(user) {
|
|
1524
1733
|
this.db.prepare(`
|
|
1525
|
-
INSERT INTO hub_users (id, username, device_name, role, status, token_hash, created_at, approved_at)
|
|
1526
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
1734
|
+
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)
|
|
1735
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1527
1736
|
ON CONFLICT(id) DO UPDATE SET
|
|
1528
1737
|
username = excluded.username,
|
|
1529
1738
|
device_name = excluded.device_name,
|
|
@@ -1531,72 +1740,71 @@ class SqliteStore {
|
|
|
1531
1740
|
status = excluded.status,
|
|
1532
1741
|
token_hash = excluded.token_hash,
|
|
1533
1742
|
created_at = excluded.created_at,
|
|
1534
|
-
approved_at = excluded.approved_at
|
|
1535
|
-
|
|
1743
|
+
approved_at = excluded.approved_at,
|
|
1744
|
+
identity_key = excluded.identity_key,
|
|
1745
|
+
left_at = excluded.left_at,
|
|
1746
|
+
removed_at = excluded.removed_at,
|
|
1747
|
+
rejected_at = excluded.rejected_at,
|
|
1748
|
+
rejoin_requested_at = excluded.rejoin_requested_at
|
|
1749
|
+
`).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);
|
|
1536
1750
|
}
|
|
1537
1751
|
getHubUser(userId) {
|
|
1538
1752
|
const row = this.db.prepare('SELECT * FROM hub_users WHERE id = ?').get(userId);
|
|
1539
1753
|
if (!row)
|
|
1540
1754
|
return null;
|
|
1541
|
-
return
|
|
1755
|
+
return rowToHubUser(row);
|
|
1542
1756
|
}
|
|
1543
1757
|
listHubUsers(status) {
|
|
1544
1758
|
const rows = status
|
|
1545
1759
|
? this.db.prepare('SELECT * FROM hub_users WHERE status = ? ORDER BY created_at').all(status)
|
|
1546
1760
|
: this.db.prepare('SELECT * FROM hub_users ORDER BY created_at').all();
|
|
1547
|
-
return rows.map(
|
|
1548
|
-
}
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
listHubGroups() {
|
|
1560
|
-
const rows = this.db.prepare('SELECT * FROM hub_groups ORDER BY name').all();
|
|
1561
|
-
return rows.map(rowToHubGroup);
|
|
1562
|
-
}
|
|
1563
|
-
addHubGroupMember(groupId, userId, joinedAt = Date.now()) {
|
|
1564
|
-
this.db.prepare(`
|
|
1565
|
-
INSERT INTO hub_group_members (group_id, user_id, joined_at)
|
|
1566
|
-
VALUES (?, ?, ?)
|
|
1567
|
-
ON CONFLICT(group_id, user_id) DO UPDATE SET joined_at = excluded.joined_at
|
|
1568
|
-
`).run(groupId, userId, joinedAt);
|
|
1761
|
+
return rows.map(rowToHubUser);
|
|
1762
|
+
}
|
|
1763
|
+
deleteHubUser(userId, cleanResources = false) {
|
|
1764
|
+
if (cleanResources) {
|
|
1765
|
+
this.db.prepare('DELETE FROM hub_tasks WHERE source_user_id = ?').run(userId);
|
|
1766
|
+
this.db.prepare('DELETE FROM hub_skills WHERE source_user_id = ?').run(userId);
|
|
1767
|
+
this.db.prepare('DELETE FROM hub_memories WHERE source_user_id = ?').run(userId);
|
|
1768
|
+
const result = this.db.prepare('DELETE FROM hub_users WHERE id = ?').run(userId);
|
|
1769
|
+
return result.changes > 0;
|
|
1770
|
+
}
|
|
1771
|
+
const result = this.db.prepare("UPDATE hub_users SET status = 'removed', token_hash = '', removed_at = ? WHERE id = ?").run(Date.now(), userId);
|
|
1772
|
+
return result.changes > 0;
|
|
1569
1773
|
}
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1774
|
+
findHubUserByIdentityKey(identityKey) {
|
|
1775
|
+
if (!identityKey)
|
|
1776
|
+
return null;
|
|
1777
|
+
const row = this.db.prepare('SELECT * FROM hub_users WHERE identity_key = ?').get(identityKey);
|
|
1778
|
+
return row ? rowToHubUser(row) : null;
|
|
1573
1779
|
}
|
|
1574
|
-
|
|
1575
|
-
const result = this.db.prepare('
|
|
1780
|
+
markHubUserLeft(userId) {
|
|
1781
|
+
const result = this.db.prepare("UPDATE hub_users SET status = 'left', token_hash = '', left_at = ? WHERE id = ?").run(Date.now(), userId);
|
|
1576
1782
|
return result.changes > 0;
|
|
1577
1783
|
}
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1784
|
+
updateHubUserActivity(userId, ip, timestamp) {
|
|
1785
|
+
this.db.prepare('UPDATE hub_users SET last_ip = ?, last_active_at = ? WHERE id = ?').run(ip, timestamp ?? Date.now(), userId);
|
|
1786
|
+
}
|
|
1787
|
+
getHubUserContributions() {
|
|
1788
|
+
const result = {};
|
|
1789
|
+
const memRows = this.db.prepare('SELECT source_user_id, COUNT(*) as cnt FROM hub_memories GROUP BY source_user_id').all();
|
|
1790
|
+
const taskRows = this.db.prepare('SELECT source_user_id, COUNT(*) as cnt FROM hub_tasks GROUP BY source_user_id').all();
|
|
1791
|
+
const skillRows = this.db.prepare('SELECT source_user_id, COUNT(*) as cnt FROM hub_skills GROUP BY source_user_id').all();
|
|
1792
|
+
for (const r of memRows) {
|
|
1793
|
+
if (!result[r.source_user_id])
|
|
1794
|
+
result[r.source_user_id] = { memoryCount: 0, taskCount: 0, skillCount: 0 };
|
|
1795
|
+
result[r.source_user_id].memoryCount = r.cnt;
|
|
1796
|
+
}
|
|
1797
|
+
for (const r of taskRows) {
|
|
1798
|
+
if (!result[r.source_user_id])
|
|
1799
|
+
result[r.source_user_id] = { memoryCount: 0, taskCount: 0, skillCount: 0 };
|
|
1800
|
+
result[r.source_user_id].taskCount = r.cnt;
|
|
1801
|
+
}
|
|
1802
|
+
for (const r of skillRows) {
|
|
1803
|
+
if (!result[r.source_user_id])
|
|
1804
|
+
result[r.source_user_id] = { memoryCount: 0, taskCount: 0, skillCount: 0 };
|
|
1805
|
+
result[r.source_user_id].skillCount = r.cnt;
|
|
1806
|
+
}
|
|
1807
|
+
return result;
|
|
1600
1808
|
}
|
|
1601
1809
|
// ─── Hub Shared Data ───
|
|
1602
1810
|
upsertHubTask(task) {
|
|
@@ -1616,6 +1824,10 @@ class SqliteStore {
|
|
|
1616
1824
|
const row = this.db.prepare('SELECT * FROM hub_tasks WHERE source_user_id = ? AND source_task_id = ?').get(sourceUserId, sourceTaskId);
|
|
1617
1825
|
return row ? rowToHubTask(row) : null;
|
|
1618
1826
|
}
|
|
1827
|
+
getHubTaskById(taskId) {
|
|
1828
|
+
const row = this.db.prepare('SELECT * FROM hub_tasks WHERE id = ?').get(taskId);
|
|
1829
|
+
return row ? rowToHubTask(row) : null;
|
|
1830
|
+
}
|
|
1619
1831
|
upsertHubChunk(chunk) {
|
|
1620
1832
|
if (!chunk.sourceTaskId)
|
|
1621
1833
|
throw new Error("sourceTaskId is required for hub chunk upserts");
|
|
@@ -1688,6 +1900,17 @@ class SqliteStore {
|
|
|
1688
1900
|
out.push(row.vector.readFloatLE(i * 4));
|
|
1689
1901
|
return out;
|
|
1690
1902
|
}
|
|
1903
|
+
getVisibleHubSkillEmbeddings() {
|
|
1904
|
+
const rows = this.db.prepare(`
|
|
1905
|
+
SELECT hse.skill_id, hse.vector, hse.dimensions
|
|
1906
|
+
FROM hub_skill_embeddings hse
|
|
1907
|
+
JOIN hub_skills hs ON hs.id = hse.skill_id
|
|
1908
|
+
`).all();
|
|
1909
|
+
return rows.map(r => ({
|
|
1910
|
+
skillId: r.skill_id,
|
|
1911
|
+
vector: new Float32Array(r.vector.buffer, r.vector.byteOffset, r.dimensions),
|
|
1912
|
+
}));
|
|
1913
|
+
}
|
|
1691
1914
|
searchHubChunks(query, options) {
|
|
1692
1915
|
const limit = options?.maxResults ?? 10;
|
|
1693
1916
|
const userId = options?.userId ?? "";
|
|
@@ -1753,7 +1976,7 @@ class SqliteStore {
|
|
|
1753
1976
|
let rows;
|
|
1754
1977
|
if (sanitized) {
|
|
1755
1978
|
rows = this.db.prepare(`
|
|
1756
|
-
SELECT hs.id, hs.name, hs.description, hs.version, hs.visibility, '' AS group_name, hu.username AS owner_name, hs.quality_score,
|
|
1979
|
+
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,
|
|
1757
1980
|
bm25(hub_skills_fts) as rank
|
|
1758
1981
|
FROM hub_skills_fts f
|
|
1759
1982
|
JOIN hub_skills hs ON hs.rowid = f.rowid
|
|
@@ -1765,7 +1988,7 @@ class SqliteStore {
|
|
|
1765
1988
|
}
|
|
1766
1989
|
else {
|
|
1767
1990
|
rows = this.db.prepare(`
|
|
1768
|
-
SELECT hs.id, hs.name, hs.description, hs.version, hs.visibility, '' AS group_name, hu.username AS owner_name, hs.quality_score,
|
|
1991
|
+
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,
|
|
1769
1992
|
0 as rank
|
|
1770
1993
|
FROM hub_skills hs
|
|
1771
1994
|
LEFT JOIN hub_users hu ON hu.id = hs.source_user_id
|
|
@@ -1780,7 +2003,7 @@ class SqliteStore {
|
|
|
1780
2003
|
}
|
|
1781
2004
|
listVisibleHubTasks(userId, limit = 40) {
|
|
1782
2005
|
const rows = this.db.prepare(`
|
|
1783
|
-
SELECT t.*, u.username AS owner_name, NULL AS group_name,
|
|
2006
|
+
SELECT t.*, u.username AS owner_name, u.status AS owner_status, NULL AS group_name,
|
|
1784
2007
|
(SELECT COUNT(*) FROM hub_chunks c WHERE c.hub_task_id = t.id) AS chunk_count
|
|
1785
2008
|
FROM hub_tasks t
|
|
1786
2009
|
LEFT JOIN hub_users u ON u.id = t.source_user_id
|
|
@@ -1790,33 +2013,36 @@ class SqliteStore {
|
|
|
1790
2013
|
return rows.map(r => ({
|
|
1791
2014
|
id: r.id, sourceTaskId: r.source_task_id, sourceUserId: r.source_user_id,
|
|
1792
2015
|
title: r.title, summary: r.summary, groupId: r.group_id, groupName: r.group_name ?? null,
|
|
1793
|
-
visibility: r.visibility, ownerName: r.owner_name ?? "unknown", chunkCount: r.chunk_count ?? 0,
|
|
2016
|
+
visibility: r.visibility, ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", chunkCount: r.chunk_count ?? 0,
|
|
1794
2017
|
createdAt: r.created_at, updatedAt: r.updated_at,
|
|
1795
2018
|
}));
|
|
1796
2019
|
}
|
|
1797
2020
|
listAllHubTasks() {
|
|
1798
2021
|
const rows = this.db.prepare(`
|
|
1799
|
-
SELECT t.*, u.username AS owner_name,
|
|
2022
|
+
SELECT t.*, u.username AS owner_name, u.status AS owner_status,
|
|
1800
2023
|
(SELECT COUNT(*) FROM hub_chunks c WHERE c.hub_task_id = t.id) AS chunk_count
|
|
1801
2024
|
FROM hub_tasks t
|
|
1802
2025
|
LEFT JOIN hub_users u ON u.id = t.source_user_id
|
|
1803
|
-
LEFT JOIN hub_groups g ON g.id = t.group_id
|
|
1804
2026
|
ORDER BY t.updated_at DESC
|
|
1805
2027
|
`).all();
|
|
1806
2028
|
return rows.map(r => ({
|
|
1807
2029
|
id: r.id, sourceTaskId: r.source_task_id, sourceUserId: r.source_user_id,
|
|
1808
|
-
title: r.title, summary: r.summary, groupId: r.group_id, groupName:
|
|
1809
|
-
visibility: r.visibility, ownerName: r.owner_name ?? "unknown", chunkCount: r.chunk_count ?? 0,
|
|
2030
|
+
title: r.title, summary: r.summary, groupId: r.group_id, groupName: null,
|
|
2031
|
+
visibility: r.visibility, ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", chunkCount: r.chunk_count ?? 0,
|
|
1810
2032
|
createdAt: r.created_at, updatedAt: r.updated_at,
|
|
1811
2033
|
}));
|
|
1812
2034
|
}
|
|
2035
|
+
listHubChunksByTaskId(hubTaskId) {
|
|
2036
|
+
const rows = this.db.prepare('SELECT * FROM hub_chunks WHERE hub_task_id = ? ORDER BY created_at ASC').all(hubTaskId);
|
|
2037
|
+
return rows.map(rowToHubChunk);
|
|
2038
|
+
}
|
|
1813
2039
|
deleteHubTaskById(taskId) {
|
|
1814
2040
|
const info = this.db.prepare('DELETE FROM hub_tasks WHERE id = ?').run(taskId);
|
|
1815
2041
|
return info.changes > 0;
|
|
1816
2042
|
}
|
|
1817
2043
|
listVisibleHubSkills(userId, limit = 40) {
|
|
1818
2044
|
const rows = this.db.prepare(`
|
|
1819
|
-
SELECT s.*, u.username AS owner_name, NULL AS group_name
|
|
2045
|
+
SELECT s.*, u.username AS owner_name, u.status AS owner_status, NULL AS group_name
|
|
1820
2046
|
FROM hub_skills s
|
|
1821
2047
|
LEFT JOIN hub_users u ON u.id = s.source_user_id
|
|
1822
2048
|
ORDER BY s.updated_at DESC
|
|
@@ -1826,23 +2052,22 @@ class SqliteStore {
|
|
|
1826
2052
|
id: r.id, sourceSkillId: r.source_skill_id, sourceUserId: r.source_user_id,
|
|
1827
2053
|
name: r.name, description: r.description, version: r.version,
|
|
1828
2054
|
groupId: r.group_id, groupName: r.group_name ?? null, visibility: r.visibility,
|
|
1829
|
-
ownerName: r.owner_name ?? "unknown", qualityScore: r.quality_score,
|
|
2055
|
+
ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", qualityScore: r.quality_score,
|
|
1830
2056
|
createdAt: r.created_at, updatedAt: r.updated_at,
|
|
1831
2057
|
}));
|
|
1832
2058
|
}
|
|
1833
2059
|
listAllHubSkills() {
|
|
1834
2060
|
const rows = this.db.prepare(`
|
|
1835
|
-
SELECT s.*, u.username AS owner_name,
|
|
2061
|
+
SELECT s.*, u.username AS owner_name, u.status AS owner_status
|
|
1836
2062
|
FROM hub_skills s
|
|
1837
2063
|
LEFT JOIN hub_users u ON u.id = s.source_user_id
|
|
1838
|
-
LEFT JOIN hub_groups g ON g.id = s.group_id
|
|
1839
2064
|
ORDER BY s.updated_at DESC
|
|
1840
2065
|
`).all();
|
|
1841
2066
|
return rows.map(r => ({
|
|
1842
2067
|
id: r.id, sourceSkillId: r.source_skill_id, sourceUserId: r.source_user_id,
|
|
1843
2068
|
name: r.name, description: r.description, version: r.version,
|
|
1844
|
-
groupId: r.group_id, groupName:
|
|
1845
|
-
ownerName: r.owner_name ?? "unknown", qualityScore: r.quality_score,
|
|
2069
|
+
groupId: r.group_id, groupName: null, visibility: r.visibility,
|
|
2070
|
+
ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", qualityScore: r.quality_score,
|
|
1846
2071
|
createdAt: r.created_at, updatedAt: r.updated_at,
|
|
1847
2072
|
}));
|
|
1848
2073
|
}
|
|
@@ -1881,6 +2106,37 @@ class SqliteStore {
|
|
|
1881
2106
|
const info = this.db.prepare('DELETE FROM hub_memories WHERE id = ?').run(memoryId);
|
|
1882
2107
|
return info.changes > 0;
|
|
1883
2108
|
}
|
|
2109
|
+
// ─── Hub Notifications ───
|
|
2110
|
+
insertHubNotification(n) {
|
|
2111
|
+
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());
|
|
2112
|
+
}
|
|
2113
|
+
hasRecentHubNotification(userId, type, resource, windowMs = 300_000) {
|
|
2114
|
+
const since = Date.now() - windowMs;
|
|
2115
|
+
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);
|
|
2116
|
+
return row.cnt > 0;
|
|
2117
|
+
}
|
|
2118
|
+
listHubNotifications(userId, opts) {
|
|
2119
|
+
const where = opts?.unreadOnly ? 'WHERE user_id = ? AND read = 0' : 'WHERE user_id = ?';
|
|
2120
|
+
const limit = opts?.limit ?? 50;
|
|
2121
|
+
const rows = this.db.prepare(`SELECT * FROM hub_notifications ${where} ORDER BY created_at DESC LIMIT ?`).all(userId, limit);
|
|
2122
|
+
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 }));
|
|
2123
|
+
}
|
|
2124
|
+
countUnreadHubNotifications(userId) {
|
|
2125
|
+
const row = this.db.prepare('SELECT COUNT(*) AS cnt FROM hub_notifications WHERE user_id = ? AND read = 0').get(userId);
|
|
2126
|
+
return row.cnt;
|
|
2127
|
+
}
|
|
2128
|
+
markHubNotificationsRead(userId, ids) {
|
|
2129
|
+
if (ids && ids.length > 0) {
|
|
2130
|
+
const placeholders = ids.map(() => '?').join(',');
|
|
2131
|
+
this.db.prepare(`UPDATE hub_notifications SET read = 1 WHERE user_id = ? AND id IN (${placeholders})`).run(userId, ...ids);
|
|
2132
|
+
}
|
|
2133
|
+
else {
|
|
2134
|
+
this.db.prepare('UPDATE hub_notifications SET read = 1 WHERE user_id = ?').run(userId);
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
clearHubNotifications(userId) {
|
|
2138
|
+
this.db.prepare('DELETE FROM hub_notifications WHERE user_id = ?').run(userId);
|
|
2139
|
+
}
|
|
1884
2140
|
upsertHubMemoryEmbedding(memoryId, vector) {
|
|
1885
2141
|
const buf = Buffer.from(vector.buffer, vector.byteOffset, vector.byteLength);
|
|
1886
2142
|
this.db.prepare(`
|
|
@@ -1937,7 +2193,7 @@ class SqliteStore {
|
|
|
1937
2193
|
}
|
|
1938
2194
|
listVisibleHubMemories(userId, limit = 40) {
|
|
1939
2195
|
const rows = this.db.prepare(`
|
|
1940
|
-
SELECT m.*, u.username AS owner_name, NULL AS group_name
|
|
2196
|
+
SELECT m.*, u.username AS owner_name, u.status AS owner_status, NULL AS group_name
|
|
1941
2197
|
FROM hub_memories m
|
|
1942
2198
|
LEFT JOIN hub_users u ON u.id = m.source_user_id
|
|
1943
2199
|
ORDER BY m.updated_at DESC
|
|
@@ -1945,24 +2201,23 @@ class SqliteStore {
|
|
|
1945
2201
|
`).all(limit);
|
|
1946
2202
|
return rows.map(r => ({
|
|
1947
2203
|
id: r.id, sourceChunkId: r.source_chunk_id, sourceUserId: r.source_user_id,
|
|
1948
|
-
role: r.role, summary: r.summary, kind: r.kind,
|
|
2204
|
+
role: r.role, content: r.content ?? "", summary: r.summary, kind: r.kind,
|
|
1949
2205
|
groupId: r.group_id, groupName: r.group_name ?? null, visibility: r.visibility,
|
|
1950
|
-
ownerName: r.owner_name ?? "unknown", createdAt: r.created_at, updatedAt: r.updated_at,
|
|
2206
|
+
ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", createdAt: r.created_at, updatedAt: r.updated_at,
|
|
1951
2207
|
}));
|
|
1952
2208
|
}
|
|
1953
2209
|
listAllHubMemories() {
|
|
1954
2210
|
const rows = this.db.prepare(`
|
|
1955
|
-
SELECT m.*, u.username AS owner_name,
|
|
2211
|
+
SELECT m.*, u.username AS owner_name, u.status AS owner_status
|
|
1956
2212
|
FROM hub_memories m
|
|
1957
2213
|
LEFT JOIN hub_users u ON u.id = m.source_user_id
|
|
1958
|
-
LEFT JOIN hub_groups g ON g.id = m.group_id
|
|
1959
2214
|
ORDER BY m.updated_at DESC
|
|
1960
2215
|
`).all();
|
|
1961
2216
|
return rows.map(r => ({
|
|
1962
2217
|
id: r.id, sourceChunkId: r.source_chunk_id, sourceUserId: r.source_user_id,
|
|
1963
|
-
role: r.role, summary: r.summary, kind: r.kind,
|
|
1964
|
-
groupId: r.group_id, groupName:
|
|
1965
|
-
ownerName: r.owner_name ?? "unknown", createdAt: r.created_at, updatedAt: r.updated_at,
|
|
2218
|
+
role: r.role, content: r.content ?? "", summary: r.summary, kind: r.kind,
|
|
2219
|
+
groupId: r.group_id, groupName: null, visibility: r.visibility,
|
|
2220
|
+
ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", createdAt: r.created_at, updatedAt: r.updated_at,
|
|
1966
2221
|
}));
|
|
1967
2222
|
}
|
|
1968
2223
|
resolveCanonicalHubTaskId(taskId, sourceUserId, sourceTaskId) {
|
|
@@ -1987,12 +2242,6 @@ class SqliteStore {
|
|
|
1987
2242
|
}
|
|
1988
2243
|
throw new Error(`source skill not found for skillId=${skillId}`);
|
|
1989
2244
|
}
|
|
1990
|
-
attachGroupsToHubUser(user) {
|
|
1991
|
-
return {
|
|
1992
|
-
...user,
|
|
1993
|
-
groups: this.getGroupsForHubUser(user.id),
|
|
1994
|
-
};
|
|
1995
|
-
}
|
|
1996
2245
|
getSessionOwnerMap(sessionKeys) {
|
|
1997
2246
|
const result = new Map();
|
|
1998
2247
|
if (sessionKeys.length === 0)
|
|
@@ -2102,6 +2351,8 @@ function rowToClientHubConnection(row) {
|
|
|
2102
2351
|
userToken: row.user_token,
|
|
2103
2352
|
role: row.role,
|
|
2104
2353
|
connectedAt: row.connected_at,
|
|
2354
|
+
identityKey: row.identity_key || "",
|
|
2355
|
+
lastKnownStatus: row.last_known_status || "",
|
|
2105
2356
|
};
|
|
2106
2357
|
}
|
|
2107
2358
|
function rowToHubUser(row) {
|
|
@@ -2115,14 +2366,13 @@ function rowToHubUser(row) {
|
|
|
2115
2366
|
tokenHash: row.token_hash,
|
|
2116
2367
|
createdAt: row.created_at,
|
|
2117
2368
|
approvedAt: row.approved_at,
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
createdAt: row.created_at,
|
|
2369
|
+
lastIp: row.last_ip || "",
|
|
2370
|
+
lastActiveAt: row.last_active_at ?? null,
|
|
2371
|
+
identityKey: row.identity_key || "",
|
|
2372
|
+
leftAt: row.left_at ?? null,
|
|
2373
|
+
removedAt: row.removed_at ?? null,
|
|
2374
|
+
rejectedAt: row.rejected_at ?? null,
|
|
2375
|
+
rejoinRequestedAt: row.rejoin_requested_at ?? null,
|
|
2126
2376
|
};
|
|
2127
2377
|
}
|
|
2128
2378
|
function rowToHubTask(row) {
|