@memtensor/memos-local-openclaw-plugin 1.0.7 → 1.0.8-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +4 -0
- package/index.ts +137 -87
- package/openclaw.plugin.json +1 -1
- package/package.json +4 -3
- package/scripts/postinstall.cjs +59 -25
- package/skill/memos-memory-guide/SKILL.md +5 -2
- package/src/client/hub.ts +11 -0
- package/src/hub/server.ts +13 -6
- package/src/ingest/providers/anthropic.ts +9 -6
- package/src/ingest/providers/bedrock.ts +9 -6
- package/src/ingest/providers/gemini.ts +9 -6
- package/src/ingest/providers/index.ts +136 -22
- package/src/ingest/providers/openai.ts +141 -6
- package/src/ingest/task-processor.ts +61 -41
- package/src/ingest/worker.ts +32 -11
- package/src/recall/engine.ts +2 -1
- package/src/shared/llm-call.ts +14 -1
- package/src/sharing/types.ts +1 -0
- package/src/storage/sqlite.ts +194 -11
- package/src/types.ts +3 -0
- package/src/viewer/html.ts +953 -281
- package/src/viewer/server.ts +305 -20
package/src/storage/sqlite.ts
CHANGED
|
@@ -110,9 +110,12 @@ export class SqliteStore {
|
|
|
110
110
|
this.migrateOwnerFields();
|
|
111
111
|
this.migrateSkillVisibility();
|
|
112
112
|
this.migrateSkillEmbeddingsAndFts();
|
|
113
|
+
this.migrateTaskTopicColumn();
|
|
114
|
+
this.migrateTaskEmbeddingsAndFts();
|
|
113
115
|
this.migrateFtsToTrigram();
|
|
114
116
|
this.migrateHubTables();
|
|
115
117
|
this.migrateHubFtsToTrigram();
|
|
118
|
+
this.migrateHubMemorySourceAgent();
|
|
116
119
|
this.migrateLocalSharedTasksOwner();
|
|
117
120
|
this.migrateHubUserIdentityFields();
|
|
118
121
|
this.migrateClientHubConnectionIdentityFields();
|
|
@@ -124,6 +127,16 @@ export class SqliteStore {
|
|
|
124
127
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_chunks_dedup_created ON chunks(dedup_status, created_at DESC)");
|
|
125
128
|
}
|
|
126
129
|
|
|
130
|
+
private migrateHubMemorySourceAgent(): void {
|
|
131
|
+
try {
|
|
132
|
+
const cols = this.db.prepare("PRAGMA table_info(hub_memories)").all() as Array<{ name: string }>;
|
|
133
|
+
if (cols.length > 0 && !cols.some((c) => c.name === "source_agent")) {
|
|
134
|
+
this.db.exec("ALTER TABLE hub_memories ADD COLUMN source_agent TEXT NOT NULL DEFAULT ''");
|
|
135
|
+
this.log.info("Migrated: added source_agent column to hub_memories");
|
|
136
|
+
}
|
|
137
|
+
} catch { /* table may not exist yet */ }
|
|
138
|
+
}
|
|
139
|
+
|
|
127
140
|
private migrateLocalSharedTasksOwner(): void {
|
|
128
141
|
try {
|
|
129
142
|
const cols = this.db.prepare("PRAGMA table_info(local_shared_tasks)").all() as Array<{ name: string }>;
|
|
@@ -290,6 +303,55 @@ export class SqliteStore {
|
|
|
290
303
|
} catch { /* best-effort */ }
|
|
291
304
|
}
|
|
292
305
|
|
|
306
|
+
private migrateTaskEmbeddingsAndFts(): void {
|
|
307
|
+
this.db.exec(`
|
|
308
|
+
CREATE TABLE IF NOT EXISTS task_embeddings (
|
|
309
|
+
task_id TEXT PRIMARY KEY REFERENCES tasks(id) ON DELETE CASCADE,
|
|
310
|
+
vector BLOB NOT NULL,
|
|
311
|
+
dimensions INTEGER NOT NULL,
|
|
312
|
+
updated_at INTEGER NOT NULL
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS tasks_fts USING fts5(
|
|
316
|
+
summary,
|
|
317
|
+
topic,
|
|
318
|
+
content='tasks',
|
|
319
|
+
content_rowid='rowid',
|
|
320
|
+
tokenize='trigram'
|
|
321
|
+
);
|
|
322
|
+
`);
|
|
323
|
+
|
|
324
|
+
try {
|
|
325
|
+
this.db.exec(`
|
|
326
|
+
CREATE TRIGGER IF NOT EXISTS tasks_fts_ai AFTER INSERT ON tasks BEGIN
|
|
327
|
+
INSERT INTO tasks_fts(rowid, summary, topic)
|
|
328
|
+
VALUES (new.rowid, new.summary, COALESCE(new.topic, ''));
|
|
329
|
+
END;
|
|
330
|
+
CREATE TRIGGER IF NOT EXISTS tasks_fts_ad AFTER DELETE ON tasks BEGIN
|
|
331
|
+
INSERT INTO tasks_fts(tasks_fts, rowid, summary, topic)
|
|
332
|
+
VALUES ('delete', old.rowid, old.summary, COALESCE(old.topic, ''));
|
|
333
|
+
END;
|
|
334
|
+
CREATE TRIGGER IF NOT EXISTS tasks_fts_au AFTER UPDATE ON tasks BEGIN
|
|
335
|
+
INSERT INTO tasks_fts(tasks_fts, rowid, summary, topic)
|
|
336
|
+
VALUES ('delete', old.rowid, old.summary, COALESCE(old.topic, ''));
|
|
337
|
+
INSERT INTO tasks_fts(rowid, summary, topic)
|
|
338
|
+
VALUES (new.rowid, new.summary, COALESCE(new.topic, ''));
|
|
339
|
+
END;
|
|
340
|
+
`);
|
|
341
|
+
} catch {
|
|
342
|
+
// triggers may already exist
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
try {
|
|
346
|
+
const count = (this.db.prepare("SELECT COUNT(*) as c FROM tasks_fts").get() as { c: number }).c;
|
|
347
|
+
const taskCount = (this.db.prepare("SELECT COUNT(*) as c FROM tasks").get() as { c: number }).c;
|
|
348
|
+
if (count === 0 && taskCount > 0) {
|
|
349
|
+
this.db.exec("INSERT INTO tasks_fts(rowid, summary, topic) SELECT rowid, summary, COALESCE(topic, '') FROM tasks");
|
|
350
|
+
this.log.info(`Migrated: backfilled tasks_fts for ${taskCount} tasks`);
|
|
351
|
+
}
|
|
352
|
+
} catch { /* best-effort */ }
|
|
353
|
+
}
|
|
354
|
+
|
|
293
355
|
private migrateFtsToTrigram(): void {
|
|
294
356
|
// Check if chunks_fts still uses the old tokenizer (porter unicode61)
|
|
295
357
|
try {
|
|
@@ -507,6 +569,14 @@ export class SqliteStore {
|
|
|
507
569
|
}
|
|
508
570
|
}
|
|
509
571
|
|
|
572
|
+
private migrateTaskTopicColumn(): void {
|
|
573
|
+
const cols = this.db.prepare("PRAGMA table_info(tasks)").all() as Array<{ name: string }>;
|
|
574
|
+
if (!cols.some((c) => c.name === "topic")) {
|
|
575
|
+
this.db.exec("ALTER TABLE tasks ADD COLUMN topic TEXT DEFAULT NULL");
|
|
576
|
+
this.log.info("Migrated: added topic column to tasks");
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
510
580
|
private migrateTaskSkillMeta(): void {
|
|
511
581
|
const cols = this.db.prepare("PRAGMA table_info(tasks)").all() as Array<{ name: string }>;
|
|
512
582
|
if (!cols.some((c) => c.name === "skill_status")) {
|
|
@@ -992,6 +1062,7 @@ export class SqliteStore {
|
|
|
992
1062
|
id TEXT PRIMARY KEY,
|
|
993
1063
|
source_chunk_id TEXT NOT NULL,
|
|
994
1064
|
source_user_id TEXT NOT NULL,
|
|
1065
|
+
source_agent TEXT NOT NULL DEFAULT '',
|
|
995
1066
|
role TEXT NOT NULL,
|
|
996
1067
|
content TEXT NOT NULL,
|
|
997
1068
|
summary TEXT NOT NULL DEFAULT '',
|
|
@@ -1207,7 +1278,7 @@ export class SqliteStore {
|
|
|
1207
1278
|
|
|
1208
1279
|
// ─── Pattern Search (LIKE-based, for CJK text where FTS tokenization is weak) ───
|
|
1209
1280
|
|
|
1210
|
-
patternSearch(patterns: string[], opts: { role?: string; limit?: number } = {}): Array<{ chunkId: string; content: string; role: string; createdAt: number }> {
|
|
1281
|
+
patternSearch(patterns: string[], opts: { role?: string; limit?: number; ownerFilter?: string[] } = {}): Array<{ chunkId: string; content: string; role: string; createdAt: number }> {
|
|
1211
1282
|
if (patterns.length === 0) return [];
|
|
1212
1283
|
const limit = opts.limit ?? 10;
|
|
1213
1284
|
|
|
@@ -1216,13 +1287,21 @@ export class SqliteStore {
|
|
|
1216
1287
|
const roleClause = opts.role ? " AND c.role = ?" : "";
|
|
1217
1288
|
const params: (string | number)[] = patterns.map(p => `%${p}%`);
|
|
1218
1289
|
if (opts.role) params.push(opts.role);
|
|
1290
|
+
|
|
1291
|
+
let ownerClause = "";
|
|
1292
|
+
if (opts.ownerFilter && opts.ownerFilter.length > 0) {
|
|
1293
|
+
const placeholders = opts.ownerFilter.map(() => "?").join(",");
|
|
1294
|
+
ownerClause = ` AND c.owner IN (${placeholders})`;
|
|
1295
|
+
params.push(...opts.ownerFilter);
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1219
1298
|
params.push(limit);
|
|
1220
1299
|
|
|
1221
1300
|
try {
|
|
1222
1301
|
const rows = this.db.prepare(`
|
|
1223
1302
|
SELECT c.id as chunk_id, c.content, c.role, c.created_at
|
|
1224
1303
|
FROM chunks c
|
|
1225
|
-
WHERE (${whereClause})${roleClause} AND c.dedup_status = 'active'
|
|
1304
|
+
WHERE (${whereClause})${roleClause}${ownerClause} AND c.dedup_status = 'active'
|
|
1226
1305
|
ORDER BY c.created_at DESC
|
|
1227
1306
|
LIMIT ?
|
|
1228
1307
|
`).all(...params) as Array<{ chunk_id: string; content: string; role: string; created_at: number }>;
|
|
@@ -1425,8 +1504,15 @@ export class SqliteStore {
|
|
|
1425
1504
|
|
|
1426
1505
|
deleteAll(): number {
|
|
1427
1506
|
this.db.exec("PRAGMA foreign_keys = OFF");
|
|
1507
|
+
try {
|
|
1508
|
+
this.db.exec("DROP TRIGGER IF EXISTS tasks_fts_ai");
|
|
1509
|
+
this.db.exec("DROP TRIGGER IF EXISTS tasks_fts_ad");
|
|
1510
|
+
this.db.exec("DROP TRIGGER IF EXISTS tasks_fts_au");
|
|
1511
|
+
this.db.exec("DELETE FROM tasks_fts");
|
|
1512
|
+
} catch (_) {}
|
|
1428
1513
|
const tables = [
|
|
1429
1514
|
"task_skills",
|
|
1515
|
+
"task_embeddings",
|
|
1430
1516
|
"skill_embeddings",
|
|
1431
1517
|
"skill_versions",
|
|
1432
1518
|
"skills",
|
|
@@ -1449,6 +1535,7 @@ export class SqliteStore {
|
|
|
1449
1535
|
}
|
|
1450
1536
|
}
|
|
1451
1537
|
this.db.exec("PRAGMA foreign_keys = ON");
|
|
1538
|
+
this.migrateTaskEmbeddingsAndFts();
|
|
1452
1539
|
const remaining = this.countChunks();
|
|
1453
1540
|
return remaining === 0 ? 1 : 0;
|
|
1454
1541
|
}
|
|
@@ -1469,6 +1556,21 @@ export class SqliteStore {
|
|
|
1469
1556
|
return result.changes > 0;
|
|
1470
1557
|
}
|
|
1471
1558
|
|
|
1559
|
+
disableSkill(skillId: string): boolean {
|
|
1560
|
+
const skill = this.getSkill(skillId);
|
|
1561
|
+
if (!skill || skill.status === "archived") return false;
|
|
1562
|
+
this.db.prepare("DELETE FROM skill_embeddings WHERE skill_id = ?").run(skillId);
|
|
1563
|
+
this.updateSkill(skillId, { status: "archived", installed: 0 });
|
|
1564
|
+
return true;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
enableSkill(skillId: string): boolean {
|
|
1568
|
+
const skill = this.getSkill(skillId);
|
|
1569
|
+
if (!skill || skill.status !== "archived") return false;
|
|
1570
|
+
this.updateSkill(skillId, { status: "active" });
|
|
1571
|
+
return true;
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1472
1574
|
// ─── Task CRUD ───
|
|
1473
1575
|
|
|
1474
1576
|
insertTask(task: Task): void {
|
|
@@ -1550,10 +1652,11 @@ export class SqliteStore {
|
|
|
1550
1652
|
return rows.map(rowToChunk);
|
|
1551
1653
|
}
|
|
1552
1654
|
|
|
1553
|
-
listTasks(opts: { status?: string; limit?: number; offset?: number; owner?: string } = {}): { tasks: Task[]; total: number } {
|
|
1655
|
+
listTasks(opts: { status?: string; limit?: number; offset?: number; owner?: string; session?: string } = {}): { tasks: Task[]; total: number } {
|
|
1554
1656
|
const conditions: string[] = [];
|
|
1555
1657
|
const params: unknown[] = [];
|
|
1556
1658
|
if (opts.status) { conditions.push("status = ?"); params.push(opts.status); }
|
|
1659
|
+
if (opts.session) { conditions.push("session_key = ?"); params.push(opts.session); }
|
|
1557
1660
|
if (opts.owner) {
|
|
1558
1661
|
conditions.push("(owner = ? OR (owner = 'public' AND id IN (SELECT task_id FROM local_shared_tasks WHERE original_owner = ?)))");
|
|
1559
1662
|
params.push(opts.owner, opts.owner);
|
|
@@ -1675,9 +1778,24 @@ export class SqliteStore {
|
|
|
1675
1778
|
this.db.prepare(`UPDATE skills SET ${sets.join(", ")} WHERE id = ?`).run(...params);
|
|
1676
1779
|
}
|
|
1677
1780
|
|
|
1678
|
-
listSkills(opts: { status?: string } = {}): Skill[] {
|
|
1679
|
-
const
|
|
1680
|
-
const params
|
|
1781
|
+
listSkills(opts: { status?: string; session?: string; owner?: string } = {}): Skill[] {
|
|
1782
|
+
const conditions: string[] = [];
|
|
1783
|
+
const params: unknown[] = [];
|
|
1784
|
+
if (opts.status) { conditions.push("status = ?"); params.push(opts.status); }
|
|
1785
|
+
if (opts.owner) {
|
|
1786
|
+
conditions.push("(owner = ? OR owner = 'public')");
|
|
1787
|
+
params.push(opts.owner);
|
|
1788
|
+
}
|
|
1789
|
+
if (opts.session) {
|
|
1790
|
+
conditions.push(`EXISTS (
|
|
1791
|
+
SELECT 1
|
|
1792
|
+
FROM task_skills ts
|
|
1793
|
+
JOIN tasks t ON t.id = ts.task_id
|
|
1794
|
+
WHERE ts.skill_id = skills.id AND t.session_key = ?
|
|
1795
|
+
)`);
|
|
1796
|
+
params.push(opts.session);
|
|
1797
|
+
}
|
|
1798
|
+
const cond = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1681
1799
|
const rows = this.db.prepare(`SELECT * FROM skills ${cond} ORDER BY updated_at DESC`).all(...params) as SkillRow[];
|
|
1682
1800
|
return rows.map(rowToSkill);
|
|
1683
1801
|
}
|
|
@@ -1767,6 +1885,61 @@ export class SqliteStore {
|
|
|
1767
1885
|
}
|
|
1768
1886
|
}
|
|
1769
1887
|
|
|
1888
|
+
// ─── Task Embeddings & Search ───
|
|
1889
|
+
|
|
1890
|
+
upsertTaskEmbedding(taskId: string, vector: number[]): void {
|
|
1891
|
+
const buf = Buffer.from(new Float32Array(vector).buffer);
|
|
1892
|
+
this.db.prepare(`
|
|
1893
|
+
INSERT OR REPLACE INTO task_embeddings (task_id, vector, dimensions, updated_at)
|
|
1894
|
+
VALUES (?, ?, ?, ?)
|
|
1895
|
+
`).run(taskId, buf, vector.length, Date.now());
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
getTaskEmbeddings(owner?: string): Array<{ taskId: string; vector: number[] }> {
|
|
1899
|
+
let sql = `SELECT te.task_id, te.vector, te.dimensions
|
|
1900
|
+
FROM task_embeddings te
|
|
1901
|
+
JOIN tasks t ON t.id = te.task_id`;
|
|
1902
|
+
const params: any[] = [];
|
|
1903
|
+
if (owner) {
|
|
1904
|
+
sql += ` WHERE (t.owner = ? OR t.owner = 'public')`;
|
|
1905
|
+
params.push(owner);
|
|
1906
|
+
}
|
|
1907
|
+
const rows = this.db.prepare(sql).all(...params) as Array<{ task_id: string; vector: Buffer; dimensions: number }>;
|
|
1908
|
+
return rows.map((r) => ({
|
|
1909
|
+
taskId: r.task_id,
|
|
1910
|
+
vector: Array.from(new Float32Array(r.vector.buffer, r.vector.byteOffset, r.dimensions)),
|
|
1911
|
+
}));
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
taskFtsSearch(query: string, limit: number, owner?: string): Array<{ taskId: string; score: number }> {
|
|
1915
|
+
const sanitized = sanitizeFtsQuery(query);
|
|
1916
|
+
if (!sanitized) return [];
|
|
1917
|
+
try {
|
|
1918
|
+
let sql = `
|
|
1919
|
+
SELECT t.id as task_id, rank
|
|
1920
|
+
FROM tasks_fts f
|
|
1921
|
+
JOIN tasks t ON t.rowid = f.rowid
|
|
1922
|
+
WHERE tasks_fts MATCH ?`;
|
|
1923
|
+
const params: any[] = [sanitized];
|
|
1924
|
+
if (owner) {
|
|
1925
|
+
sql += ` AND (t.owner = ? OR t.owner = 'public')`;
|
|
1926
|
+
params.push(owner);
|
|
1927
|
+
}
|
|
1928
|
+
sql += ` ORDER BY rank LIMIT ?`;
|
|
1929
|
+
params.push(limit);
|
|
1930
|
+
const rows = this.db.prepare(sql).all(...params) as Array<{ task_id: string; rank: number }>;
|
|
1931
|
+
if (rows.length === 0) return [];
|
|
1932
|
+
const maxAbsRank = Math.max(...rows.map((r) => Math.abs(r.rank)));
|
|
1933
|
+
return rows.map((r) => ({
|
|
1934
|
+
taskId: r.task_id,
|
|
1935
|
+
score: maxAbsRank > 0 ? Math.abs(r.rank) / maxAbsRank : 0,
|
|
1936
|
+
}));
|
|
1937
|
+
} catch {
|
|
1938
|
+
this.log.warn(`Task FTS query failed for: "${sanitized}", returning empty`);
|
|
1939
|
+
return [];
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1770
1943
|
listPublicSkills(): Skill[] {
|
|
1771
1944
|
const rows = this.db.prepare("SELECT * FROM skills WHERE visibility = 'public' AND status = 'active' ORDER BY updated_at DESC").all() as SkillRow[];
|
|
1772
1945
|
return rows.map(rowToSkill);
|
|
@@ -2430,9 +2603,10 @@ export class SqliteStore {
|
|
|
2430
2603
|
|
|
2431
2604
|
upsertHubMemory(memory: HubMemoryRecord): void {
|
|
2432
2605
|
this.db.prepare(`
|
|
2433
|
-
INSERT INTO hub_memories (id, source_chunk_id, source_user_id, role, content, summary, kind, group_id, visibility, created_at, updated_at)
|
|
2434
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2606
|
+
INSERT INTO hub_memories (id, source_chunk_id, source_user_id, source_agent, role, content, summary, kind, group_id, visibility, created_at, updated_at)
|
|
2607
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2435
2608
|
ON CONFLICT(source_user_id, source_chunk_id) DO UPDATE SET
|
|
2609
|
+
source_agent = excluded.source_agent,
|
|
2436
2610
|
role = excluded.role,
|
|
2437
2611
|
content = excluded.content,
|
|
2438
2612
|
summary = excluded.summary,
|
|
@@ -2441,7 +2615,7 @@ export class SqliteStore {
|
|
|
2441
2615
|
visibility = excluded.visibility,
|
|
2442
2616
|
created_at = excluded.created_at,
|
|
2443
2617
|
updated_at = excluded.updated_at
|
|
2444
|
-
`).run(memory.id, memory.sourceChunkId, memory.sourceUserId, memory.role, memory.content, memory.summary, memory.kind, memory.groupId, memory.visibility, memory.createdAt, memory.updatedAt);
|
|
2618
|
+
`).run(memory.id, memory.sourceChunkId, memory.sourceUserId, memory.sourceAgent, memory.role, memory.content, memory.summary, memory.kind, memory.groupId, memory.visibility, memory.createdAt, memory.updatedAt);
|
|
2445
2619
|
}
|
|
2446
2620
|
|
|
2447
2621
|
getHubMemoryBySource(sourceUserId: string, sourceChunkId: string): HubMemoryRecord | null {
|
|
@@ -2550,6 +2724,11 @@ export class SqliteStore {
|
|
|
2550
2724
|
this.db.prepare("UPDATE local_shared_tasks SET hub_task_id = '', hub_instance_id = '', visibility = 'public', group_id = NULL, synced_chunks = 0 WHERE task_id = ?").run(taskId);
|
|
2551
2725
|
}
|
|
2552
2726
|
|
|
2727
|
+
/** Client UI: remove team_shared_chunks rows for all chunks linked to this task (list badge chunk fallback). */
|
|
2728
|
+
clearTeamSharedChunksForTask(taskId: string): void {
|
|
2729
|
+
this.db.prepare("DELETE FROM team_shared_chunks WHERE chunk_id IN (SELECT id FROM chunks WHERE task_id = ?)").run(taskId);
|
|
2730
|
+
}
|
|
2731
|
+
|
|
2553
2732
|
clearAllTeamSharingState(): void {
|
|
2554
2733
|
this.clearTeamSharedChunks();
|
|
2555
2734
|
this.clearTeamSharedSkills();
|
|
@@ -2607,7 +2786,7 @@ export class SqliteStore {
|
|
|
2607
2786
|
if (!sanitized) return [];
|
|
2608
2787
|
const rows = this.db.prepare(`
|
|
2609
2788
|
SELECT hm.id, hm.content, hm.summary, hm.role, hm.created_at, hm.visibility, '' as group_name, hu.username as owner_name,
|
|
2610
|
-
bm25(hub_memories_fts) as rank
|
|
2789
|
+
COALESCE(hm.source_agent, '') as source_agent, bm25(hub_memories_fts) as rank
|
|
2611
2790
|
FROM hub_memories_fts f
|
|
2612
2791
|
JOIN hub_memories hm ON hm.rowid = f.rowid
|
|
2613
2792
|
LEFT JOIN hub_users hu ON hu.id = hm.source_user_id
|
|
@@ -2623,7 +2802,7 @@ export class SqliteStore {
|
|
|
2623
2802
|
getVisibleHubSearchHitByMemoryId(memoryId: string, userId: string): HubMemorySearchRow | null {
|
|
2624
2803
|
const row = this.db.prepare(`
|
|
2625
2804
|
SELECT hm.id, hm.content, hm.summary, hm.role, hm.created_at, hm.visibility, '' as group_name, hu.username as owner_name,
|
|
2626
|
-
0 as rank
|
|
2805
|
+
COALESCE(hm.source_agent, '') as source_agent, 0 as rank
|
|
2627
2806
|
FROM hub_memories hm
|
|
2628
2807
|
LEFT JOIN hub_users hu ON hu.id = hm.source_user_id
|
|
2629
2808
|
WHERE hm.id = ?
|
|
@@ -3117,6 +3296,7 @@ export interface HubMemoryRecord {
|
|
|
3117
3296
|
id: string;
|
|
3118
3297
|
sourceChunkId: string;
|
|
3119
3298
|
sourceUserId: string;
|
|
3299
|
+
sourceAgent: string;
|
|
3120
3300
|
role: string;
|
|
3121
3301
|
content: string;
|
|
3122
3302
|
summary: string;
|
|
@@ -3131,6 +3311,7 @@ interface HubMemoryRow {
|
|
|
3131
3311
|
id: string;
|
|
3132
3312
|
source_chunk_id: string;
|
|
3133
3313
|
source_user_id: string;
|
|
3314
|
+
source_agent: string;
|
|
3134
3315
|
role: string;
|
|
3135
3316
|
content: string;
|
|
3136
3317
|
summary: string;
|
|
@@ -3146,6 +3327,7 @@ function rowToHubMemory(row: HubMemoryRow): HubMemoryRecord {
|
|
|
3146
3327
|
id: row.id,
|
|
3147
3328
|
sourceChunkId: row.source_chunk_id,
|
|
3148
3329
|
sourceUserId: row.source_user_id,
|
|
3330
|
+
sourceAgent: row.source_agent || "",
|
|
3149
3331
|
role: row.role,
|
|
3150
3332
|
content: row.content,
|
|
3151
3333
|
summary: row.summary,
|
|
@@ -3166,6 +3348,7 @@ interface HubMemorySearchRow {
|
|
|
3166
3348
|
visibility: string;
|
|
3167
3349
|
group_name: string | null;
|
|
3168
3350
|
owner_name: string | null;
|
|
3351
|
+
source_agent: string;
|
|
3169
3352
|
rank: number;
|
|
3170
3353
|
}
|
|
3171
3354
|
|
package/src/types.ts
CHANGED
|
@@ -322,6 +322,8 @@ export interface MemosLocalConfig {
|
|
|
322
322
|
skillEvolution?: SkillEvolutionConfig;
|
|
323
323
|
telemetry?: TelemetryConfig;
|
|
324
324
|
sharing?: SharingConfig;
|
|
325
|
+
/** Hours of inactivity after which an active task is automatically finalized. 0 = disabled. Default 4. */
|
|
326
|
+
taskAutoFinalizeHours?: number;
|
|
325
327
|
}
|
|
326
328
|
|
|
327
329
|
// ─── Defaults ───
|
|
@@ -357,6 +359,7 @@ export const DEFAULTS = {
|
|
|
357
359
|
skillAutoRecallLimit: 2,
|
|
358
360
|
skillPreferUpgrade: true,
|
|
359
361
|
skillRedactSensitive: true,
|
|
362
|
+
taskAutoFinalizeHours: 4,
|
|
360
363
|
} as const;
|
|
361
364
|
|
|
362
365
|
// ─── Plugin Hooks (OpenClaw integration) ───
|