@memtensor/memos-local-openclaw-plugin 1.0.6-beta.9 → 1.0.7-beta.1
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/index.ts +266 -273
- package/openclaw.plugin.json +1 -1
- package/package.json +3 -5
- 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/scripts/postinstall.cjs +44 -44
- package/skill/memos-memory-guide/SKILL.md +25 -2
- package/src/context-engine/index.ts +321 -0
- package/src/ingest/providers/index.ts +14 -1
- package/src/shared/llm-call.ts +14 -1
- package/src/update-check.ts +2 -7
- package/src/viewer/html.ts +4 -4
- package/src/viewer/server.ts +25 -3
- package/telemetry.credentials.json +5 -0
- package/dist/capture/index.d.ts +0 -26
- package/dist/capture/index.d.ts.map +0 -1
- package/dist/capture/index.js +0 -283
- package/dist/capture/index.js.map +0 -1
- package/dist/client/connector.d.ts +0 -34
- package/dist/client/connector.d.ts.map +0 -1
- package/dist/client/connector.js +0 -381
- package/dist/client/connector.js.map +0 -1
- package/dist/client/hub.d.ts +0 -61
- package/dist/client/hub.d.ts.map +0 -1
- package/dist/client/hub.js +0 -174
- package/dist/client/hub.js.map +0 -1
- package/dist/client/skill-sync.d.ts +0 -36
- package/dist/client/skill-sync.d.ts.map +0 -1
- package/dist/client/skill-sync.js +0 -226
- package/dist/client/skill-sync.js.map +0 -1
- package/dist/config.d.ts +0 -5
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -171
- package/dist/config.js.map +0 -1
- package/dist/embedding/index.d.ts +0 -14
- package/dist/embedding/index.d.ts.map +0 -1
- package/dist/embedding/index.js +0 -105
- package/dist/embedding/index.js.map +0 -1
- package/dist/embedding/local.d.ts +0 -3
- package/dist/embedding/local.d.ts.map +0 -1
- package/dist/embedding/local.js +0 -66
- package/dist/embedding/local.js.map +0 -1
- package/dist/embedding/providers/cohere.d.ts +0 -4
- package/dist/embedding/providers/cohere.d.ts.map +0 -1
- package/dist/embedding/providers/cohere.js +0 -57
- package/dist/embedding/providers/cohere.js.map +0 -1
- package/dist/embedding/providers/gemini.d.ts +0 -3
- package/dist/embedding/providers/gemini.d.ts.map +0 -1
- package/dist/embedding/providers/gemini.js +0 -31
- package/dist/embedding/providers/gemini.js.map +0 -1
- package/dist/embedding/providers/mistral.d.ts +0 -3
- package/dist/embedding/providers/mistral.d.ts.map +0 -1
- package/dist/embedding/providers/mistral.js +0 -25
- package/dist/embedding/providers/mistral.js.map +0 -1
- package/dist/embedding/providers/openai.d.ts +0 -3
- package/dist/embedding/providers/openai.d.ts.map +0 -1
- package/dist/embedding/providers/openai.js +0 -35
- package/dist/embedding/providers/openai.js.map +0 -1
- package/dist/embedding/providers/voyage.d.ts +0 -3
- package/dist/embedding/providers/voyage.d.ts.map +0 -1
- package/dist/embedding/providers/voyage.js +0 -25
- package/dist/embedding/providers/voyage.js.map +0 -1
- package/dist/hub/auth.d.ts +0 -19
- package/dist/hub/auth.d.ts.map +0 -1
- package/dist/hub/auth.js +0 -70
- package/dist/hub/auth.js.map +0 -1
- package/dist/hub/server.d.ts +0 -52
- package/dist/hub/server.d.ts.map +0 -1
- package/dist/hub/server.js +0 -1197
- package/dist/hub/server.js.map +0 -1
- package/dist/hub/user-manager.d.ts +0 -40
- package/dist/hub/user-manager.d.ts.map +0 -1
- package/dist/hub/user-manager.js +0 -153
- package/dist/hub/user-manager.js.map +0 -1
- package/dist/index.d.ts +0 -46
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -82
- package/dist/index.js.map +0 -1
- package/dist/ingest/chunker.d.ts +0 -15
- package/dist/ingest/chunker.d.ts.map +0 -1
- package/dist/ingest/chunker.js +0 -192
- package/dist/ingest/chunker.js.map +0 -1
- package/dist/ingest/dedup.d.ts +0 -19
- package/dist/ingest/dedup.d.ts.map +0 -1
- package/dist/ingest/dedup.js +0 -50
- package/dist/ingest/dedup.js.map +0 -1
- package/dist/ingest/providers/anthropic.d.ts +0 -21
- package/dist/ingest/providers/anthropic.d.ts.map +0 -1
- package/dist/ingest/providers/anthropic.js +0 -314
- package/dist/ingest/providers/anthropic.js.map +0 -1
- package/dist/ingest/providers/bedrock.d.ts +0 -21
- package/dist/ingest/providers/bedrock.d.ts.map +0 -1
- package/dist/ingest/providers/bedrock.js +0 -313
- package/dist/ingest/providers/bedrock.js.map +0 -1
- package/dist/ingest/providers/gemini.d.ts +0 -21
- package/dist/ingest/providers/gemini.d.ts.map +0 -1
- package/dist/ingest/providers/gemini.js +0 -298
- package/dist/ingest/providers/gemini.js.map +0 -1
- package/dist/ingest/providers/index.d.ts +0 -68
- package/dist/ingest/providers/index.d.ts.map +0 -1
- package/dist/ingest/providers/index.js +0 -611
- package/dist/ingest/providers/index.js.map +0 -1
- package/dist/ingest/providers/openai.d.ts +0 -30
- package/dist/ingest/providers/openai.d.ts.map +0 -1
- package/dist/ingest/providers/openai.js +0 -387
- package/dist/ingest/providers/openai.js.map +0 -1
- package/dist/ingest/task-processor.d.ts +0 -91
- package/dist/ingest/task-processor.d.ts.map +0 -1
- package/dist/ingest/task-processor.js +0 -478
- package/dist/ingest/task-processor.js.map +0 -1
- package/dist/ingest/worker.d.ts +0 -23
- package/dist/ingest/worker.d.ts.map +0 -1
- package/dist/ingest/worker.js +0 -255
- package/dist/ingest/worker.js.map +0 -1
- package/dist/openclaw-api.d.ts +0 -53
- package/dist/openclaw-api.d.ts.map +0 -1
- package/dist/openclaw-api.js +0 -189
- package/dist/openclaw-api.js.map +0 -1
- package/dist/recall/engine.d.ts +0 -28
- package/dist/recall/engine.d.ts.map +0 -1
- package/dist/recall/engine.js +0 -343
- package/dist/recall/engine.js.map +0 -1
- package/dist/recall/mmr.d.ts +0 -17
- package/dist/recall/mmr.d.ts.map +0 -1
- package/dist/recall/mmr.js +0 -53
- package/dist/recall/mmr.js.map +0 -1
- package/dist/recall/recency.d.ts +0 -20
- package/dist/recall/recency.d.ts.map +0 -1
- package/dist/recall/recency.js +0 -26
- package/dist/recall/recency.js.map +0 -1
- package/dist/recall/rrf.d.ts +0 -16
- package/dist/recall/rrf.d.ts.map +0 -1
- package/dist/recall/rrf.js +0 -15
- package/dist/recall/rrf.js.map +0 -1
- package/dist/shared/llm-call.d.ts +0 -30
- package/dist/shared/llm-call.d.ts.map +0 -1
- package/dist/shared/llm-call.js +0 -253
- package/dist/shared/llm-call.js.map +0 -1
- package/dist/sharing/types.contract.d.ts +0 -2
- package/dist/sharing/types.contract.d.ts.map +0 -1
- package/dist/sharing/types.contract.js +0 -3
- package/dist/sharing/types.contract.js.map +0 -1
- package/dist/sharing/types.d.ts +0 -80
- package/dist/sharing/types.d.ts.map +0 -1
- package/dist/sharing/types.js +0 -3
- package/dist/sharing/types.js.map +0 -1
- package/dist/skill/bundled-memory-guide.d.ts +0 -2
- package/dist/skill/bundled-memory-guide.d.ts.map +0 -1
- package/dist/skill/bundled-memory-guide.js +0 -45
- package/dist/skill/bundled-memory-guide.js.map +0 -1
- package/dist/skill/evaluator.d.ts +0 -28
- package/dist/skill/evaluator.d.ts.map +0 -1
- package/dist/skill/evaluator.js +0 -169
- package/dist/skill/evaluator.js.map +0 -1
- package/dist/skill/evolver.d.ts +0 -48
- package/dist/skill/evolver.d.ts.map +0 -1
- package/dist/skill/evolver.js +0 -406
- package/dist/skill/evolver.js.map +0 -1
- package/dist/skill/generator.d.ts +0 -26
- package/dist/skill/generator.d.ts.map +0 -1
- package/dist/skill/generator.js +0 -521
- package/dist/skill/generator.js.map +0 -1
- package/dist/skill/installer.d.ts +0 -42
- package/dist/skill/installer.d.ts.map +0 -1
- package/dist/skill/installer.js +0 -165
- package/dist/skill/installer.js.map +0 -1
- package/dist/skill/upgrader.d.ts +0 -19
- package/dist/skill/upgrader.d.ts.map +0 -1
- package/dist/skill/upgrader.js +0 -366
- package/dist/skill/upgrader.js.map +0 -1
- package/dist/skill/validator.d.ts +0 -30
- package/dist/skill/validator.d.ts.map +0 -1
- package/dist/skill/validator.js +0 -272
- package/dist/skill/validator.js.map +0 -1
- package/dist/storage/ensure-binding.d.ts +0 -12
- package/dist/storage/ensure-binding.d.ts.map +0 -1
- package/dist/storage/ensure-binding.js +0 -53
- package/dist/storage/ensure-binding.js.map +0 -1
- package/dist/storage/sqlite.d.ts +0 -649
- package/dist/storage/sqlite.d.ts.map +0 -1
- package/dist/storage/sqlite.js +0 -2657
- package/dist/storage/sqlite.js.map +0 -1
- package/dist/storage/vector.d.ts +0 -12
- package/dist/storage/vector.d.ts.map +0 -1
- package/dist/storage/vector.js +0 -34
- package/dist/storage/vector.js.map +0 -1
- package/dist/telemetry.d.ts +0 -47
- package/dist/telemetry.d.ts.map +0 -1
- package/dist/telemetry.js +0 -312
- package/dist/telemetry.js.map +0 -1
- package/dist/tools/index.d.ts +0 -5
- package/dist/tools/index.d.ts.map +0 -1
- package/dist/tools/index.js +0 -12
- package/dist/tools/index.js.map +0 -1
- package/dist/tools/memory-get.d.ts +0 -4
- package/dist/tools/memory-get.d.ts.map +0 -1
- package/dist/tools/memory-get.js +0 -64
- package/dist/tools/memory-get.js.map +0 -1
- package/dist/tools/memory-search.d.ts +0 -7
- package/dist/tools/memory-search.d.ts.map +0 -1
- package/dist/tools/memory-search.js +0 -84
- package/dist/tools/memory-search.js.map +0 -1
- package/dist/tools/memory-timeline.d.ts +0 -4
- package/dist/tools/memory-timeline.d.ts.map +0 -1
- package/dist/tools/memory-timeline.js +0 -73
- package/dist/tools/memory-timeline.js.map +0 -1
- package/dist/tools/network-memory-detail.d.ts +0 -4
- package/dist/tools/network-memory-detail.d.ts.map +0 -1
- package/dist/tools/network-memory-detail.js +0 -34
- package/dist/tools/network-memory-detail.js.map +0 -1
- package/dist/types.d.ts +0 -330
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -38
- package/dist/types.js.map +0 -1
- package/dist/update-check.d.ts +0 -21
- package/dist/update-check.d.ts.map +0 -1
- package/dist/update-check.js +0 -110
- package/dist/update-check.js.map +0 -1
- package/dist/viewer/html.d.ts +0 -2
- package/dist/viewer/html.d.ts.map +0 -1
- package/dist/viewer/html.js +0 -9168
- package/dist/viewer/html.js.map +0 -1
- package/dist/viewer/server.d.ts +0 -205
- package/dist/viewer/server.d.ts.map +0 -1
- package/dist/viewer/server.js +0 -4876
- package/dist/viewer/server.js.map +0 -1
package/dist/storage/sqlite.js
DELETED
|
@@ -1,2657 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.SqliteStore = void 0;
|
|
40
|
-
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
41
|
-
const crypto_1 = require("crypto");
|
|
42
|
-
const fs = __importStar(require("fs"));
|
|
43
|
-
const path = __importStar(require("path"));
|
|
44
|
-
class SqliteStore {
|
|
45
|
-
log;
|
|
46
|
-
db;
|
|
47
|
-
constructor(dbPath, log) {
|
|
48
|
-
this.log = log;
|
|
49
|
-
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
50
|
-
this.db = new better_sqlite3_1.default(dbPath);
|
|
51
|
-
this.db.pragma("journal_mode = WAL");
|
|
52
|
-
this.db.pragma("foreign_keys = ON");
|
|
53
|
-
this.migrate();
|
|
54
|
-
}
|
|
55
|
-
// ─── Schema ───
|
|
56
|
-
migrate() {
|
|
57
|
-
this.db.exec(`
|
|
58
|
-
CREATE TABLE IF NOT EXISTS chunks (
|
|
59
|
-
id TEXT PRIMARY KEY,
|
|
60
|
-
session_key TEXT NOT NULL,
|
|
61
|
-
turn_id TEXT NOT NULL,
|
|
62
|
-
seq INTEGER NOT NULL,
|
|
63
|
-
role TEXT NOT NULL,
|
|
64
|
-
content TEXT NOT NULL,
|
|
65
|
-
kind TEXT NOT NULL DEFAULT 'paragraph',
|
|
66
|
-
summary TEXT NOT NULL DEFAULT '',
|
|
67
|
-
created_at INTEGER NOT NULL,
|
|
68
|
-
updated_at INTEGER NOT NULL
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
CREATE INDEX IF NOT EXISTS idx_chunks_session
|
|
72
|
-
ON chunks(session_key);
|
|
73
|
-
CREATE INDEX IF NOT EXISTS idx_chunks_turn
|
|
74
|
-
ON chunks(session_key, turn_id, seq);
|
|
75
|
-
CREATE INDEX IF NOT EXISTS idx_chunks_created
|
|
76
|
-
ON chunks(created_at);
|
|
77
|
-
CREATE INDEX IF NOT EXISTS idx_chunks_session_created
|
|
78
|
-
ON chunks(session_key, created_at, seq);
|
|
79
|
-
|
|
80
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS chunks_fts USING fts5(
|
|
81
|
-
summary,
|
|
82
|
-
content,
|
|
83
|
-
content='chunks',
|
|
84
|
-
content_rowid='rowid',
|
|
85
|
-
tokenize='trigram'
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
CREATE TRIGGER IF NOT EXISTS chunks_ai AFTER INSERT ON chunks BEGIN
|
|
89
|
-
INSERT INTO chunks_fts(rowid, summary, content)
|
|
90
|
-
VALUES (new.rowid, new.summary, new.content);
|
|
91
|
-
END;
|
|
92
|
-
|
|
93
|
-
CREATE TRIGGER IF NOT EXISTS chunks_ad AFTER DELETE ON chunks BEGIN
|
|
94
|
-
INSERT INTO chunks_fts(chunks_fts, rowid, summary, content)
|
|
95
|
-
VALUES ('delete', old.rowid, old.summary, old.content);
|
|
96
|
-
END;
|
|
97
|
-
|
|
98
|
-
CREATE TRIGGER IF NOT EXISTS chunks_au AFTER UPDATE ON chunks BEGIN
|
|
99
|
-
INSERT INTO chunks_fts(chunks_fts, rowid, summary, content)
|
|
100
|
-
VALUES ('delete', old.rowid, old.summary, old.content);
|
|
101
|
-
INSERT INTO chunks_fts(rowid, summary, content)
|
|
102
|
-
VALUES (new.rowid, new.summary, new.content);
|
|
103
|
-
END;
|
|
104
|
-
|
|
105
|
-
CREATE TABLE IF NOT EXISTS embeddings (
|
|
106
|
-
chunk_id TEXT PRIMARY KEY REFERENCES chunks(id) ON DELETE CASCADE,
|
|
107
|
-
vector BLOB NOT NULL,
|
|
108
|
-
dimensions INTEGER NOT NULL,
|
|
109
|
-
updated_at INTEGER NOT NULL
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
CREATE TABLE IF NOT EXISTS viewer_events (
|
|
113
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
114
|
-
event_type TEXT NOT NULL,
|
|
115
|
-
created_at INTEGER NOT NULL
|
|
116
|
-
);
|
|
117
|
-
CREATE INDEX IF NOT EXISTS idx_viewer_events_created ON viewer_events(created_at);
|
|
118
|
-
CREATE INDEX IF NOT EXISTS idx_viewer_events_type ON viewer_events(event_type);
|
|
119
|
-
|
|
120
|
-
CREATE TABLE IF NOT EXISTS tasks (
|
|
121
|
-
id TEXT PRIMARY KEY,
|
|
122
|
-
session_key TEXT NOT NULL,
|
|
123
|
-
title TEXT NOT NULL DEFAULT '',
|
|
124
|
-
summary TEXT NOT NULL DEFAULT '',
|
|
125
|
-
status TEXT NOT NULL DEFAULT 'active',
|
|
126
|
-
started_at INTEGER NOT NULL,
|
|
127
|
-
ended_at INTEGER,
|
|
128
|
-
updated_at INTEGER NOT NULL
|
|
129
|
-
);
|
|
130
|
-
CREATE INDEX IF NOT EXISTS idx_tasks_session ON tasks(session_key);
|
|
131
|
-
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
|
|
132
|
-
`);
|
|
133
|
-
this.migrateTaskId();
|
|
134
|
-
this.migrateContentHash();
|
|
135
|
-
this.migrateSkillTables();
|
|
136
|
-
this.migrateSkillId();
|
|
137
|
-
this.migrateSkillQualityScore();
|
|
138
|
-
this.migrateTaskSkillMeta();
|
|
139
|
-
this.migrateToolCalls();
|
|
140
|
-
this.migrateMergeFields();
|
|
141
|
-
this.migrateApiLogs();
|
|
142
|
-
this.migrateDedupStatus();
|
|
143
|
-
this.migrateChunksIndexesForRecall();
|
|
144
|
-
this.migrateOwnerFields();
|
|
145
|
-
this.migrateSkillVisibility();
|
|
146
|
-
this.migrateSkillEmbeddingsAndFts();
|
|
147
|
-
this.migrateFtsToTrigram();
|
|
148
|
-
this.migrateHubTables();
|
|
149
|
-
this.migrateHubFtsToTrigram();
|
|
150
|
-
this.migrateLocalSharedTasksOwner();
|
|
151
|
-
this.migrateHubUserIdentityFields();
|
|
152
|
-
this.migrateClientHubConnectionIdentityFields();
|
|
153
|
-
this.migrateTeamSharingInstanceId();
|
|
154
|
-
this.log.debug("Database schema initialized");
|
|
155
|
-
}
|
|
156
|
-
migrateChunksIndexesForRecall() {
|
|
157
|
-
this.db.exec("CREATE INDEX IF NOT EXISTS idx_chunks_dedup_created ON chunks(dedup_status, created_at DESC)");
|
|
158
|
-
}
|
|
159
|
-
migrateLocalSharedTasksOwner() {
|
|
160
|
-
try {
|
|
161
|
-
const cols = this.db.prepare("PRAGMA table_info(local_shared_tasks)").all();
|
|
162
|
-
if (cols.length > 0 && !cols.some((c) => c.name === "original_owner")) {
|
|
163
|
-
this.db.exec("ALTER TABLE local_shared_tasks ADD COLUMN original_owner TEXT NOT NULL DEFAULT 'agent:main'");
|
|
164
|
-
this.log.info("Migrated: added original_owner column to local_shared_tasks");
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
catch { /* table may not exist yet */ }
|
|
168
|
-
}
|
|
169
|
-
migrateHubUserIdentityFields() {
|
|
170
|
-
try {
|
|
171
|
-
const cols = this.db.prepare("PRAGMA table_info(hub_users)").all();
|
|
172
|
-
if (cols.length === 0)
|
|
173
|
-
return;
|
|
174
|
-
if (!cols.some(c => c.name === "identity_key")) {
|
|
175
|
-
this.db.exec("ALTER TABLE hub_users ADD COLUMN identity_key TEXT NOT NULL DEFAULT ''");
|
|
176
|
-
this.db.exec("CREATE INDEX IF NOT EXISTS idx_hub_users_identity_key ON hub_users(identity_key)");
|
|
177
|
-
this.log.info("Migrated: added identity_key to hub_users");
|
|
178
|
-
}
|
|
179
|
-
if (!cols.some(c => c.name === "left_at")) {
|
|
180
|
-
this.db.exec("ALTER TABLE hub_users ADD COLUMN left_at INTEGER");
|
|
181
|
-
this.log.info("Migrated: added left_at to hub_users");
|
|
182
|
-
}
|
|
183
|
-
if (!cols.some(c => c.name === "removed_at")) {
|
|
184
|
-
this.db.exec("ALTER TABLE hub_users ADD COLUMN removed_at INTEGER");
|
|
185
|
-
this.log.info("Migrated: added removed_at to hub_users");
|
|
186
|
-
}
|
|
187
|
-
if (!cols.some(c => c.name === "rejected_at")) {
|
|
188
|
-
this.db.exec("ALTER TABLE hub_users ADD COLUMN rejected_at INTEGER");
|
|
189
|
-
this.log.info("Migrated: added rejected_at to hub_users");
|
|
190
|
-
}
|
|
191
|
-
if (!cols.some(c => c.name === "rejoin_requested_at")) {
|
|
192
|
-
this.db.exec("ALTER TABLE hub_users ADD COLUMN rejoin_requested_at INTEGER");
|
|
193
|
-
this.log.info("Migrated: added rejoin_requested_at to hub_users");
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
catch { /* table may not exist yet */ }
|
|
197
|
-
}
|
|
198
|
-
migrateClientHubConnectionIdentityFields() {
|
|
199
|
-
try {
|
|
200
|
-
const cols = this.db.prepare("PRAGMA table_info(client_hub_connection)").all();
|
|
201
|
-
if (cols.length === 0)
|
|
202
|
-
return;
|
|
203
|
-
if (!cols.some(c => c.name === "identity_key")) {
|
|
204
|
-
this.db.exec("ALTER TABLE client_hub_connection ADD COLUMN identity_key TEXT NOT NULL DEFAULT ''");
|
|
205
|
-
this.log.info("Migrated: added identity_key to client_hub_connection");
|
|
206
|
-
}
|
|
207
|
-
if (!cols.some(c => c.name === "last_known_status")) {
|
|
208
|
-
this.db.exec("ALTER TABLE client_hub_connection ADD COLUMN last_known_status TEXT NOT NULL DEFAULT ''");
|
|
209
|
-
this.log.info("Migrated: added last_known_status to client_hub_connection");
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
catch { /* table may not exist yet */ }
|
|
213
|
-
}
|
|
214
|
-
migrateTeamSharingInstanceId() {
|
|
215
|
-
try {
|
|
216
|
-
const tscCols = this.db.prepare("PRAGMA table_info(team_shared_chunks)").all();
|
|
217
|
-
if (tscCols.length > 0 && !tscCols.some(c => c.name === "hub_instance_id")) {
|
|
218
|
-
this.db.exec("ALTER TABLE team_shared_chunks ADD COLUMN hub_instance_id TEXT NOT NULL DEFAULT ''");
|
|
219
|
-
this.log.info("Migrated: added hub_instance_id to team_shared_chunks");
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
catch { /* table may not exist yet */ }
|
|
223
|
-
try {
|
|
224
|
-
const lstCols = this.db.prepare("PRAGMA table_info(local_shared_tasks)").all();
|
|
225
|
-
if (lstCols.length > 0 && !lstCols.some(c => c.name === "hub_instance_id")) {
|
|
226
|
-
this.db.exec("ALTER TABLE local_shared_tasks ADD COLUMN hub_instance_id TEXT NOT NULL DEFAULT ''");
|
|
227
|
-
this.log.info("Migrated: added hub_instance_id to local_shared_tasks");
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
catch { /* table may not exist yet */ }
|
|
231
|
-
try {
|
|
232
|
-
const connCols = this.db.prepare("PRAGMA table_info(client_hub_connection)").all();
|
|
233
|
-
if (connCols.length > 0 && !connCols.some(c => c.name === "hub_instance_id")) {
|
|
234
|
-
this.db.exec("ALTER TABLE client_hub_connection ADD COLUMN hub_instance_id TEXT NOT NULL DEFAULT ''");
|
|
235
|
-
this.log.info("Migrated: added hub_instance_id to client_hub_connection");
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
catch { /* table may not exist yet */ }
|
|
239
|
-
this.db.exec(`
|
|
240
|
-
CREATE TABLE IF NOT EXISTS team_shared_skills (
|
|
241
|
-
skill_id TEXT PRIMARY KEY,
|
|
242
|
-
hub_skill_id TEXT NOT NULL DEFAULT '',
|
|
243
|
-
visibility TEXT NOT NULL DEFAULT 'public',
|
|
244
|
-
group_id TEXT,
|
|
245
|
-
hub_instance_id TEXT NOT NULL DEFAULT '',
|
|
246
|
-
shared_at INTEGER NOT NULL
|
|
247
|
-
)
|
|
248
|
-
`);
|
|
249
|
-
}
|
|
250
|
-
migrateOwnerFields() {
|
|
251
|
-
const chunkCols = this.db.prepare("PRAGMA table_info(chunks)").all();
|
|
252
|
-
if (!chunkCols.some((c) => c.name === "owner")) {
|
|
253
|
-
this.db.exec("ALTER TABLE chunks ADD COLUMN owner TEXT NOT NULL DEFAULT 'agent:main'");
|
|
254
|
-
this.db.exec("CREATE INDEX IF NOT EXISTS idx_chunks_owner ON chunks(owner)");
|
|
255
|
-
this.log.info("Migrated: added owner column to chunks");
|
|
256
|
-
}
|
|
257
|
-
const taskCols = this.db.prepare("PRAGMA table_info(tasks)").all();
|
|
258
|
-
if (!taskCols.some((c) => c.name === "owner")) {
|
|
259
|
-
this.db.exec("ALTER TABLE tasks ADD COLUMN owner TEXT NOT NULL DEFAULT 'agent:main'");
|
|
260
|
-
this.db.exec("CREATE INDEX IF NOT EXISTS idx_tasks_owner ON tasks(owner)");
|
|
261
|
-
this.log.info("Migrated: added owner column to tasks");
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
migrateSkillVisibility() {
|
|
265
|
-
const cols = this.db.prepare("PRAGMA table_info(skills)").all();
|
|
266
|
-
if (!cols.some((c) => c.name === "owner")) {
|
|
267
|
-
this.db.exec("ALTER TABLE skills ADD COLUMN owner TEXT NOT NULL DEFAULT 'agent:main'");
|
|
268
|
-
this.db.exec("CREATE INDEX IF NOT EXISTS idx_skills_owner ON skills(owner)");
|
|
269
|
-
this.log.info("Migrated: added owner column to skills");
|
|
270
|
-
}
|
|
271
|
-
if (!cols.some((c) => c.name === "visibility")) {
|
|
272
|
-
this.db.exec("ALTER TABLE skills ADD COLUMN visibility TEXT NOT NULL DEFAULT 'private'");
|
|
273
|
-
this.db.exec("CREATE INDEX IF NOT EXISTS idx_skills_visibility ON skills(visibility)");
|
|
274
|
-
this.log.info("Migrated: added visibility column to skills");
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
migrateSkillEmbeddingsAndFts() {
|
|
278
|
-
this.db.exec(`
|
|
279
|
-
CREATE TABLE IF NOT EXISTS skill_embeddings (
|
|
280
|
-
skill_id TEXT PRIMARY KEY REFERENCES skills(id) ON DELETE CASCADE,
|
|
281
|
-
vector BLOB NOT NULL,
|
|
282
|
-
dimensions INTEGER NOT NULL,
|
|
283
|
-
updated_at INTEGER NOT NULL
|
|
284
|
-
);
|
|
285
|
-
|
|
286
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS skills_fts USING fts5(
|
|
287
|
-
name,
|
|
288
|
-
description,
|
|
289
|
-
content='skills',
|
|
290
|
-
content_rowid='rowid',
|
|
291
|
-
tokenize='trigram'
|
|
292
|
-
);
|
|
293
|
-
`);
|
|
294
|
-
try {
|
|
295
|
-
this.db.exec(`
|
|
296
|
-
CREATE TRIGGER IF NOT EXISTS skills_ai AFTER INSERT ON skills BEGIN
|
|
297
|
-
INSERT INTO skills_fts(rowid, name, description)
|
|
298
|
-
VALUES (new.rowid, new.name, new.description);
|
|
299
|
-
END;
|
|
300
|
-
CREATE TRIGGER IF NOT EXISTS skills_ad AFTER DELETE ON skills BEGIN
|
|
301
|
-
INSERT INTO skills_fts(skills_fts, rowid, name, description)
|
|
302
|
-
VALUES ('delete', old.rowid, old.name, old.description);
|
|
303
|
-
END;
|
|
304
|
-
CREATE TRIGGER IF NOT EXISTS skills_au AFTER UPDATE ON skills BEGIN
|
|
305
|
-
INSERT INTO skills_fts(skills_fts, rowid, name, description)
|
|
306
|
-
VALUES ('delete', old.rowid, old.name, old.description);
|
|
307
|
-
INSERT INTO skills_fts(rowid, name, description)
|
|
308
|
-
VALUES (new.rowid, new.name, new.description);
|
|
309
|
-
END;
|
|
310
|
-
`);
|
|
311
|
-
}
|
|
312
|
-
catch {
|
|
313
|
-
// triggers may already exist
|
|
314
|
-
}
|
|
315
|
-
// Backfill FTS for existing skills
|
|
316
|
-
try {
|
|
317
|
-
const count = this.db.prepare("SELECT COUNT(*) as c FROM skills_fts").get().c;
|
|
318
|
-
const skillCount = this.db.prepare("SELECT COUNT(*) as c FROM skills").get().c;
|
|
319
|
-
if (count === 0 && skillCount > 0) {
|
|
320
|
-
this.db.exec("INSERT INTO skills_fts(rowid, name, description) SELECT rowid, name, description FROM skills");
|
|
321
|
-
this.log.info(`Migrated: backfilled skills_fts for ${skillCount} skills`);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
catch { /* best-effort */ }
|
|
325
|
-
}
|
|
326
|
-
migrateFtsToTrigram() {
|
|
327
|
-
// Check if chunks_fts still uses the old tokenizer (porter unicode61)
|
|
328
|
-
try {
|
|
329
|
-
const row = this.db.prepare("SELECT sql FROM sqlite_master WHERE name='chunks_fts'").get();
|
|
330
|
-
if (row && row.sql && !row.sql.includes("trigram")) {
|
|
331
|
-
this.log.info("Migrating chunks_fts from porter/unicode61 to trigram tokenizer...");
|
|
332
|
-
this.db.exec("DROP TRIGGER IF EXISTS chunks_ai");
|
|
333
|
-
this.db.exec("DROP TRIGGER IF EXISTS chunks_ad");
|
|
334
|
-
this.db.exec("DROP TRIGGER IF EXISTS chunks_au");
|
|
335
|
-
this.db.exec("DROP TABLE IF EXISTS chunks_fts");
|
|
336
|
-
this.db.exec(`
|
|
337
|
-
CREATE VIRTUAL TABLE chunks_fts USING fts5(
|
|
338
|
-
summary, content, content='chunks', content_rowid='rowid',
|
|
339
|
-
tokenize='trigram'
|
|
340
|
-
)
|
|
341
|
-
`);
|
|
342
|
-
this.db.exec(`
|
|
343
|
-
CREATE TRIGGER chunks_ai AFTER INSERT ON chunks BEGIN
|
|
344
|
-
INSERT INTO chunks_fts(rowid, summary, content) VALUES (new.rowid, new.summary, new.content);
|
|
345
|
-
END;
|
|
346
|
-
CREATE TRIGGER chunks_ad AFTER DELETE ON chunks BEGIN
|
|
347
|
-
INSERT INTO chunks_fts(chunks_fts, rowid, summary, content) VALUES ('delete', old.rowid, old.summary, old.content);
|
|
348
|
-
END;
|
|
349
|
-
CREATE TRIGGER chunks_au AFTER UPDATE ON chunks BEGIN
|
|
350
|
-
INSERT INTO chunks_fts(chunks_fts, rowid, summary, content) VALUES ('delete', old.rowid, old.summary, old.content);
|
|
351
|
-
INSERT INTO chunks_fts(rowid, summary, content) VALUES (new.rowid, new.summary, new.content);
|
|
352
|
-
END
|
|
353
|
-
`);
|
|
354
|
-
this.db.exec("INSERT INTO chunks_fts(rowid, summary, content) SELECT rowid, summary, content FROM chunks");
|
|
355
|
-
const count = this.db.prepare("SELECT COUNT(*) as c FROM chunks_fts").get().c;
|
|
356
|
-
this.log.info(`Migrated chunks_fts to trigram: ${count} rows indexed`);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
catch (err) {
|
|
360
|
-
this.log.warn(`Failed to migrate chunks_fts to trigram: ${err}`);
|
|
361
|
-
}
|
|
362
|
-
// Same for skills_fts
|
|
363
|
-
try {
|
|
364
|
-
const row = this.db.prepare("SELECT sql FROM sqlite_master WHERE name='skills_fts'").get();
|
|
365
|
-
if (row && row.sql && !row.sql.includes("trigram")) {
|
|
366
|
-
this.log.info("Migrating skills_fts to trigram tokenizer...");
|
|
367
|
-
this.db.exec("DROP TRIGGER IF EXISTS skills_ai");
|
|
368
|
-
this.db.exec("DROP TRIGGER IF EXISTS skills_ad");
|
|
369
|
-
this.db.exec("DROP TRIGGER IF EXISTS skills_au");
|
|
370
|
-
this.db.exec("DROP TABLE IF EXISTS skills_fts");
|
|
371
|
-
this.db.exec(`
|
|
372
|
-
CREATE VIRTUAL TABLE skills_fts USING fts5(
|
|
373
|
-
name, description, content='skills', content_rowid='rowid',
|
|
374
|
-
tokenize='trigram'
|
|
375
|
-
)
|
|
376
|
-
`);
|
|
377
|
-
this.db.exec(`
|
|
378
|
-
CREATE TRIGGER skills_ai AFTER INSERT ON skills BEGIN
|
|
379
|
-
INSERT INTO skills_fts(rowid, name, description) VALUES (new.rowid, new.name, new.description);
|
|
380
|
-
END;
|
|
381
|
-
CREATE TRIGGER skills_ad AFTER DELETE ON skills BEGIN
|
|
382
|
-
INSERT INTO skills_fts(skills_fts, rowid, name, description) VALUES ('delete', old.rowid, old.name, old.description);
|
|
383
|
-
END;
|
|
384
|
-
CREATE TRIGGER skills_au AFTER UPDATE ON skills BEGIN
|
|
385
|
-
INSERT INTO skills_fts(skills_fts, rowid, name, description) VALUES ('delete', old.rowid, old.name, old.description);
|
|
386
|
-
INSERT INTO skills_fts(rowid, name, description) VALUES (new.rowid, new.name, new.description);
|
|
387
|
-
END
|
|
388
|
-
`);
|
|
389
|
-
this.db.exec("INSERT INTO skills_fts(rowid, name, description) SELECT rowid, name, description FROM skills");
|
|
390
|
-
this.log.info("Migrated skills_fts to trigram");
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
catch (err) {
|
|
394
|
-
this.log.warn(`Failed to migrate skills_fts to trigram: ${err}`);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
migrateHubFtsToTrigram() {
|
|
398
|
-
const tables = [
|
|
399
|
-
{
|
|
400
|
-
fts: "hub_chunks_fts", source: "hub_chunks", columns: "summary, content",
|
|
401
|
-
triggers: ["hub_chunks_ai", "hub_chunks_ad", "hub_chunks_au"],
|
|
402
|
-
},
|
|
403
|
-
{
|
|
404
|
-
fts: "hub_skills_fts", source: "hub_skills", columns: "name, description",
|
|
405
|
-
triggers: ["hub_skills_ai", "hub_skills_ad", "hub_skills_au"],
|
|
406
|
-
},
|
|
407
|
-
{
|
|
408
|
-
fts: "hub_memories_fts", source: "hub_memories", columns: "summary, content",
|
|
409
|
-
triggers: ["hub_memories_ai", "hub_memories_ad", "hub_memories_au"],
|
|
410
|
-
},
|
|
411
|
-
];
|
|
412
|
-
for (const t of tables) {
|
|
413
|
-
try {
|
|
414
|
-
const row = this.db.prepare(`SELECT sql FROM sqlite_master WHERE name='${t.fts}'`).get();
|
|
415
|
-
if (!row || !row.sql)
|
|
416
|
-
continue;
|
|
417
|
-
if (row.sql.includes("trigram"))
|
|
418
|
-
continue;
|
|
419
|
-
this.log.info(`Migrating ${t.fts} to trigram tokenizer...`);
|
|
420
|
-
for (const tr of t.triggers)
|
|
421
|
-
this.db.exec(`DROP TRIGGER IF EXISTS ${tr}`);
|
|
422
|
-
this.db.exec(`DROP TABLE IF EXISTS ${t.fts}`);
|
|
423
|
-
this.db.exec(`CREATE VIRTUAL TABLE ${t.fts} USING fts5(${t.columns}, content='${t.source}', content_rowid='rowid', tokenize='trigram')`);
|
|
424
|
-
this.db.exec(`
|
|
425
|
-
CREATE TRIGGER ${t.triggers[0]} AFTER INSERT ON ${t.source} BEGIN
|
|
426
|
-
INSERT INTO ${t.fts}(rowid, ${t.columns}) VALUES (new.rowid, ${t.columns.split(", ").map(c => "new." + c).join(", ")});
|
|
427
|
-
END;
|
|
428
|
-
CREATE TRIGGER ${t.triggers[1]} AFTER DELETE ON ${t.source} BEGIN
|
|
429
|
-
INSERT INTO ${t.fts}(${t.fts}, rowid, ${t.columns}) VALUES ('delete', old.rowid, ${t.columns.split(", ").map(c => "old." + c).join(", ")});
|
|
430
|
-
END;
|
|
431
|
-
CREATE TRIGGER ${t.triggers[2]} AFTER UPDATE ON ${t.source} BEGIN
|
|
432
|
-
INSERT INTO ${t.fts}(${t.fts}, rowid, ${t.columns}) VALUES ('delete', old.rowid, ${t.columns.split(", ").map(c => "old." + c).join(", ")});
|
|
433
|
-
INSERT INTO ${t.fts}(rowid, ${t.columns}) VALUES (new.rowid, ${t.columns.split(", ").map(c => "new." + c).join(", ")});
|
|
434
|
-
END
|
|
435
|
-
`);
|
|
436
|
-
this.db.exec(`INSERT INTO ${t.fts}(rowid, ${t.columns}) SELECT rowid, ${t.columns} FROM ${t.source}`);
|
|
437
|
-
const cnt = this.db.prepare(`SELECT COUNT(*) as c FROM ${t.fts}`).get().c;
|
|
438
|
-
this.log.info(`Migrated ${t.fts} to trigram: ${cnt} rows indexed`);
|
|
439
|
-
}
|
|
440
|
-
catch (err) {
|
|
441
|
-
this.log.warn(`Failed to migrate ${t.fts} to trigram: ${err}`);
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
migrateTaskId() {
|
|
446
|
-
const cols = this.db.prepare("PRAGMA table_info(chunks)").all();
|
|
447
|
-
if (!cols.some((c) => c.name === "task_id")) {
|
|
448
|
-
this.db.exec("ALTER TABLE chunks ADD COLUMN task_id TEXT REFERENCES tasks(id)");
|
|
449
|
-
this.db.exec("CREATE INDEX IF NOT EXISTS idx_chunks_task ON chunks(task_id)");
|
|
450
|
-
this.log.info("Migrated: added task_id column to chunks");
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
migrateContentHash() {
|
|
454
|
-
const cols = this.db.prepare("PRAGMA table_info(chunks)").all();
|
|
455
|
-
if (!cols.some((c) => c.name === "content_hash")) {
|
|
456
|
-
this.db.exec("ALTER TABLE chunks ADD COLUMN content_hash TEXT");
|
|
457
|
-
this.db.exec("CREATE INDEX IF NOT EXISTS idx_chunks_dedup ON chunks(session_key, role, content_hash)");
|
|
458
|
-
// Backfill existing rows
|
|
459
|
-
const rows = this.db.prepare("SELECT id, content FROM chunks WHERE content_hash IS NULL").all();
|
|
460
|
-
const updateStmt = this.db.prepare("UPDATE chunks SET content_hash = ? WHERE id = ?");
|
|
461
|
-
for (const r of rows) {
|
|
462
|
-
updateStmt.run(contentHash(r.content), r.id);
|
|
463
|
-
}
|
|
464
|
-
if (rows.length > 0) {
|
|
465
|
-
this.log.info(`Migrated: backfilled content_hash for ${rows.length} chunks`);
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
migrateSkillTables() {
|
|
470
|
-
this.db.exec(`
|
|
471
|
-
CREATE TABLE IF NOT EXISTS skills (
|
|
472
|
-
id TEXT PRIMARY KEY,
|
|
473
|
-
name TEXT NOT NULL UNIQUE,
|
|
474
|
-
description TEXT NOT NULL DEFAULT '',
|
|
475
|
-
version INTEGER NOT NULL DEFAULT 1,
|
|
476
|
-
status TEXT NOT NULL DEFAULT 'active',
|
|
477
|
-
tags TEXT NOT NULL DEFAULT '[]',
|
|
478
|
-
source_type TEXT NOT NULL DEFAULT 'task',
|
|
479
|
-
dir_path TEXT NOT NULL DEFAULT '',
|
|
480
|
-
installed INTEGER NOT NULL DEFAULT 0,
|
|
481
|
-
created_at INTEGER NOT NULL,
|
|
482
|
-
updated_at INTEGER NOT NULL
|
|
483
|
-
);
|
|
484
|
-
CREATE INDEX IF NOT EXISTS idx_skills_status ON skills(status);
|
|
485
|
-
CREATE INDEX IF NOT EXISTS idx_skills_name ON skills(name);
|
|
486
|
-
|
|
487
|
-
CREATE TABLE IF NOT EXISTS skill_versions (
|
|
488
|
-
id TEXT PRIMARY KEY,
|
|
489
|
-
skill_id TEXT NOT NULL REFERENCES skills(id),
|
|
490
|
-
version INTEGER NOT NULL,
|
|
491
|
-
content TEXT NOT NULL,
|
|
492
|
-
changelog TEXT NOT NULL DEFAULT '',
|
|
493
|
-
upgrade_type TEXT NOT NULL DEFAULT 'create',
|
|
494
|
-
source_task_id TEXT,
|
|
495
|
-
metrics TEXT NOT NULL DEFAULT '{}',
|
|
496
|
-
created_at INTEGER NOT NULL,
|
|
497
|
-
UNIQUE(skill_id, version)
|
|
498
|
-
);
|
|
499
|
-
CREATE INDEX IF NOT EXISTS idx_skill_versions_skill ON skill_versions(skill_id);
|
|
500
|
-
|
|
501
|
-
CREATE TABLE IF NOT EXISTS task_skills (
|
|
502
|
-
task_id TEXT NOT NULL REFERENCES tasks(id),
|
|
503
|
-
skill_id TEXT NOT NULL REFERENCES skills(id),
|
|
504
|
-
relation TEXT NOT NULL DEFAULT 'generated_from',
|
|
505
|
-
version_at INTEGER NOT NULL DEFAULT 1,
|
|
506
|
-
created_at INTEGER NOT NULL,
|
|
507
|
-
PRIMARY KEY (task_id, skill_id)
|
|
508
|
-
);
|
|
509
|
-
`);
|
|
510
|
-
}
|
|
511
|
-
migrateSkillId() {
|
|
512
|
-
const cols = this.db.prepare("PRAGMA table_info(chunks)").all();
|
|
513
|
-
if (!cols.some((c) => c.name === "skill_id")) {
|
|
514
|
-
this.db.exec("ALTER TABLE chunks ADD COLUMN skill_id TEXT");
|
|
515
|
-
this.db.exec("CREATE INDEX IF NOT EXISTS idx_chunks_skill ON chunks(skill_id)");
|
|
516
|
-
this.log.info("Migrated: added skill_id column to chunks");
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
migrateSkillQualityScore() {
|
|
520
|
-
const skillCols = this.db.prepare("PRAGMA table_info(skills)").all();
|
|
521
|
-
if (!skillCols.some((c) => c.name === "quality_score")) {
|
|
522
|
-
this.db.exec("ALTER TABLE skills ADD COLUMN quality_score REAL");
|
|
523
|
-
this.log.info("Migrated: added quality_score column to skills");
|
|
524
|
-
}
|
|
525
|
-
const versionCols = this.db.prepare("PRAGMA table_info(skill_versions)").all();
|
|
526
|
-
if (!versionCols.some((c) => c.name === "quality_score")) {
|
|
527
|
-
this.db.exec("ALTER TABLE skill_versions ADD COLUMN quality_score REAL");
|
|
528
|
-
this.log.info("Migrated: added quality_score column to skill_versions");
|
|
529
|
-
}
|
|
530
|
-
if (!versionCols.some((c) => c.name === "change_summary")) {
|
|
531
|
-
this.db.exec("ALTER TABLE skill_versions ADD COLUMN change_summary TEXT NOT NULL DEFAULT ''");
|
|
532
|
-
this.log.info("Migrated: added change_summary column to skill_versions");
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
migrateTaskSkillMeta() {
|
|
536
|
-
const cols = this.db.prepare("PRAGMA table_info(tasks)").all();
|
|
537
|
-
if (!cols.some((c) => c.name === "skill_status")) {
|
|
538
|
-
this.db.exec("ALTER TABLE tasks ADD COLUMN skill_status TEXT DEFAULT NULL");
|
|
539
|
-
this.db.exec("ALTER TABLE tasks ADD COLUMN skill_reason TEXT DEFAULT NULL");
|
|
540
|
-
this.log.info("Migrated: added skill_status/skill_reason columns to tasks");
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
setTaskSkillMeta(taskId, meta) {
|
|
544
|
-
this.db.prepare("UPDATE tasks SET skill_status = ?, skill_reason = ?, updated_at = ? WHERE id = ?")
|
|
545
|
-
.run(meta.skillStatus, meta.skillReason, Date.now(), taskId);
|
|
546
|
-
}
|
|
547
|
-
getTasksBySkillStatus(statuses) {
|
|
548
|
-
const placeholders = statuses.map(() => "?").join(",");
|
|
549
|
-
const rows = this.db.prepare(`SELECT * FROM tasks WHERE skill_status IN (${placeholders}) AND status = 'completed' ORDER BY updated_at ASC`).all(...statuses);
|
|
550
|
-
return rows.map(rowToTask);
|
|
551
|
-
}
|
|
552
|
-
migrateMergeFields() {
|
|
553
|
-
const cols = this.db.prepare("PRAGMA table_info(chunks)").all();
|
|
554
|
-
if (!cols.some((c) => c.name === "merge_count")) {
|
|
555
|
-
this.db.exec("ALTER TABLE chunks ADD COLUMN merge_count INTEGER NOT NULL DEFAULT 0");
|
|
556
|
-
this.db.exec("ALTER TABLE chunks ADD COLUMN last_hit_at INTEGER");
|
|
557
|
-
this.db.exec("ALTER TABLE chunks ADD COLUMN merge_history TEXT NOT NULL DEFAULT '[]'");
|
|
558
|
-
this.log.info("Migrated: added merge_count/last_hit_at/merge_history columns to chunks");
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
migrateApiLogs() {
|
|
562
|
-
this.db.exec(`
|
|
563
|
-
CREATE TABLE IF NOT EXISTS api_logs (
|
|
564
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
565
|
-
tool_name TEXT NOT NULL,
|
|
566
|
-
input_data TEXT NOT NULL DEFAULT '{}',
|
|
567
|
-
output_data TEXT NOT NULL DEFAULT '',
|
|
568
|
-
duration_ms INTEGER NOT NULL DEFAULT 0,
|
|
569
|
-
success INTEGER NOT NULL DEFAULT 1,
|
|
570
|
-
called_at INTEGER NOT NULL
|
|
571
|
-
);
|
|
572
|
-
CREATE INDEX IF NOT EXISTS idx_api_logs_at ON api_logs(called_at);
|
|
573
|
-
CREATE INDEX IF NOT EXISTS idx_api_logs_name ON api_logs(tool_name);
|
|
574
|
-
`);
|
|
575
|
-
}
|
|
576
|
-
migrateDedupStatus() {
|
|
577
|
-
const cols = this.db.prepare("PRAGMA table_info(chunks)").all();
|
|
578
|
-
if (!cols.some((c) => c.name === "dedup_status")) {
|
|
579
|
-
this.db.exec("ALTER TABLE chunks ADD COLUMN dedup_status TEXT NOT NULL DEFAULT 'active'");
|
|
580
|
-
this.db.exec("ALTER TABLE chunks ADD COLUMN dedup_target TEXT DEFAULT NULL");
|
|
581
|
-
this.db.exec("ALTER TABLE chunks ADD COLUMN dedup_reason TEXT DEFAULT NULL");
|
|
582
|
-
this.db.exec("CREATE INDEX IF NOT EXISTS idx_chunks_dedup_status ON chunks(dedup_status)");
|
|
583
|
-
this.log.info("Migrated: added dedup_status/dedup_target/dedup_reason columns to chunks");
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
recordApiLog(toolName, input, output, durationMs, success) {
|
|
587
|
-
const inputStr = typeof input === "string" ? input : JSON.stringify(input ?? {});
|
|
588
|
-
this.db.prepare("INSERT INTO api_logs (tool_name, input_data, output_data, duration_ms, success, called_at) VALUES (?, ?, ?, ?, ?, ?)").run(toolName, inputStr, output, Math.round(durationMs), success ? 1 : 0, Date.now());
|
|
589
|
-
}
|
|
590
|
-
getApiLogs(limit = 50, offset = 0, toolFilter) {
|
|
591
|
-
const whereClause = toolFilter ? " WHERE tool_name = ?" : "";
|
|
592
|
-
const filterParams = toolFilter ? [toolFilter] : [];
|
|
593
|
-
const countRow = this.db.prepare("SELECT COUNT(*) as c FROM api_logs" + whereClause).get(...filterParams);
|
|
594
|
-
const rows = this.db.prepare("SELECT id, tool_name, input_data, output_data, duration_ms, success, called_at FROM api_logs" +
|
|
595
|
-
whereClause + " ORDER BY called_at DESC LIMIT ? OFFSET ?").all(...filterParams, limit, offset);
|
|
596
|
-
return {
|
|
597
|
-
logs: rows.map((r) => ({
|
|
598
|
-
id: r.id,
|
|
599
|
-
toolName: r.tool_name,
|
|
600
|
-
input: r.input_data,
|
|
601
|
-
output: r.output_data,
|
|
602
|
-
durationMs: r.duration_ms,
|
|
603
|
-
success: r.success === 1,
|
|
604
|
-
calledAt: r.called_at,
|
|
605
|
-
})),
|
|
606
|
-
total: countRow.c,
|
|
607
|
-
};
|
|
608
|
-
}
|
|
609
|
-
getApiLogToolNames() {
|
|
610
|
-
const rows = this.db.prepare("SELECT DISTINCT tool_name FROM api_logs ORDER BY tool_name").all();
|
|
611
|
-
return rows.map((r) => r.tool_name);
|
|
612
|
-
}
|
|
613
|
-
recordMergeHit(chunkId, action, reason, oldSummary, newSummary) {
|
|
614
|
-
const chunk = this.getChunk(chunkId);
|
|
615
|
-
if (!chunk)
|
|
616
|
-
return;
|
|
617
|
-
const history = JSON.parse(chunk.mergeHistory || "[]");
|
|
618
|
-
const entry = { at: Date.now(), action, reason };
|
|
619
|
-
if (action === "UPDATE" && oldSummary && newSummary) {
|
|
620
|
-
entry.from = oldSummary;
|
|
621
|
-
entry.to = newSummary;
|
|
622
|
-
}
|
|
623
|
-
history.push(entry);
|
|
624
|
-
this.db.prepare(`
|
|
625
|
-
UPDATE chunks SET merge_count = merge_count + 1, last_hit_at = ?, merge_history = ?, updated_at = ?
|
|
626
|
-
WHERE id = ?
|
|
627
|
-
`).run(Date.now(), JSON.stringify(history), Date.now(), chunkId);
|
|
628
|
-
}
|
|
629
|
-
updateChunkSummaryAndContent(chunkId, newSummary, appendContent) {
|
|
630
|
-
this.db.prepare(`
|
|
631
|
-
UPDATE chunks SET summary = ?, content = content || ? || ?, updated_at = ? WHERE id = ?
|
|
632
|
-
`).run(newSummary, "\n\n---\n\n", appendContent, Date.now(), chunkId);
|
|
633
|
-
}
|
|
634
|
-
migrateToolCalls() {
|
|
635
|
-
this.db.exec(`
|
|
636
|
-
CREATE TABLE IF NOT EXISTS tool_calls (
|
|
637
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
638
|
-
tool_name TEXT NOT NULL,
|
|
639
|
-
duration_ms INTEGER NOT NULL,
|
|
640
|
-
success INTEGER NOT NULL DEFAULT 1,
|
|
641
|
-
called_at INTEGER NOT NULL
|
|
642
|
-
);
|
|
643
|
-
CREATE INDEX IF NOT EXISTS idx_tool_calls_at ON tool_calls(called_at);
|
|
644
|
-
CREATE INDEX IF NOT EXISTS idx_tool_calls_name ON tool_calls(tool_name);
|
|
645
|
-
`);
|
|
646
|
-
}
|
|
647
|
-
recordToolCall(toolName, durationMs, success) {
|
|
648
|
-
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());
|
|
649
|
-
}
|
|
650
|
-
getToolMetrics(minutes, fromMs, toMs) {
|
|
651
|
-
const since = fromMs ?? (Date.now() - minutes * 60 * 1000);
|
|
652
|
-
const until = toMs ?? Date.now();
|
|
653
|
-
const rows = this.db.prepare(`SELECT tool_name,
|
|
654
|
-
duration_ms,
|
|
655
|
-
success,
|
|
656
|
-
strftime('%Y-%m-%d %H:%M', called_at/1000, 'unixepoch', 'localtime') as minute_key
|
|
657
|
-
FROM tool_calls
|
|
658
|
-
WHERE called_at >= ? AND called_at <= ?
|
|
659
|
-
ORDER BY called_at`).all(since, until);
|
|
660
|
-
const toolSet = new Set();
|
|
661
|
-
const minuteMap = new Map();
|
|
662
|
-
const aggMap = new Map();
|
|
663
|
-
for (const r of rows) {
|
|
664
|
-
toolSet.add(r.tool_name);
|
|
665
|
-
if (!aggMap.has(r.tool_name))
|
|
666
|
-
aggMap.set(r.tool_name, { durations: [], errors: 0 });
|
|
667
|
-
const agg = aggMap.get(r.tool_name);
|
|
668
|
-
agg.durations.push(r.duration_ms);
|
|
669
|
-
if (!r.success)
|
|
670
|
-
agg.errors++;
|
|
671
|
-
if (!minuteMap.has(r.minute_key))
|
|
672
|
-
minuteMap.set(r.minute_key, new Map());
|
|
673
|
-
const toolMap = minuteMap.get(r.minute_key);
|
|
674
|
-
if (!toolMap.has(r.tool_name))
|
|
675
|
-
toolMap.set(r.tool_name, { total: 0, count: 0 });
|
|
676
|
-
const entry = toolMap.get(r.tool_name);
|
|
677
|
-
entry.total += r.duration_ms;
|
|
678
|
-
entry.count++;
|
|
679
|
-
}
|
|
680
|
-
const tools = Array.from(toolSet).sort();
|
|
681
|
-
const allMinutes = [];
|
|
682
|
-
if (minutes > 0) {
|
|
683
|
-
const startMinute = new Date(since);
|
|
684
|
-
startMinute.setSeconds(0, 0);
|
|
685
|
-
const now = new Date();
|
|
686
|
-
for (let t = startMinute.getTime(); t <= now.getTime(); t += 60000) {
|
|
687
|
-
const d = new Date(t);
|
|
688
|
-
const pad = (n) => String(n).padStart(2, "0");
|
|
689
|
-
allMinutes.push(`${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`);
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
const series = allMinutes.map((m) => {
|
|
693
|
-
const entry = { minute: m };
|
|
694
|
-
const toolMap = minuteMap.get(m);
|
|
695
|
-
for (const t of tools) {
|
|
696
|
-
const data = toolMap?.get(t);
|
|
697
|
-
entry[t] = data ? Math.round(data.total / data.count) : 0;
|
|
698
|
-
}
|
|
699
|
-
return entry;
|
|
700
|
-
});
|
|
701
|
-
const p95 = (arr) => {
|
|
702
|
-
if (arr.length === 0)
|
|
703
|
-
return 0;
|
|
704
|
-
const sorted = [...arr].sort((a, b) => a - b);
|
|
705
|
-
return sorted[Math.floor(sorted.length * 0.95)] ?? sorted[sorted.length - 1];
|
|
706
|
-
};
|
|
707
|
-
const aggregated = tools.map((t) => {
|
|
708
|
-
const agg = aggMap.get(t);
|
|
709
|
-
return {
|
|
710
|
-
tool: t,
|
|
711
|
-
totalCalls: agg.durations.length,
|
|
712
|
-
avgMs: Math.round(agg.durations.reduce((s, v) => s + v, 0) / agg.durations.length),
|
|
713
|
-
p95Ms: p95(agg.durations),
|
|
714
|
-
errorCount: agg.errors,
|
|
715
|
-
};
|
|
716
|
-
});
|
|
717
|
-
return { tools, series, aggregated };
|
|
718
|
-
}
|
|
719
|
-
/** Record a viewer API call for analytics (list, search, etc.). */
|
|
720
|
-
recordViewerEvent(eventType) {
|
|
721
|
-
this.db.prepare("INSERT INTO viewer_events (event_type, created_at) VALUES (?, ?)").run(eventType, Date.now());
|
|
722
|
-
}
|
|
723
|
-
/**
|
|
724
|
-
* Return metrics for the last N days: writes per day (from chunks), viewer calls per day.
|
|
725
|
-
*/
|
|
726
|
-
getMetrics(days) {
|
|
727
|
-
const since = Date.now() - days * 86400 * 1000;
|
|
728
|
-
const now = new Date();
|
|
729
|
-
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
|
|
730
|
-
const writesRows = this.db
|
|
731
|
-
.prepare(`SELECT date(created_at/1000, 'unixepoch', 'localtime') as d, COUNT(*) as c
|
|
732
|
-
FROM chunks WHERE created_at >= ? GROUP BY d ORDER BY d`)
|
|
733
|
-
.all(since);
|
|
734
|
-
const writesPerDay = writesRows.map((r) => ({ date: r.d, count: r.c }));
|
|
735
|
-
const eventsRows = this.db
|
|
736
|
-
.prepare(`SELECT date(created_at/1000, 'unixepoch', 'localtime') as d, event_type, COUNT(*) as c
|
|
737
|
-
FROM viewer_events WHERE created_at >= ? GROUP BY d, event_type ORDER BY d`)
|
|
738
|
-
.all(since);
|
|
739
|
-
const byDate = new Map();
|
|
740
|
-
for (const r of eventsRows) {
|
|
741
|
-
let row = byDate.get(r.d);
|
|
742
|
-
if (!row) {
|
|
743
|
-
row = { list: 0, search: 0 };
|
|
744
|
-
byDate.set(r.d, row);
|
|
745
|
-
}
|
|
746
|
-
if (r.event_type === "list")
|
|
747
|
-
row.list += r.c;
|
|
748
|
-
else if (r.event_type === "search")
|
|
749
|
-
row.search += r.c;
|
|
750
|
-
}
|
|
751
|
-
const viewerCallsPerDay = Array.from(byDate.entries())
|
|
752
|
-
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
753
|
-
.map(([date, v]) => ({ date, list: v.list, search: v.search, total: v.list + v.search }));
|
|
754
|
-
const totalChunks = this.db.prepare("SELECT COUNT(*) as c FROM chunks").get().c;
|
|
755
|
-
const totalSessions = this.db.prepare("SELECT COUNT(DISTINCT session_key) as c FROM chunks").get().c;
|
|
756
|
-
const totalEmbeddings = this.db.prepare("SELECT COUNT(*) as c FROM embeddings").get().c;
|
|
757
|
-
const todayWrites = this.db.prepare("SELECT COUNT(*) as c FROM chunks WHERE created_at >= ?").get(todayStart).c;
|
|
758
|
-
const todayViewerCalls = this.db.prepare("SELECT COUNT(*) as c FROM viewer_events WHERE created_at >= ?").get(todayStart).c;
|
|
759
|
-
return {
|
|
760
|
-
writesPerDay,
|
|
761
|
-
viewerCallsPerDay,
|
|
762
|
-
totals: {
|
|
763
|
-
memories: totalChunks,
|
|
764
|
-
sessions: totalSessions,
|
|
765
|
-
embeddings: totalEmbeddings,
|
|
766
|
-
todayWrites,
|
|
767
|
-
todayViewerCalls,
|
|
768
|
-
},
|
|
769
|
-
};
|
|
770
|
-
}
|
|
771
|
-
migrateHubTables() {
|
|
772
|
-
this.db.exec(`
|
|
773
|
-
CREATE TABLE IF NOT EXISTS client_hub_connection (
|
|
774
|
-
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
775
|
-
hub_url TEXT NOT NULL,
|
|
776
|
-
user_id TEXT NOT NULL,
|
|
777
|
-
username TEXT NOT NULL,
|
|
778
|
-
user_token TEXT NOT NULL,
|
|
779
|
-
role TEXT NOT NULL,
|
|
780
|
-
connected_at INTEGER NOT NULL
|
|
781
|
-
);
|
|
782
|
-
|
|
783
|
-
CREATE TABLE IF NOT EXISTS local_shared_tasks (
|
|
784
|
-
task_id TEXT PRIMARY KEY,
|
|
785
|
-
hub_task_id TEXT NOT NULL,
|
|
786
|
-
visibility TEXT NOT NULL DEFAULT 'public',
|
|
787
|
-
group_id TEXT,
|
|
788
|
-
synced_chunks INTEGER NOT NULL DEFAULT 0,
|
|
789
|
-
hub_instance_id TEXT NOT NULL DEFAULT '',
|
|
790
|
-
shared_at INTEGER NOT NULL
|
|
791
|
-
);
|
|
792
|
-
|
|
793
|
-
CREATE TABLE IF NOT EXISTS local_shared_memories (
|
|
794
|
-
chunk_id TEXT PRIMARY KEY REFERENCES chunks(id) ON DELETE CASCADE,
|
|
795
|
-
original_owner TEXT NOT NULL,
|
|
796
|
-
shared_at INTEGER NOT NULL
|
|
797
|
-
);
|
|
798
|
-
|
|
799
|
-
-- Client: team share UI metadata only (no hub_memories row — avoids local FTS/embed recall duplication)
|
|
800
|
-
CREATE TABLE IF NOT EXISTS team_shared_chunks (
|
|
801
|
-
chunk_id TEXT PRIMARY KEY REFERENCES chunks(id) ON DELETE CASCADE,
|
|
802
|
-
hub_memory_id TEXT NOT NULL DEFAULT '',
|
|
803
|
-
visibility TEXT NOT NULL DEFAULT 'public',
|
|
804
|
-
group_id TEXT,
|
|
805
|
-
hub_instance_id TEXT NOT NULL DEFAULT '',
|
|
806
|
-
shared_at INTEGER NOT NULL
|
|
807
|
-
);
|
|
808
|
-
|
|
809
|
-
CREATE TABLE IF NOT EXISTS team_shared_skills (
|
|
810
|
-
skill_id TEXT PRIMARY KEY,
|
|
811
|
-
hub_skill_id TEXT NOT NULL DEFAULT '',
|
|
812
|
-
visibility TEXT NOT NULL DEFAULT 'public',
|
|
813
|
-
group_id TEXT,
|
|
814
|
-
hub_instance_id TEXT NOT NULL DEFAULT '',
|
|
815
|
-
shared_at INTEGER NOT NULL
|
|
816
|
-
);
|
|
817
|
-
|
|
818
|
-
CREATE TABLE IF NOT EXISTS hub_users (
|
|
819
|
-
id TEXT PRIMARY KEY,
|
|
820
|
-
username TEXT NOT NULL UNIQUE,
|
|
821
|
-
device_name TEXT NOT NULL DEFAULT '',
|
|
822
|
-
role TEXT NOT NULL,
|
|
823
|
-
status TEXT NOT NULL,
|
|
824
|
-
token_hash TEXT NOT NULL DEFAULT '',
|
|
825
|
-
created_at INTEGER NOT NULL,
|
|
826
|
-
approved_at INTEGER,
|
|
827
|
-
last_ip TEXT NOT NULL DEFAULT '',
|
|
828
|
-
last_active_at INTEGER
|
|
829
|
-
);
|
|
830
|
-
CREATE INDEX IF NOT EXISTS idx_hub_users_status ON hub_users(status);
|
|
831
|
-
CREATE INDEX IF NOT EXISTS idx_hub_users_role ON hub_users(role);
|
|
832
|
-
|
|
833
|
-
CREATE TABLE IF NOT EXISTS hub_groups (
|
|
834
|
-
id TEXT PRIMARY KEY,
|
|
835
|
-
name TEXT NOT NULL,
|
|
836
|
-
description TEXT NOT NULL DEFAULT '',
|
|
837
|
-
created_at INTEGER NOT NULL
|
|
838
|
-
);
|
|
839
|
-
|
|
840
|
-
CREATE TABLE IF NOT EXISTS hub_group_members (
|
|
841
|
-
group_id TEXT NOT NULL REFERENCES hub_groups(id) ON DELETE CASCADE,
|
|
842
|
-
user_id TEXT NOT NULL REFERENCES hub_users(id) ON DELETE CASCADE,
|
|
843
|
-
joined_at INTEGER NOT NULL,
|
|
844
|
-
PRIMARY KEY (group_id, user_id)
|
|
845
|
-
);
|
|
846
|
-
|
|
847
|
-
CREATE TABLE IF NOT EXISTS hub_tasks (
|
|
848
|
-
id TEXT PRIMARY KEY,
|
|
849
|
-
source_task_id TEXT NOT NULL,
|
|
850
|
-
source_user_id TEXT NOT NULL,
|
|
851
|
-
title TEXT NOT NULL,
|
|
852
|
-
summary TEXT NOT NULL DEFAULT '',
|
|
853
|
-
group_id TEXT,
|
|
854
|
-
visibility TEXT NOT NULL,
|
|
855
|
-
created_at INTEGER NOT NULL,
|
|
856
|
-
updated_at INTEGER NOT NULL,
|
|
857
|
-
UNIQUE(source_user_id, source_task_id)
|
|
858
|
-
);
|
|
859
|
-
CREATE INDEX IF NOT EXISTS idx_hub_tasks_visibility ON hub_tasks(visibility);
|
|
860
|
-
CREATE INDEX IF NOT EXISTS idx_hub_tasks_group ON hub_tasks(group_id);
|
|
861
|
-
|
|
862
|
-
CREATE TABLE IF NOT EXISTS hub_chunks (
|
|
863
|
-
id TEXT PRIMARY KEY,
|
|
864
|
-
hub_task_id TEXT NOT NULL REFERENCES hub_tasks(id) ON DELETE CASCADE,
|
|
865
|
-
source_chunk_id TEXT NOT NULL,
|
|
866
|
-
source_user_id TEXT NOT NULL,
|
|
867
|
-
role TEXT NOT NULL,
|
|
868
|
-
content TEXT NOT NULL,
|
|
869
|
-
summary TEXT NOT NULL DEFAULT '',
|
|
870
|
-
kind TEXT NOT NULL DEFAULT 'paragraph',
|
|
871
|
-
created_at INTEGER NOT NULL,
|
|
872
|
-
UNIQUE(source_user_id, source_chunk_id)
|
|
873
|
-
);
|
|
874
|
-
CREATE INDEX IF NOT EXISTS idx_hub_chunks_task ON hub_chunks(hub_task_id);
|
|
875
|
-
|
|
876
|
-
CREATE TABLE IF NOT EXISTS hub_embeddings (
|
|
877
|
-
chunk_id TEXT PRIMARY KEY REFERENCES hub_chunks(id) ON DELETE CASCADE,
|
|
878
|
-
vector BLOB NOT NULL,
|
|
879
|
-
dimensions INTEGER NOT NULL,
|
|
880
|
-
updated_at INTEGER NOT NULL
|
|
881
|
-
);
|
|
882
|
-
|
|
883
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS hub_chunks_fts USING fts5(
|
|
884
|
-
summary,
|
|
885
|
-
content,
|
|
886
|
-
content='hub_chunks',
|
|
887
|
-
content_rowid='rowid',
|
|
888
|
-
tokenize='trigram'
|
|
889
|
-
);
|
|
890
|
-
|
|
891
|
-
CREATE TRIGGER IF NOT EXISTS hub_chunks_ai AFTER INSERT ON hub_chunks BEGIN
|
|
892
|
-
INSERT INTO hub_chunks_fts(rowid, summary, content)
|
|
893
|
-
VALUES (new.rowid, new.summary, new.content);
|
|
894
|
-
END;
|
|
895
|
-
|
|
896
|
-
CREATE TRIGGER IF NOT EXISTS hub_chunks_ad AFTER DELETE ON hub_chunks BEGIN
|
|
897
|
-
INSERT INTO hub_chunks_fts(hub_chunks_fts, rowid, summary, content)
|
|
898
|
-
VALUES ('delete', old.rowid, old.summary, old.content);
|
|
899
|
-
END;
|
|
900
|
-
|
|
901
|
-
CREATE TRIGGER IF NOT EXISTS hub_chunks_au AFTER UPDATE ON hub_chunks BEGIN
|
|
902
|
-
INSERT INTO hub_chunks_fts(hub_chunks_fts, rowid, summary, content)
|
|
903
|
-
VALUES ('delete', old.rowid, old.summary, old.content);
|
|
904
|
-
INSERT INTO hub_chunks_fts(rowid, summary, content)
|
|
905
|
-
VALUES (new.rowid, new.summary, new.content);
|
|
906
|
-
END;
|
|
907
|
-
|
|
908
|
-
CREATE TABLE IF NOT EXISTS hub_skills (
|
|
909
|
-
id TEXT PRIMARY KEY,
|
|
910
|
-
source_skill_id TEXT NOT NULL,
|
|
911
|
-
source_user_id TEXT NOT NULL,
|
|
912
|
-
name TEXT NOT NULL,
|
|
913
|
-
description TEXT NOT NULL DEFAULT '',
|
|
914
|
-
version INTEGER NOT NULL,
|
|
915
|
-
group_id TEXT,
|
|
916
|
-
visibility TEXT NOT NULL,
|
|
917
|
-
bundle TEXT NOT NULL,
|
|
918
|
-
quality_score REAL,
|
|
919
|
-
created_at INTEGER NOT NULL,
|
|
920
|
-
updated_at INTEGER NOT NULL,
|
|
921
|
-
UNIQUE(source_user_id, source_skill_id)
|
|
922
|
-
);
|
|
923
|
-
CREATE INDEX IF NOT EXISTS idx_hub_skills_visibility ON hub_skills(visibility);
|
|
924
|
-
CREATE INDEX IF NOT EXISTS idx_hub_skills_group ON hub_skills(group_id);
|
|
925
|
-
|
|
926
|
-
CREATE TABLE IF NOT EXISTS hub_skill_embeddings (
|
|
927
|
-
skill_id TEXT PRIMARY KEY REFERENCES hub_skills(id) ON DELETE CASCADE,
|
|
928
|
-
vector BLOB NOT NULL,
|
|
929
|
-
dimensions INTEGER NOT NULL,
|
|
930
|
-
updated_at INTEGER NOT NULL
|
|
931
|
-
);
|
|
932
|
-
|
|
933
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS hub_skills_fts USING fts5(
|
|
934
|
-
name,
|
|
935
|
-
description,
|
|
936
|
-
content='hub_skills',
|
|
937
|
-
content_rowid='rowid',
|
|
938
|
-
tokenize='trigram'
|
|
939
|
-
);
|
|
940
|
-
|
|
941
|
-
CREATE TRIGGER IF NOT EXISTS hub_skills_ai AFTER INSERT ON hub_skills BEGIN
|
|
942
|
-
INSERT INTO hub_skills_fts(rowid, name, description)
|
|
943
|
-
VALUES (new.rowid, new.name, new.description);
|
|
944
|
-
END;
|
|
945
|
-
|
|
946
|
-
CREATE TRIGGER IF NOT EXISTS hub_skills_ad AFTER DELETE ON hub_skills BEGIN
|
|
947
|
-
INSERT INTO hub_skills_fts(hub_skills_fts, rowid, name, description)
|
|
948
|
-
VALUES ('delete', old.rowid, old.name, old.description);
|
|
949
|
-
END;
|
|
950
|
-
|
|
951
|
-
CREATE TRIGGER IF NOT EXISTS hub_skills_au AFTER UPDATE ON hub_skills BEGIN
|
|
952
|
-
INSERT INTO hub_skills_fts(hub_skills_fts, rowid, name, description)
|
|
953
|
-
VALUES ('delete', old.rowid, old.name, old.description);
|
|
954
|
-
INSERT INTO hub_skills_fts(rowid, name, description)
|
|
955
|
-
VALUES (new.rowid, new.name, new.description);
|
|
956
|
-
END;
|
|
957
|
-
|
|
958
|
-
-- Independent shared memories (not tied to a task)
|
|
959
|
-
CREATE TABLE IF NOT EXISTS hub_memories (
|
|
960
|
-
id TEXT PRIMARY KEY,
|
|
961
|
-
source_chunk_id TEXT NOT NULL,
|
|
962
|
-
source_user_id TEXT NOT NULL,
|
|
963
|
-
role TEXT NOT NULL,
|
|
964
|
-
content TEXT NOT NULL,
|
|
965
|
-
summary TEXT NOT NULL DEFAULT '',
|
|
966
|
-
kind TEXT NOT NULL DEFAULT 'paragraph',
|
|
967
|
-
group_id TEXT,
|
|
968
|
-
visibility TEXT NOT NULL,
|
|
969
|
-
created_at INTEGER NOT NULL,
|
|
970
|
-
updated_at INTEGER NOT NULL,
|
|
971
|
-
UNIQUE(source_user_id, source_chunk_id)
|
|
972
|
-
);
|
|
973
|
-
CREATE INDEX IF NOT EXISTS idx_hub_memories_visibility ON hub_memories(visibility);
|
|
974
|
-
CREATE INDEX IF NOT EXISTS idx_hub_memories_group ON hub_memories(group_id);
|
|
975
|
-
|
|
976
|
-
CREATE TABLE IF NOT EXISTS hub_memory_embeddings (
|
|
977
|
-
memory_id TEXT PRIMARY KEY REFERENCES hub_memories(id) ON DELETE CASCADE,
|
|
978
|
-
vector BLOB NOT NULL,
|
|
979
|
-
dimensions INTEGER NOT NULL,
|
|
980
|
-
updated_at INTEGER NOT NULL
|
|
981
|
-
);
|
|
982
|
-
|
|
983
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS hub_memories_fts USING fts5(
|
|
984
|
-
summary,
|
|
985
|
-
content,
|
|
986
|
-
content='hub_memories',
|
|
987
|
-
content_rowid='rowid',
|
|
988
|
-
tokenize='trigram'
|
|
989
|
-
);
|
|
990
|
-
|
|
991
|
-
CREATE TRIGGER IF NOT EXISTS hub_memories_ai AFTER INSERT ON hub_memories BEGIN
|
|
992
|
-
INSERT INTO hub_memories_fts(rowid, summary, content)
|
|
993
|
-
VALUES (new.rowid, new.summary, new.content);
|
|
994
|
-
END;
|
|
995
|
-
|
|
996
|
-
CREATE TRIGGER IF NOT EXISTS hub_memories_ad AFTER DELETE ON hub_memories BEGIN
|
|
997
|
-
INSERT INTO hub_memories_fts(hub_memories_fts, rowid, summary, content)
|
|
998
|
-
VALUES ('delete', old.rowid, old.summary, old.content);
|
|
999
|
-
END;
|
|
1000
|
-
|
|
1001
|
-
CREATE TRIGGER IF NOT EXISTS hub_memories_au AFTER UPDATE ON hub_memories BEGIN
|
|
1002
|
-
INSERT INTO hub_memories_fts(hub_memories_fts, rowid, summary, content)
|
|
1003
|
-
VALUES ('delete', old.rowid, old.summary, old.content);
|
|
1004
|
-
INSERT INTO hub_memories_fts(rowid, summary, content)
|
|
1005
|
-
VALUES (new.rowid, new.summary, new.content);
|
|
1006
|
-
END;
|
|
1007
|
-
`);
|
|
1008
|
-
this.db.exec(`
|
|
1009
|
-
CREATE TABLE IF NOT EXISTS hub_notifications (
|
|
1010
|
-
id TEXT PRIMARY KEY,
|
|
1011
|
-
user_id TEXT NOT NULL,
|
|
1012
|
-
type TEXT NOT NULL,
|
|
1013
|
-
resource TEXT NOT NULL,
|
|
1014
|
-
title TEXT NOT NULL,
|
|
1015
|
-
message TEXT NOT NULL DEFAULT '',
|
|
1016
|
-
read INTEGER NOT NULL DEFAULT 0,
|
|
1017
|
-
created_at INTEGER NOT NULL
|
|
1018
|
-
);
|
|
1019
|
-
CREATE INDEX IF NOT EXISTS idx_hub_notif_user ON hub_notifications(user_id, read, created_at DESC);
|
|
1020
|
-
`);
|
|
1021
|
-
try {
|
|
1022
|
-
const cols = this.db.prepare("PRAGMA table_info(hub_users)").all();
|
|
1023
|
-
if (cols.length > 0 && !cols.some(c => c.name === "last_ip")) {
|
|
1024
|
-
this.db.exec("ALTER TABLE hub_users ADD COLUMN last_ip TEXT NOT NULL DEFAULT ''");
|
|
1025
|
-
this.log.info("Migrated: added last_ip column to hub_users");
|
|
1026
|
-
}
|
|
1027
|
-
if (cols.length > 0 && !cols.some(c => c.name === "last_active_at")) {
|
|
1028
|
-
this.db.exec("ALTER TABLE hub_users ADD COLUMN last_active_at INTEGER");
|
|
1029
|
-
this.log.info("Migrated: added last_active_at column to hub_users");
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
catch { /* table may not exist yet */ }
|
|
1033
|
-
}
|
|
1034
|
-
// ─── Write ───
|
|
1035
|
-
insertChunk(chunk) {
|
|
1036
|
-
const stmt = this.db.prepare(`
|
|
1037
|
-
INSERT OR REPLACE INTO chunks (id, session_key, turn_id, seq, role, content, kind, summary, task_id, content_hash, owner, dedup_status, dedup_target, dedup_reason, created_at, updated_at)
|
|
1038
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1039
|
-
`);
|
|
1040
|
-
stmt.run(chunk.id, chunk.sessionKey, chunk.turnId, chunk.seq, chunk.role, chunk.content, chunk.kind, chunk.summary, chunk.taskId, contentHash(chunk.content), chunk.owner ?? "agent:main", chunk.dedupStatus ?? "active", chunk.dedupTarget ?? null, chunk.dedupReason ?? null, chunk.createdAt, chunk.updatedAt);
|
|
1041
|
-
}
|
|
1042
|
-
markDedupStatus(chunkId, status, targetChunkId, reason) {
|
|
1043
|
-
this.db.prepare("UPDATE chunks SET dedup_status = ?, dedup_target = ?, dedup_reason = ?, updated_at = ? WHERE id = ?").run(status, targetChunkId, reason, Date.now(), chunkId);
|
|
1044
|
-
}
|
|
1045
|
-
updateSummary(chunkId, summary) {
|
|
1046
|
-
this.db.prepare("UPDATE chunks SET summary = ?, updated_at = ? WHERE id = ?").run(summary, Date.now(), chunkId);
|
|
1047
|
-
}
|
|
1048
|
-
upsertEmbedding(chunkId, vector) {
|
|
1049
|
-
const buf = Buffer.from(new Float32Array(vector).buffer);
|
|
1050
|
-
this.db.prepare(`
|
|
1051
|
-
INSERT OR REPLACE INTO embeddings (chunk_id, vector, dimensions, updated_at)
|
|
1052
|
-
VALUES (?, ?, ?, ?)
|
|
1053
|
-
`).run(chunkId, buf, vector.length, Date.now());
|
|
1054
|
-
}
|
|
1055
|
-
deleteEmbedding(chunkId) {
|
|
1056
|
-
this.db.prepare("DELETE FROM embeddings WHERE chunk_id = ?").run(chunkId);
|
|
1057
|
-
}
|
|
1058
|
-
// ─── Read ───
|
|
1059
|
-
getChunk(chunkId) {
|
|
1060
|
-
const row = this.db.prepare("SELECT * FROM chunks WHERE id = ?").get(chunkId);
|
|
1061
|
-
return row ? rowToChunk(row) : null;
|
|
1062
|
-
}
|
|
1063
|
-
getChunkForOwners(chunkId, ownerFilter) {
|
|
1064
|
-
if (!ownerFilter || ownerFilter.length === 0)
|
|
1065
|
-
return this.getChunk(chunkId);
|
|
1066
|
-
const placeholders = ownerFilter.map(() => "?").join(",");
|
|
1067
|
-
const row = this.db.prepare(`SELECT * FROM chunks WHERE id = ? AND owner IN (${placeholders}) LIMIT 1`).get(chunkId, ...ownerFilter);
|
|
1068
|
-
return row ? rowToChunk(row) : null;
|
|
1069
|
-
}
|
|
1070
|
-
getChunksByRef(ref, ownerFilter) {
|
|
1071
|
-
return this.getChunkForOwners(ref.chunkId, ownerFilter);
|
|
1072
|
-
}
|
|
1073
|
-
getNeighborChunks(sessionKey, turnId, seq, window, ownerFilter) {
|
|
1074
|
-
let sql = `
|
|
1075
|
-
SELECT * FROM chunks
|
|
1076
|
-
WHERE session_key = ?`;
|
|
1077
|
-
const params = [sessionKey];
|
|
1078
|
-
if (ownerFilter && ownerFilter.length > 0) {
|
|
1079
|
-
const placeholders = ownerFilter.map(() => "?").join(",");
|
|
1080
|
-
sql += ` AND owner IN (${placeholders})`;
|
|
1081
|
-
params.push(...ownerFilter);
|
|
1082
|
-
}
|
|
1083
|
-
sql += `
|
|
1084
|
-
ORDER BY created_at, seq
|
|
1085
|
-
`;
|
|
1086
|
-
const allRows = this.db.prepare(sql).all(...params);
|
|
1087
|
-
const targetIdx = allRows.findIndex((r) => r.turn_id === turnId && r.seq === seq);
|
|
1088
|
-
if (targetIdx === -1)
|
|
1089
|
-
return [];
|
|
1090
|
-
const radius = window * 3;
|
|
1091
|
-
const start = Math.max(0, targetIdx - radius);
|
|
1092
|
-
const end = Math.min(allRows.length, targetIdx + radius + 1);
|
|
1093
|
-
return allRows.slice(start, end).map(rowToChunk);
|
|
1094
|
-
}
|
|
1095
|
-
// ─── FTS Search ───
|
|
1096
|
-
ftsSearch(query, limit, ownerFilter) {
|
|
1097
|
-
const sanitized = sanitizeFtsQuery(query);
|
|
1098
|
-
if (!sanitized)
|
|
1099
|
-
return [];
|
|
1100
|
-
try {
|
|
1101
|
-
let sql = `
|
|
1102
|
-
SELECT c.id as chunk_id, rank
|
|
1103
|
-
FROM chunks_fts f
|
|
1104
|
-
JOIN chunks c ON c.rowid = f.rowid
|
|
1105
|
-
WHERE chunks_fts MATCH ? AND c.dedup_status = 'active'`;
|
|
1106
|
-
const params = [sanitized];
|
|
1107
|
-
if (ownerFilter && ownerFilter.length > 0) {
|
|
1108
|
-
const placeholders = ownerFilter.map(() => "?").join(",");
|
|
1109
|
-
sql += ` AND c.owner IN (${placeholders})`;
|
|
1110
|
-
params.push(...ownerFilter);
|
|
1111
|
-
}
|
|
1112
|
-
sql += ` ORDER BY rank LIMIT ?`;
|
|
1113
|
-
params.push(limit);
|
|
1114
|
-
const rows = this.db.prepare(sql).all(...params);
|
|
1115
|
-
if (rows.length === 0)
|
|
1116
|
-
return [];
|
|
1117
|
-
const maxAbsRank = Math.max(...rows.map((r) => Math.abs(r.rank)));
|
|
1118
|
-
return rows.map((r) => ({
|
|
1119
|
-
chunkId: r.chunk_id,
|
|
1120
|
-
score: maxAbsRank > 0 ? Math.abs(r.rank) / maxAbsRank : 0,
|
|
1121
|
-
}));
|
|
1122
|
-
}
|
|
1123
|
-
catch {
|
|
1124
|
-
this.log.warn(`FTS query failed for: "${sanitized}", returning empty`);
|
|
1125
|
-
return [];
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
// ─── Pattern Search (LIKE-based, for CJK text where FTS tokenization is weak) ───
|
|
1129
|
-
patternSearch(patterns, opts = {}) {
|
|
1130
|
-
if (patterns.length === 0)
|
|
1131
|
-
return [];
|
|
1132
|
-
const limit = opts.limit ?? 10;
|
|
1133
|
-
const conditions = patterns.map(() => "c.content LIKE ?");
|
|
1134
|
-
const whereClause = conditions.join(" OR ");
|
|
1135
|
-
const roleClause = opts.role ? " AND c.role = ?" : "";
|
|
1136
|
-
const params = patterns.map(p => `%${p}%`);
|
|
1137
|
-
if (opts.role)
|
|
1138
|
-
params.push(opts.role);
|
|
1139
|
-
params.push(limit);
|
|
1140
|
-
try {
|
|
1141
|
-
const rows = this.db.prepare(`
|
|
1142
|
-
SELECT c.id as chunk_id, c.content, c.role, c.created_at
|
|
1143
|
-
FROM chunks c
|
|
1144
|
-
WHERE (${whereClause})${roleClause} AND c.dedup_status = 'active'
|
|
1145
|
-
ORDER BY c.created_at DESC
|
|
1146
|
-
LIMIT ?
|
|
1147
|
-
`).all(...params);
|
|
1148
|
-
return rows.map(r => ({
|
|
1149
|
-
chunkId: r.chunk_id,
|
|
1150
|
-
content: r.content,
|
|
1151
|
-
role: r.role,
|
|
1152
|
-
createdAt: r.created_at,
|
|
1153
|
-
}));
|
|
1154
|
-
}
|
|
1155
|
-
catch {
|
|
1156
|
-
return [];
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
hubMemoryPatternSearch(patterns, opts = {}) {
|
|
1160
|
-
if (patterns.length === 0)
|
|
1161
|
-
return [];
|
|
1162
|
-
const limit = opts.limit ?? 10;
|
|
1163
|
-
const conditions = patterns.map(() => "(hm.content LIKE ? OR hm.summary LIKE ?)");
|
|
1164
|
-
const params = [];
|
|
1165
|
-
for (const p of patterns) {
|
|
1166
|
-
params.push(`%${p}%`, `%${p}%`);
|
|
1167
|
-
}
|
|
1168
|
-
params.push(limit);
|
|
1169
|
-
try {
|
|
1170
|
-
const rows = this.db.prepare(`
|
|
1171
|
-
SELECT hm.id as memory_id, hm.content, hm.role, hm.created_at
|
|
1172
|
-
FROM hub_memories hm
|
|
1173
|
-
WHERE ${conditions.join(" OR ")}
|
|
1174
|
-
ORDER BY hm.created_at DESC
|
|
1175
|
-
LIMIT ?
|
|
1176
|
-
`).all(...params);
|
|
1177
|
-
return rows.map(r => ({ memoryId: r.memory_id, content: r.content, role: r.role, createdAt: r.created_at }));
|
|
1178
|
-
}
|
|
1179
|
-
catch {
|
|
1180
|
-
return [];
|
|
1181
|
-
}
|
|
1182
|
-
}
|
|
1183
|
-
listHubMemories(opts = {}) {
|
|
1184
|
-
const limit = opts.limit ?? 200;
|
|
1185
|
-
try {
|
|
1186
|
-
return this.db.prepare("SELECT id, summary, content FROM hub_memories ORDER BY created_at DESC LIMIT ?").all(limit);
|
|
1187
|
-
}
|
|
1188
|
-
catch {
|
|
1189
|
-
return [];
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
// ─── Vector Search ───
|
|
1193
|
-
getAllEmbeddings(ownerFilter) {
|
|
1194
|
-
let sql = `SELECT e.chunk_id, e.vector, e.dimensions FROM embeddings e
|
|
1195
|
-
JOIN chunks c ON c.id = e.chunk_id
|
|
1196
|
-
WHERE c.dedup_status = 'active'`;
|
|
1197
|
-
const params = [];
|
|
1198
|
-
if (ownerFilter && ownerFilter.length > 0) {
|
|
1199
|
-
const placeholders = ownerFilter.map(() => "?").join(",");
|
|
1200
|
-
sql += ` AND c.owner IN (${placeholders})`;
|
|
1201
|
-
params.push(...ownerFilter);
|
|
1202
|
-
}
|
|
1203
|
-
const rows = this.db.prepare(sql).all(...params);
|
|
1204
|
-
return rows.map((r) => ({
|
|
1205
|
-
chunkId: r.chunk_id,
|
|
1206
|
-
vector: Array.from(new Float32Array(r.vector.buffer, r.vector.byteOffset, r.dimensions)),
|
|
1207
|
-
}));
|
|
1208
|
-
}
|
|
1209
|
-
getRecentEmbeddings(limit, ownerFilter) {
|
|
1210
|
-
if (limit <= 0)
|
|
1211
|
-
return this.getAllEmbeddings(ownerFilter);
|
|
1212
|
-
let sql = `SELECT e.chunk_id, e.vector, e.dimensions
|
|
1213
|
-
FROM chunks c
|
|
1214
|
-
JOIN embeddings e ON e.chunk_id = c.id
|
|
1215
|
-
WHERE c.dedup_status = 'active'`;
|
|
1216
|
-
const params = [];
|
|
1217
|
-
if (ownerFilter && ownerFilter.length > 0) {
|
|
1218
|
-
const placeholders = ownerFilter.map(() => "?").join(",");
|
|
1219
|
-
sql += ` AND c.owner IN (${placeholders})`;
|
|
1220
|
-
params.push(...ownerFilter);
|
|
1221
|
-
}
|
|
1222
|
-
sql += ` ORDER BY c.created_at DESC LIMIT ?`;
|
|
1223
|
-
params.push(limit);
|
|
1224
|
-
const rows = this.db.prepare(sql).all(...params);
|
|
1225
|
-
return rows.map((r) => ({
|
|
1226
|
-
chunkId: r.chunk_id,
|
|
1227
|
-
vector: Array.from(new Float32Array(r.vector.buffer, r.vector.byteOffset, r.dimensions)),
|
|
1228
|
-
}));
|
|
1229
|
-
}
|
|
1230
|
-
getEmbedding(chunkId) {
|
|
1231
|
-
const row = this.db.prepare("SELECT vector, dimensions FROM embeddings WHERE chunk_id = ?").get(chunkId);
|
|
1232
|
-
if (!row)
|
|
1233
|
-
return null;
|
|
1234
|
-
return Array.from(new Float32Array(row.vector.buffer, row.vector.byteOffset, row.dimensions));
|
|
1235
|
-
}
|
|
1236
|
-
// ─── Update ───
|
|
1237
|
-
updateChunk(chunkId, fields) {
|
|
1238
|
-
const sets = [];
|
|
1239
|
-
const params = [];
|
|
1240
|
-
if (fields.summary !== undefined) {
|
|
1241
|
-
sets.push("summary = ?");
|
|
1242
|
-
params.push(fields.summary);
|
|
1243
|
-
}
|
|
1244
|
-
if (fields.content !== undefined) {
|
|
1245
|
-
sets.push("content = ?");
|
|
1246
|
-
params.push(fields.content);
|
|
1247
|
-
}
|
|
1248
|
-
if (fields.role !== undefined) {
|
|
1249
|
-
sets.push("role = ?");
|
|
1250
|
-
params.push(fields.role);
|
|
1251
|
-
}
|
|
1252
|
-
if (fields.kind !== undefined) {
|
|
1253
|
-
sets.push("kind = ?");
|
|
1254
|
-
params.push(fields.kind);
|
|
1255
|
-
}
|
|
1256
|
-
if (fields.owner !== undefined) {
|
|
1257
|
-
sets.push("owner = ?");
|
|
1258
|
-
params.push(fields.owner);
|
|
1259
|
-
}
|
|
1260
|
-
if (sets.length === 0)
|
|
1261
|
-
return false;
|
|
1262
|
-
sets.push("updated_at = ?");
|
|
1263
|
-
params.push(Date.now());
|
|
1264
|
-
params.push(chunkId);
|
|
1265
|
-
const result = this.db.prepare(`UPDATE chunks SET ${sets.join(", ")} WHERE id = ?`).run(...params);
|
|
1266
|
-
return result.changes > 0;
|
|
1267
|
-
}
|
|
1268
|
-
/**
|
|
1269
|
-
* Find user-role chunks that contain system-injected content that should
|
|
1270
|
-
* have been stripped before storage. Returns chunk IDs and a preview.
|
|
1271
|
-
*/
|
|
1272
|
-
findPollutedUserChunks() {
|
|
1273
|
-
const results = [];
|
|
1274
|
-
const patterns = [
|
|
1275
|
-
{ sql: "content LIKE '%<memory_context>%'", reason: "memory_context injection" },
|
|
1276
|
-
{ sql: "content LIKE '%=== MemOS LONG-TERM MEMORY%'", reason: "MemOS legacy injection" },
|
|
1277
|
-
{ sql: "content LIKE '%[MemOS Auto-Recall]%'", reason: "MemOS Auto-Recall injection" },
|
|
1278
|
-
{ sql: "content LIKE '%## Memory system%No memories were automatically recalled%'", reason: "Memory system no-recall hint" },
|
|
1279
|
-
{ sql: "content LIKE '%## Retrieved memories from past conversations%CRITICAL INSTRUCTION%'", reason: "prependContext recall injection" },
|
|
1280
|
-
{ sql: "content LIKE '%VERIFIED facts the user previously shared%'", reason: "VERIFIED facts injection" },
|
|
1281
|
-
{ sql: "content LIKE '%<memos_system_instruction>%'", reason: "memos_system_instruction injection" },
|
|
1282
|
-
{ sql: "content LIKE '%📝 Related memories:%'", reason: "Related memories injection" },
|
|
1283
|
-
];
|
|
1284
|
-
for (const { sql, reason } of patterns) {
|
|
1285
|
-
const rows = this.db.prepare(`SELECT id, substr(content, 1, 120) AS preview FROM chunks WHERE role = 'user' AND ${sql}`).all();
|
|
1286
|
-
for (const row of rows) {
|
|
1287
|
-
results.push({ id: row.id, preview: row.preview, reason });
|
|
1288
|
-
}
|
|
1289
|
-
}
|
|
1290
|
-
return results;
|
|
1291
|
-
}
|
|
1292
|
-
/**
|
|
1293
|
-
* Find user chunks where user+assistant content was mixed together
|
|
1294
|
-
* (separated by \n\n---\n), and truncate to keep only the user's part.
|
|
1295
|
-
*/
|
|
1296
|
-
fixMixedUserChunks() {
|
|
1297
|
-
const rows = this.db.prepare(`SELECT id, content FROM chunks WHERE role = 'user'
|
|
1298
|
-
AND content LIKE '%' || char(10) || char(10) || '---' || char(10) || '%'
|
|
1299
|
-
AND length(content) > 300`).all();
|
|
1300
|
-
let fixed = 0;
|
|
1301
|
-
for (const { id, content } of rows) {
|
|
1302
|
-
const dashIdx = content.indexOf("\n\n---\n");
|
|
1303
|
-
if (dashIdx > 5) {
|
|
1304
|
-
const userPart = content.slice(0, dashIdx).trim();
|
|
1305
|
-
if (userPart.length >= 5 && userPart.length < content.length) {
|
|
1306
|
-
this.db.prepare("UPDATE chunks SET content = ?, updated_at = ? WHERE id = ?")
|
|
1307
|
-
.run(userPart, Date.now(), id);
|
|
1308
|
-
fixed++;
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
return fixed;
|
|
1313
|
-
}
|
|
1314
|
-
// ─── Delete ───
|
|
1315
|
-
deleteChunk(chunkId) {
|
|
1316
|
-
const result = this.db.prepare("DELETE FROM chunks WHERE id = ?").run(chunkId);
|
|
1317
|
-
return result.changes > 0;
|
|
1318
|
-
}
|
|
1319
|
-
deleteSession(sessionKey) {
|
|
1320
|
-
const result = this.db.prepare("DELETE FROM chunks WHERE session_key = ?").run(sessionKey);
|
|
1321
|
-
return result.changes;
|
|
1322
|
-
}
|
|
1323
|
-
deleteAll() {
|
|
1324
|
-
this.db.exec("PRAGMA foreign_keys = OFF");
|
|
1325
|
-
const tables = [
|
|
1326
|
-
"task_skills",
|
|
1327
|
-
"skill_embeddings",
|
|
1328
|
-
"skill_versions",
|
|
1329
|
-
"skills",
|
|
1330
|
-
"local_shared_memories",
|
|
1331
|
-
"team_shared_chunks",
|
|
1332
|
-
"team_shared_skills",
|
|
1333
|
-
"local_shared_tasks",
|
|
1334
|
-
"embeddings",
|
|
1335
|
-
"chunks",
|
|
1336
|
-
"tasks",
|
|
1337
|
-
"viewer_events",
|
|
1338
|
-
"api_logs",
|
|
1339
|
-
"tool_calls",
|
|
1340
|
-
];
|
|
1341
|
-
for (const table of tables) {
|
|
1342
|
-
try {
|
|
1343
|
-
this.db.prepare(`DELETE FROM ${table}`).run();
|
|
1344
|
-
}
|
|
1345
|
-
catch (err) {
|
|
1346
|
-
this.log.warn(`deleteAll: failed to clear ${table}: ${err}`);
|
|
1347
|
-
}
|
|
1348
|
-
}
|
|
1349
|
-
this.db.exec("PRAGMA foreign_keys = ON");
|
|
1350
|
-
const remaining = this.countChunks();
|
|
1351
|
-
return remaining === 0 ? 1 : 0;
|
|
1352
|
-
}
|
|
1353
|
-
deleteTask(taskId) {
|
|
1354
|
-
this.db.prepare("DELETE FROM task_skills WHERE task_id = ?").run(taskId);
|
|
1355
|
-
this.db.prepare("UPDATE chunks SET task_id = NULL WHERE task_id = ?").run(taskId);
|
|
1356
|
-
const result = this.db.prepare("DELETE FROM tasks WHERE id = ?").run(taskId);
|
|
1357
|
-
return result.changes > 0;
|
|
1358
|
-
}
|
|
1359
|
-
deleteSkill(skillId) {
|
|
1360
|
-
this.db.prepare("DELETE FROM task_skills WHERE skill_id = ?").run(skillId);
|
|
1361
|
-
this.db.prepare("DELETE FROM skill_versions WHERE skill_id = ?").run(skillId);
|
|
1362
|
-
this.db.prepare("DELETE FROM skill_embeddings WHERE skill_id = ?").run(skillId);
|
|
1363
|
-
this.db.prepare("UPDATE chunks SET skill_id = NULL WHERE skill_id = ?").run(skillId);
|
|
1364
|
-
const result = this.db.prepare("DELETE FROM skills WHERE id = ?").run(skillId);
|
|
1365
|
-
return result.changes > 0;
|
|
1366
|
-
}
|
|
1367
|
-
// ─── Task CRUD ───
|
|
1368
|
-
insertTask(task) {
|
|
1369
|
-
this.db.prepare(`
|
|
1370
|
-
INSERT OR REPLACE INTO tasks (id, session_key, title, summary, status, owner, started_at, ended_at, updated_at)
|
|
1371
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1372
|
-
`).run(task.id, task.sessionKey, task.title, task.summary, task.status, task.owner ?? "agent:main", task.startedAt, task.endedAt, task.updatedAt);
|
|
1373
|
-
}
|
|
1374
|
-
getTask(taskId) {
|
|
1375
|
-
const row = this.db.prepare("SELECT * FROM tasks WHERE id = ?").get(taskId);
|
|
1376
|
-
return row ? rowToTask(row) : null;
|
|
1377
|
-
}
|
|
1378
|
-
getActiveTask(sessionKey, owner) {
|
|
1379
|
-
if (owner) {
|
|
1380
|
-
const row = this.db.prepare("SELECT * FROM tasks WHERE session_key = ? AND status = 'active' AND owner = ? ORDER BY started_at DESC LIMIT 1").get(sessionKey, owner);
|
|
1381
|
-
return row ? rowToTask(row) : null;
|
|
1382
|
-
}
|
|
1383
|
-
const row = this.db.prepare("SELECT * FROM tasks WHERE session_key = ? AND status = 'active' ORDER BY started_at DESC LIMIT 1").get(sessionKey);
|
|
1384
|
-
return row ? rowToTask(row) : null;
|
|
1385
|
-
}
|
|
1386
|
-
hasTaskForSession(sessionKey) {
|
|
1387
|
-
const row = this.db.prepare("SELECT 1 FROM tasks WHERE session_key = ? LIMIT 1").get(sessionKey);
|
|
1388
|
-
return !!row;
|
|
1389
|
-
}
|
|
1390
|
-
hasSkillForSessionTask(sessionKey) {
|
|
1391
|
-
const row = this.db.prepare("SELECT 1 FROM task_skills ts JOIN tasks t ON ts.task_id = t.id WHERE t.session_key = ? LIMIT 1").get(sessionKey);
|
|
1392
|
-
return !!row;
|
|
1393
|
-
}
|
|
1394
|
-
getCompletedTasksForSession(sessionKey) {
|
|
1395
|
-
const rows = this.db.prepare("SELECT * FROM tasks WHERE session_key = ? AND status = 'completed'").all(sessionKey);
|
|
1396
|
-
return rows.map(rowToTask);
|
|
1397
|
-
}
|
|
1398
|
-
getAllActiveTasks(owner) {
|
|
1399
|
-
if (owner) {
|
|
1400
|
-
const rows = this.db.prepare("SELECT * FROM tasks WHERE status = 'active' AND owner = ? ORDER BY started_at DESC").all(owner);
|
|
1401
|
-
return rows.map(rowToTask);
|
|
1402
|
-
}
|
|
1403
|
-
const rows = this.db.prepare("SELECT * FROM tasks WHERE status = 'active' ORDER BY started_at DESC").all();
|
|
1404
|
-
return rows.map(rowToTask);
|
|
1405
|
-
}
|
|
1406
|
-
updateTask(taskId, fields) {
|
|
1407
|
-
const sets = [];
|
|
1408
|
-
const params = [];
|
|
1409
|
-
if (fields.title !== undefined) {
|
|
1410
|
-
sets.push("title = ?");
|
|
1411
|
-
params.push(fields.title);
|
|
1412
|
-
}
|
|
1413
|
-
if (fields.summary !== undefined) {
|
|
1414
|
-
sets.push("summary = ?");
|
|
1415
|
-
params.push(fields.summary);
|
|
1416
|
-
}
|
|
1417
|
-
if (fields.status !== undefined) {
|
|
1418
|
-
sets.push("status = ?");
|
|
1419
|
-
params.push(fields.status);
|
|
1420
|
-
}
|
|
1421
|
-
if (fields.endedAt !== undefined) {
|
|
1422
|
-
sets.push("ended_at = ?");
|
|
1423
|
-
params.push(fields.endedAt);
|
|
1424
|
-
}
|
|
1425
|
-
if (sets.length === 0)
|
|
1426
|
-
return false;
|
|
1427
|
-
sets.push("updated_at = ?");
|
|
1428
|
-
params.push(Date.now());
|
|
1429
|
-
params.push(taskId);
|
|
1430
|
-
const result = this.db.prepare(`UPDATE tasks SET ${sets.join(", ")} WHERE id = ?`).run(...params);
|
|
1431
|
-
return result.changes > 0;
|
|
1432
|
-
}
|
|
1433
|
-
getChunksByTask(taskId) {
|
|
1434
|
-
const rows = this.db.prepare("SELECT * FROM chunks WHERE task_id = ? ORDER BY created_at, seq").all(taskId);
|
|
1435
|
-
return rows.map(rowToChunk);
|
|
1436
|
-
}
|
|
1437
|
-
listTasks(opts = {}) {
|
|
1438
|
-
const conditions = [];
|
|
1439
|
-
const params = [];
|
|
1440
|
-
if (opts.status) {
|
|
1441
|
-
conditions.push("status = ?");
|
|
1442
|
-
params.push(opts.status);
|
|
1443
|
-
}
|
|
1444
|
-
if (opts.owner) {
|
|
1445
|
-
conditions.push("(owner = ? OR (owner = 'public' AND id IN (SELECT task_id FROM local_shared_tasks WHERE original_owner = ?)))");
|
|
1446
|
-
params.push(opts.owner, opts.owner);
|
|
1447
|
-
}
|
|
1448
|
-
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1449
|
-
const countRow = this.db.prepare(`SELECT COUNT(*) as c FROM tasks ${whereClause}`).get(...params);
|
|
1450
|
-
const total = countRow.c;
|
|
1451
|
-
const limit = opts.limit ?? 50;
|
|
1452
|
-
const offset = opts.offset ?? 0;
|
|
1453
|
-
const rows = this.db.prepare(`SELECT * FROM tasks ${whereClause} ORDER BY started_at DESC LIMIT ? OFFSET ?`).all(...params, limit, offset);
|
|
1454
|
-
return { tasks: rows.map(rowToTask), total };
|
|
1455
|
-
}
|
|
1456
|
-
countChunksByTask(taskId) {
|
|
1457
|
-
const row = this.db.prepare("SELECT COUNT(*) as c FROM chunks WHERE task_id = ?").get(taskId);
|
|
1458
|
-
return row.c;
|
|
1459
|
-
}
|
|
1460
|
-
setChunkTaskId(chunkId, taskId) {
|
|
1461
|
-
this.db.prepare("UPDATE chunks SET task_id = ?, updated_at = ? WHERE id = ?").run(taskId, Date.now(), chunkId);
|
|
1462
|
-
}
|
|
1463
|
-
getUnassignedChunks(sessionKey, owner) {
|
|
1464
|
-
if (owner) {
|
|
1465
|
-
const rows = this.db.prepare("SELECT * FROM chunks WHERE session_key = ? AND task_id IS NULL AND owner = ? ORDER BY created_at, seq").all(sessionKey, owner);
|
|
1466
|
-
return rows.map(rowToChunk);
|
|
1467
|
-
}
|
|
1468
|
-
const rows = this.db.prepare("SELECT * FROM chunks WHERE session_key = ? AND task_id IS NULL ORDER BY created_at, seq").all(sessionKey);
|
|
1469
|
-
return rows.map(rowToChunk);
|
|
1470
|
-
}
|
|
1471
|
-
/**
|
|
1472
|
-
* Check if a chunk with the same (session_key, role, content_hash) already exists.
|
|
1473
|
-
* Uses indexed content_hash for O(1) lookup to prevent duplicate ingestion
|
|
1474
|
-
* when agent_end sends the full conversation history every turn.
|
|
1475
|
-
*/
|
|
1476
|
-
chunkExistsByContent(sessionKey, role, content) {
|
|
1477
|
-
const hash = contentHash(content);
|
|
1478
|
-
const row = this.db.prepare("SELECT 1 FROM chunks WHERE session_key = ? AND role = ? AND content_hash = ? LIMIT 1").get(sessionKey, role, hash);
|
|
1479
|
-
return !!row;
|
|
1480
|
-
}
|
|
1481
|
-
/**
|
|
1482
|
-
* Find an active chunk with the same content_hash within the same owner (agent dimension).
|
|
1483
|
-
* Returns the existing chunk ID if found, null otherwise.
|
|
1484
|
-
*/
|
|
1485
|
-
findActiveChunkByHash(content, owner) {
|
|
1486
|
-
const hash = contentHash(content);
|
|
1487
|
-
// Check ANY existing chunk with the same hash (regardless of dedup_status)
|
|
1488
|
-
// to prevent re-creating duplicates when all prior copies have been marked duplicate/merged.
|
|
1489
|
-
if (owner) {
|
|
1490
|
-
const row = this.db.prepare("SELECT id FROM chunks WHERE content_hash = ? AND owner = ? ORDER BY CASE dedup_status WHEN 'active' THEN 0 ELSE 1 END LIMIT 1").get(hash, owner);
|
|
1491
|
-
return row?.id ?? null;
|
|
1492
|
-
}
|
|
1493
|
-
const row = this.db.prepare("SELECT id FROM chunks WHERE content_hash = ? ORDER BY CASE dedup_status WHEN 'active' THEN 0 ELSE 1 END LIMIT 1").get(hash);
|
|
1494
|
-
return row?.id ?? null;
|
|
1495
|
-
}
|
|
1496
|
-
// ─── Util ───
|
|
1497
|
-
getRecentChunkIds(limit) {
|
|
1498
|
-
const rows = this.db.prepare("SELECT id FROM chunks ORDER BY created_at DESC LIMIT ?").all(limit);
|
|
1499
|
-
return rows.map((r) => r.id);
|
|
1500
|
-
}
|
|
1501
|
-
countChunks() {
|
|
1502
|
-
const row = this.db.prepare("SELECT COUNT(*) AS cnt FROM chunks").get();
|
|
1503
|
-
return row.cnt;
|
|
1504
|
-
}
|
|
1505
|
-
// ─── Skill CRUD ───
|
|
1506
|
-
insertSkill(skill) {
|
|
1507
|
-
this.db.prepare(`
|
|
1508
|
-
INSERT OR REPLACE INTO skills (id, name, description, version, status, tags, source_type, dir_path, installed, owner, visibility, quality_score, created_at, updated_at)
|
|
1509
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1510
|
-
`).run(skill.id, skill.name, skill.description, skill.version, skill.status, skill.tags, skill.sourceType, skill.dirPath, skill.installed, skill.owner ?? "agent:main", skill.visibility ?? "private", skill.qualityScore, skill.createdAt, skill.updatedAt);
|
|
1511
|
-
}
|
|
1512
|
-
getSkill(skillId) {
|
|
1513
|
-
const row = this.db.prepare("SELECT * FROM skills WHERE id = ?").get(skillId);
|
|
1514
|
-
return row ? rowToSkill(row) : null;
|
|
1515
|
-
}
|
|
1516
|
-
getSkillByName(name) {
|
|
1517
|
-
const row = this.db.prepare("SELECT * FROM skills WHERE name = ?").get(name);
|
|
1518
|
-
return row ? rowToSkill(row) : null;
|
|
1519
|
-
}
|
|
1520
|
-
updateSkill(skillId, fields) {
|
|
1521
|
-
const sets = [];
|
|
1522
|
-
const params = [];
|
|
1523
|
-
if (fields.description !== undefined) {
|
|
1524
|
-
sets.push("description = ?");
|
|
1525
|
-
params.push(fields.description);
|
|
1526
|
-
}
|
|
1527
|
-
if (fields.version !== undefined) {
|
|
1528
|
-
sets.push("version = ?");
|
|
1529
|
-
params.push(fields.version);
|
|
1530
|
-
}
|
|
1531
|
-
if (fields.status !== undefined) {
|
|
1532
|
-
sets.push("status = ?");
|
|
1533
|
-
params.push(fields.status);
|
|
1534
|
-
}
|
|
1535
|
-
if (fields.installed !== undefined) {
|
|
1536
|
-
sets.push("installed = ?");
|
|
1537
|
-
params.push(fields.installed);
|
|
1538
|
-
}
|
|
1539
|
-
if (fields.qualityScore !== undefined) {
|
|
1540
|
-
sets.push("quality_score = ?");
|
|
1541
|
-
params.push(fields.qualityScore);
|
|
1542
|
-
}
|
|
1543
|
-
if (sets.length === 0)
|
|
1544
|
-
return;
|
|
1545
|
-
sets.push("updated_at = ?");
|
|
1546
|
-
params.push(fields.updatedAt ?? Date.now());
|
|
1547
|
-
params.push(skillId);
|
|
1548
|
-
this.db.prepare(`UPDATE skills SET ${sets.join(", ")} WHERE id = ?`).run(...params);
|
|
1549
|
-
}
|
|
1550
|
-
listSkills(opts = {}) {
|
|
1551
|
-
const cond = opts.status ? "WHERE status = ?" : "";
|
|
1552
|
-
const params = opts.status ? [opts.status] : [];
|
|
1553
|
-
const rows = this.db.prepare(`SELECT * FROM skills ${cond} ORDER BY updated_at DESC`).all(...params);
|
|
1554
|
-
return rows.map(rowToSkill);
|
|
1555
|
-
}
|
|
1556
|
-
// ─── Skill Visibility & Embeddings ───
|
|
1557
|
-
setSkillVisibility(skillId, visibility) {
|
|
1558
|
-
this.db.prepare("UPDATE skills SET visibility = ?, updated_at = ? WHERE id = ?")
|
|
1559
|
-
.run(visibility, Date.now(), skillId);
|
|
1560
|
-
}
|
|
1561
|
-
upsertSkillEmbedding(skillId, vector) {
|
|
1562
|
-
const buf = Buffer.from(new Float32Array(vector).buffer);
|
|
1563
|
-
this.db.prepare(`
|
|
1564
|
-
INSERT OR REPLACE INTO skill_embeddings (skill_id, vector, dimensions, updated_at)
|
|
1565
|
-
VALUES (?, ?, ?, ?)
|
|
1566
|
-
`).run(skillId, buf, vector.length, Date.now());
|
|
1567
|
-
}
|
|
1568
|
-
getSkillEmbedding(skillId) {
|
|
1569
|
-
const row = this.db.prepare("SELECT vector, dimensions FROM skill_embeddings WHERE skill_id = ?").get(skillId);
|
|
1570
|
-
if (!row)
|
|
1571
|
-
return null;
|
|
1572
|
-
return Array.from(new Float32Array(row.vector.buffer, row.vector.byteOffset, row.dimensions));
|
|
1573
|
-
}
|
|
1574
|
-
getSkillEmbeddings(scope, currentOwner) {
|
|
1575
|
-
let sql = `SELECT se.skill_id, se.vector, se.dimensions
|
|
1576
|
-
FROM skill_embeddings se
|
|
1577
|
-
JOIN skills s ON s.id = se.skill_id
|
|
1578
|
-
WHERE s.status = 'active'`;
|
|
1579
|
-
const params = [];
|
|
1580
|
-
if (scope === "self") {
|
|
1581
|
-
sql += ` AND s.owner = ?`;
|
|
1582
|
-
params.push(currentOwner);
|
|
1583
|
-
}
|
|
1584
|
-
else if (scope === "public") {
|
|
1585
|
-
sql += ` AND s.visibility = 'public'`;
|
|
1586
|
-
}
|
|
1587
|
-
else {
|
|
1588
|
-
sql += ` AND (s.owner = ? OR s.visibility = 'public')`;
|
|
1589
|
-
params.push(currentOwner);
|
|
1590
|
-
}
|
|
1591
|
-
const rows = this.db.prepare(sql).all(...params);
|
|
1592
|
-
return rows.map((r) => ({
|
|
1593
|
-
skillId: r.skill_id,
|
|
1594
|
-
vector: Array.from(new Float32Array(r.vector.buffer, r.vector.byteOffset, r.dimensions)),
|
|
1595
|
-
}));
|
|
1596
|
-
}
|
|
1597
|
-
skillFtsSearch(query, limit, scope, currentOwner) {
|
|
1598
|
-
const sanitized = sanitizeFtsQuery(query);
|
|
1599
|
-
if (!sanitized)
|
|
1600
|
-
return [];
|
|
1601
|
-
try {
|
|
1602
|
-
let sql = `
|
|
1603
|
-
SELECT s.id as skill_id, rank
|
|
1604
|
-
FROM skills_fts f
|
|
1605
|
-
JOIN skills s ON s.rowid = f.rowid
|
|
1606
|
-
WHERE skills_fts MATCH ? AND s.status = 'active'`;
|
|
1607
|
-
const params = [sanitized];
|
|
1608
|
-
if (scope === "self") {
|
|
1609
|
-
sql += ` AND s.owner = ?`;
|
|
1610
|
-
params.push(currentOwner);
|
|
1611
|
-
}
|
|
1612
|
-
else if (scope === "public") {
|
|
1613
|
-
sql += ` AND s.visibility = 'public'`;
|
|
1614
|
-
}
|
|
1615
|
-
else {
|
|
1616
|
-
sql += ` AND (s.owner = ? OR s.visibility = 'public')`;
|
|
1617
|
-
params.push(currentOwner);
|
|
1618
|
-
}
|
|
1619
|
-
sql += ` ORDER BY rank LIMIT ?`;
|
|
1620
|
-
params.push(limit);
|
|
1621
|
-
const rows = this.db.prepare(sql).all(...params);
|
|
1622
|
-
if (rows.length === 0)
|
|
1623
|
-
return [];
|
|
1624
|
-
const maxAbsRank = Math.max(...rows.map((r) => Math.abs(r.rank)));
|
|
1625
|
-
return rows.map((r) => ({
|
|
1626
|
-
skillId: r.skill_id,
|
|
1627
|
-
score: maxAbsRank > 0 ? Math.abs(r.rank) / maxAbsRank : 0,
|
|
1628
|
-
}));
|
|
1629
|
-
}
|
|
1630
|
-
catch {
|
|
1631
|
-
this.log.warn(`Skill FTS query failed for: "${sanitized}", returning empty`);
|
|
1632
|
-
return [];
|
|
1633
|
-
}
|
|
1634
|
-
}
|
|
1635
|
-
listPublicSkills() {
|
|
1636
|
-
const rows = this.db.prepare("SELECT * FROM skills WHERE visibility = 'public' AND status = 'active' ORDER BY updated_at DESC").all();
|
|
1637
|
-
return rows.map(rowToSkill);
|
|
1638
|
-
}
|
|
1639
|
-
// ─── Skill Versions ───
|
|
1640
|
-
insertSkillVersion(sv) {
|
|
1641
|
-
this.db.prepare(`
|
|
1642
|
-
INSERT OR REPLACE INTO skill_versions (id, skill_id, version, content, changelog, change_summary, upgrade_type, source_task_id, metrics, quality_score, created_at)
|
|
1643
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1644
|
-
`).run(sv.id, sv.skillId, sv.version, sv.content, sv.changelog, sv.changeSummary, sv.upgradeType, sv.sourceTaskId, sv.metrics, sv.qualityScore, sv.createdAt);
|
|
1645
|
-
}
|
|
1646
|
-
getLatestSkillVersion(skillId) {
|
|
1647
|
-
const row = this.db.prepare("SELECT * FROM skill_versions WHERE skill_id = ? ORDER BY version DESC LIMIT 1").get(skillId);
|
|
1648
|
-
return row ? rowToSkillVersion(row) : null;
|
|
1649
|
-
}
|
|
1650
|
-
getSkillVersions(skillId) {
|
|
1651
|
-
const rows = this.db.prepare("SELECT * FROM skill_versions WHERE skill_id = ? ORDER BY version DESC").all(skillId);
|
|
1652
|
-
return rows.map(rowToSkillVersion);
|
|
1653
|
-
}
|
|
1654
|
-
getSkillVersion(skillId, version) {
|
|
1655
|
-
const row = this.db.prepare("SELECT * FROM skill_versions WHERE skill_id = ? AND version = ?").get(skillId, version);
|
|
1656
|
-
return row ? rowToSkillVersion(row) : null;
|
|
1657
|
-
}
|
|
1658
|
-
// ─── Task-Skill Links ───
|
|
1659
|
-
linkTaskSkill(taskId, skillId, relation, versionAt) {
|
|
1660
|
-
const skillExists = this.db.prepare("SELECT 1 FROM skills WHERE id = ?").get(skillId);
|
|
1661
|
-
if (!skillExists)
|
|
1662
|
-
return;
|
|
1663
|
-
const taskExists = this.db.prepare("SELECT 1 FROM tasks WHERE id = ?").get(taskId);
|
|
1664
|
-
if (!taskExists)
|
|
1665
|
-
return;
|
|
1666
|
-
this.db.prepare(`
|
|
1667
|
-
INSERT OR REPLACE INTO task_skills (task_id, skill_id, relation, version_at, created_at)
|
|
1668
|
-
VALUES (?, ?, ?, ?, ?)
|
|
1669
|
-
`).run(taskId, skillId, relation, versionAt, Date.now());
|
|
1670
|
-
}
|
|
1671
|
-
getSkillsByTask(taskId) {
|
|
1672
|
-
const rows = this.db.prepare(`
|
|
1673
|
-
SELECT s.*, ts.relation, ts.version_at
|
|
1674
|
-
FROM task_skills ts JOIN skills s ON s.id = ts.skill_id
|
|
1675
|
-
WHERE ts.task_id = ?
|
|
1676
|
-
`).all(taskId);
|
|
1677
|
-
return rows.map(r => ({
|
|
1678
|
-
skill: rowToSkill(r),
|
|
1679
|
-
relation: r.relation,
|
|
1680
|
-
versionAt: r.version_at,
|
|
1681
|
-
}));
|
|
1682
|
-
}
|
|
1683
|
-
getTasksBySkill(skillId) {
|
|
1684
|
-
const rows = this.db.prepare(`
|
|
1685
|
-
SELECT t.*, ts.relation
|
|
1686
|
-
FROM task_skills ts JOIN tasks t ON t.id = ts.task_id
|
|
1687
|
-
WHERE ts.skill_id = ?
|
|
1688
|
-
ORDER BY t.started_at DESC
|
|
1689
|
-
`).all(skillId);
|
|
1690
|
-
return rows.map(r => ({
|
|
1691
|
-
task: rowToTask(r),
|
|
1692
|
-
relation: r.relation,
|
|
1693
|
-
}));
|
|
1694
|
-
}
|
|
1695
|
-
countSkills(status) {
|
|
1696
|
-
const cond = status ? "WHERE status = ?" : "";
|
|
1697
|
-
const params = status ? [status] : [];
|
|
1698
|
-
const row = this.db.prepare(`SELECT COUNT(*) as c FROM skills ${cond}`).get(...params);
|
|
1699
|
-
return row.c;
|
|
1700
|
-
}
|
|
1701
|
-
// ─── Chunk-Skill ───
|
|
1702
|
-
setChunkSkillId(chunkId, skillId) {
|
|
1703
|
-
this.db.prepare("UPDATE chunks SET skill_id = ?, updated_at = ? WHERE id = ?").run(skillId, Date.now(), chunkId);
|
|
1704
|
-
}
|
|
1705
|
-
getDistinctSessionKeys() {
|
|
1706
|
-
return this.db.prepare("SELECT DISTINCT session_key FROM chunks ORDER BY session_key").all()
|
|
1707
|
-
.map(r => r.session_key);
|
|
1708
|
-
}
|
|
1709
|
-
// ─── Hub / Client connection ───
|
|
1710
|
-
setClientHubConnection(conn) {
|
|
1711
|
-
this.db.prepare(`
|
|
1712
|
-
INSERT INTO client_hub_connection (id, hub_url, user_id, username, user_token, role, connected_at, identity_key, last_known_status, hub_instance_id)
|
|
1713
|
-
VALUES (1, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1714
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
1715
|
-
hub_url = excluded.hub_url,
|
|
1716
|
-
user_id = excluded.user_id,
|
|
1717
|
-
username = excluded.username,
|
|
1718
|
-
user_token = excluded.user_token,
|
|
1719
|
-
role = excluded.role,
|
|
1720
|
-
connected_at = excluded.connected_at,
|
|
1721
|
-
identity_key = excluded.identity_key,
|
|
1722
|
-
last_known_status = excluded.last_known_status,
|
|
1723
|
-
hub_instance_id = excluded.hub_instance_id
|
|
1724
|
-
`).run(conn.hubUrl, conn.userId, conn.username, conn.userToken, conn.role, conn.connectedAt, conn.identityKey ?? "", conn.lastKnownStatus ?? "", conn.hubInstanceId ?? "");
|
|
1725
|
-
}
|
|
1726
|
-
getClientHubConnection() {
|
|
1727
|
-
const row = this.db.prepare('SELECT * FROM client_hub_connection WHERE id = 1').get();
|
|
1728
|
-
return row ? rowToClientHubConnection(row) : null;
|
|
1729
|
-
}
|
|
1730
|
-
clearClientHubConnection() {
|
|
1731
|
-
this.db.prepare('DELETE FROM client_hub_connection WHERE id = 1').run();
|
|
1732
|
-
}
|
|
1733
|
-
// ─── Local Shared Tasks (client-side tracking) ───
|
|
1734
|
-
markTaskShared(taskId, hubTaskId, syncedChunks, visibility, groupId, hubInstanceId) {
|
|
1735
|
-
this.db.prepare(`
|
|
1736
|
-
INSERT INTO local_shared_tasks (task_id, hub_task_id, visibility, group_id, synced_chunks, hub_instance_id, shared_at)
|
|
1737
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1738
|
-
ON CONFLICT(task_id) DO UPDATE SET
|
|
1739
|
-
hub_task_id = excluded.hub_task_id,
|
|
1740
|
-
visibility = excluded.visibility,
|
|
1741
|
-
group_id = excluded.group_id,
|
|
1742
|
-
synced_chunks = excluded.synced_chunks,
|
|
1743
|
-
hub_instance_id = excluded.hub_instance_id,
|
|
1744
|
-
shared_at = excluded.shared_at
|
|
1745
|
-
`).run(taskId, hubTaskId, visibility, groupId ?? null, syncedChunks, hubInstanceId ?? "", Date.now());
|
|
1746
|
-
}
|
|
1747
|
-
unmarkTaskShared(taskId) {
|
|
1748
|
-
this.db.prepare('DELETE FROM local_shared_tasks WHERE task_id = ?').run(taskId);
|
|
1749
|
-
}
|
|
1750
|
-
getLocalSharedTask(taskId) {
|
|
1751
|
-
const row = this.db.prepare('SELECT * FROM local_shared_tasks WHERE task_id = ?').get(taskId);
|
|
1752
|
-
if (!row)
|
|
1753
|
-
return null;
|
|
1754
|
-
return { taskId: row.task_id, hubTaskId: row.hub_task_id, visibility: row.visibility, groupId: row.group_id, syncedChunks: row.synced_chunks, sharedAt: row.shared_at, hubInstanceId: row.hub_instance_id || "" };
|
|
1755
|
-
}
|
|
1756
|
-
listLocalSharedTasks() {
|
|
1757
|
-
const rows = this.db.prepare('SELECT task_id, hub_task_id, visibility, group_id, synced_chunks, hub_instance_id FROM local_shared_tasks').all();
|
|
1758
|
-
return rows.map(r => ({ taskId: r.task_id, hubTaskId: r.hub_task_id, visibility: r.visibility, groupId: r.group_id, syncedChunks: r.synced_chunks, hubInstanceId: r.hub_instance_id || "" }));
|
|
1759
|
-
}
|
|
1760
|
-
// ─── Local Shared Memories (client-side tracking) ───
|
|
1761
|
-
markMemorySharedLocally(chunkId) {
|
|
1762
|
-
const chunk = this.getChunk(chunkId);
|
|
1763
|
-
if (!chunk)
|
|
1764
|
-
return { ok: false, reason: "not_found" };
|
|
1765
|
-
if (chunk.owner === "public") {
|
|
1766
|
-
const existing = this.getLocalSharedMemory(chunkId);
|
|
1767
|
-
return {
|
|
1768
|
-
ok: true,
|
|
1769
|
-
owner: "public",
|
|
1770
|
-
originalOwner: existing?.originalOwner ?? undefined,
|
|
1771
|
-
sharedAt: existing?.sharedAt ?? undefined,
|
|
1772
|
-
};
|
|
1773
|
-
}
|
|
1774
|
-
const sharedAt = Date.now();
|
|
1775
|
-
this.db.transaction(() => {
|
|
1776
|
-
this.db.prepare(`
|
|
1777
|
-
INSERT INTO local_shared_memories (chunk_id, original_owner, shared_at)
|
|
1778
|
-
VALUES (?, ?, ?)
|
|
1779
|
-
ON CONFLICT(chunk_id) DO UPDATE SET
|
|
1780
|
-
original_owner = excluded.original_owner,
|
|
1781
|
-
shared_at = excluded.shared_at
|
|
1782
|
-
`).run(chunkId, chunk.owner, sharedAt);
|
|
1783
|
-
this.updateChunk(chunkId, { owner: "public" });
|
|
1784
|
-
})();
|
|
1785
|
-
return { ok: true, owner: "public", originalOwner: chunk.owner, sharedAt };
|
|
1786
|
-
}
|
|
1787
|
-
unmarkMemorySharedLocally(chunkId, fallbackOwner) {
|
|
1788
|
-
const chunk = this.getChunk(chunkId);
|
|
1789
|
-
if (!chunk)
|
|
1790
|
-
return { ok: false, reason: "not_found" };
|
|
1791
|
-
if (chunk.owner !== "public") {
|
|
1792
|
-
return { ok: true, owner: chunk.owner };
|
|
1793
|
-
}
|
|
1794
|
-
const existing = this.getLocalSharedMemory(chunkId);
|
|
1795
|
-
const restoreOwner = existing?.originalOwner ?? fallbackOwner;
|
|
1796
|
-
if (!restoreOwner || restoreOwner === "public") {
|
|
1797
|
-
return { ok: false, reason: "original_owner_missing" };
|
|
1798
|
-
}
|
|
1799
|
-
this.db.transaction(() => {
|
|
1800
|
-
this.updateChunk(chunkId, { owner: restoreOwner });
|
|
1801
|
-
this.db.prepare("DELETE FROM local_shared_memories WHERE chunk_id = ?").run(chunkId);
|
|
1802
|
-
})();
|
|
1803
|
-
return { ok: true, owner: restoreOwner, originalOwner: restoreOwner };
|
|
1804
|
-
}
|
|
1805
|
-
getLocalSharedMemory(chunkId) {
|
|
1806
|
-
const row = this.db.prepare("SELECT chunk_id, original_owner, shared_at FROM local_shared_memories WHERE chunk_id = ?").get(chunkId);
|
|
1807
|
-
if (!row)
|
|
1808
|
-
return null;
|
|
1809
|
-
return {
|
|
1810
|
-
chunkId: row.chunk_id,
|
|
1811
|
-
originalOwner: row.original_owner,
|
|
1812
|
-
sharedAt: row.shared_at,
|
|
1813
|
-
};
|
|
1814
|
-
}
|
|
1815
|
-
// ─── Hub Users / Groups ───
|
|
1816
|
-
upsertHubUser(user) {
|
|
1817
|
-
this.db.prepare(`
|
|
1818
|
-
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)
|
|
1819
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1820
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
1821
|
-
username = excluded.username,
|
|
1822
|
-
device_name = excluded.device_name,
|
|
1823
|
-
role = excluded.role,
|
|
1824
|
-
status = excluded.status,
|
|
1825
|
-
token_hash = excluded.token_hash,
|
|
1826
|
-
created_at = excluded.created_at,
|
|
1827
|
-
approved_at = excluded.approved_at,
|
|
1828
|
-
identity_key = excluded.identity_key,
|
|
1829
|
-
left_at = excluded.left_at,
|
|
1830
|
-
removed_at = excluded.removed_at,
|
|
1831
|
-
rejected_at = excluded.rejected_at,
|
|
1832
|
-
rejoin_requested_at = excluded.rejoin_requested_at
|
|
1833
|
-
`).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);
|
|
1834
|
-
}
|
|
1835
|
-
getHubUser(userId) {
|
|
1836
|
-
const row = this.db.prepare('SELECT * FROM hub_users WHERE id = ?').get(userId);
|
|
1837
|
-
if (!row)
|
|
1838
|
-
return null;
|
|
1839
|
-
const user = rowToHubUser(row);
|
|
1840
|
-
user.groups = this.getGroupsForHubUser(userId);
|
|
1841
|
-
return user;
|
|
1842
|
-
}
|
|
1843
|
-
listHubUsers(status) {
|
|
1844
|
-
const rows = status
|
|
1845
|
-
? this.db.prepare('SELECT * FROM hub_users WHERE status = ? ORDER BY created_at').all(status)
|
|
1846
|
-
: this.db.prepare('SELECT * FROM hub_users ORDER BY created_at').all();
|
|
1847
|
-
return rows.map(r => {
|
|
1848
|
-
const user = rowToHubUser(r);
|
|
1849
|
-
user.groups = this.getGroupsForHubUser(r.id);
|
|
1850
|
-
return user;
|
|
1851
|
-
});
|
|
1852
|
-
}
|
|
1853
|
-
deleteHubMemoriesByUser(userId) {
|
|
1854
|
-
this.db.prepare('DELETE FROM hub_memories WHERE source_user_id = ?').run(userId);
|
|
1855
|
-
}
|
|
1856
|
-
deleteHubTasksByUser(userId) {
|
|
1857
|
-
this.db.prepare('DELETE FROM hub_tasks WHERE source_user_id = ?').run(userId);
|
|
1858
|
-
}
|
|
1859
|
-
deleteHubSkillsByUser(userId) {
|
|
1860
|
-
this.db.prepare('DELETE FROM hub_skills WHERE source_user_id = ?').run(userId);
|
|
1861
|
-
}
|
|
1862
|
-
deleteHubUser(userId, cleanResources = false) {
|
|
1863
|
-
if (cleanResources) {
|
|
1864
|
-
this.deleteHubTasksByUser(userId);
|
|
1865
|
-
this.deleteHubSkillsByUser(userId);
|
|
1866
|
-
this.deleteHubMemoriesByUser(userId);
|
|
1867
|
-
const result = this.db.prepare('DELETE FROM hub_users WHERE id = ?').run(userId);
|
|
1868
|
-
return result.changes > 0;
|
|
1869
|
-
}
|
|
1870
|
-
const result = this.db.prepare("UPDATE hub_users SET status = 'removed', token_hash = '', removed_at = ? WHERE id = ?").run(Date.now(), userId);
|
|
1871
|
-
return result.changes > 0;
|
|
1872
|
-
}
|
|
1873
|
-
findHubUserByIdentityKey(identityKey) {
|
|
1874
|
-
if (!identityKey)
|
|
1875
|
-
return null;
|
|
1876
|
-
const row = this.db.prepare('SELECT * FROM hub_users WHERE identity_key = ?').get(identityKey);
|
|
1877
|
-
return row ? rowToHubUser(row) : null;
|
|
1878
|
-
}
|
|
1879
|
-
markHubUserLeft(userId) {
|
|
1880
|
-
const result = this.db.prepare("UPDATE hub_users SET status = 'left', token_hash = '', left_at = ? WHERE id = ?").run(Date.now(), userId);
|
|
1881
|
-
return result.changes > 0;
|
|
1882
|
-
}
|
|
1883
|
-
updateHubUserActivity(userId, ip, timestamp) {
|
|
1884
|
-
this.db.prepare('UPDATE hub_users SET last_ip = ?, last_active_at = ? WHERE id = ?').run(ip, timestamp ?? Date.now(), userId);
|
|
1885
|
-
}
|
|
1886
|
-
// ─── Hub Groups ───
|
|
1887
|
-
upsertHubGroup(group) {
|
|
1888
|
-
this.db.prepare(`
|
|
1889
|
-
INSERT INTO hub_groups (id, name, description, created_at)
|
|
1890
|
-
VALUES (?, ?, ?, ?)
|
|
1891
|
-
ON CONFLICT(id) DO UPDATE SET name = excluded.name, description = excluded.description
|
|
1892
|
-
`).run(group.id, group.name, group.description ?? "", group.createdAt);
|
|
1893
|
-
}
|
|
1894
|
-
addHubGroupMember(groupId, userId, joinedAt) {
|
|
1895
|
-
this.db.prepare(`
|
|
1896
|
-
INSERT OR IGNORE INTO hub_group_members (group_id, user_id, joined_at)
|
|
1897
|
-
VALUES (?, ?, ?)
|
|
1898
|
-
`).run(groupId, userId, joinedAt);
|
|
1899
|
-
}
|
|
1900
|
-
removeHubGroupMember(groupId, userId) {
|
|
1901
|
-
this.db.prepare('DELETE FROM hub_group_members WHERE group_id = ? AND user_id = ?').run(groupId, userId);
|
|
1902
|
-
}
|
|
1903
|
-
getGroupsForHubUser(userId) {
|
|
1904
|
-
return this.db.prepare(`
|
|
1905
|
-
SELECT g.id, g.name, g.description FROM hub_groups g
|
|
1906
|
-
JOIN hub_group_members m ON m.group_id = g.id
|
|
1907
|
-
WHERE m.user_id = ?
|
|
1908
|
-
`).all(userId);
|
|
1909
|
-
}
|
|
1910
|
-
getHubUserContributions() {
|
|
1911
|
-
const result = {};
|
|
1912
|
-
const memRows = this.db.prepare('SELECT source_user_id, COUNT(*) as cnt FROM hub_memories GROUP BY source_user_id').all();
|
|
1913
|
-
const taskRows = this.db.prepare('SELECT source_user_id, COUNT(*) as cnt FROM hub_tasks GROUP BY source_user_id').all();
|
|
1914
|
-
const skillRows = this.db.prepare('SELECT source_user_id, COUNT(*) as cnt FROM hub_skills GROUP BY source_user_id').all();
|
|
1915
|
-
for (const r of memRows) {
|
|
1916
|
-
if (!result[r.source_user_id])
|
|
1917
|
-
result[r.source_user_id] = { memoryCount: 0, taskCount: 0, skillCount: 0 };
|
|
1918
|
-
result[r.source_user_id].memoryCount = r.cnt;
|
|
1919
|
-
}
|
|
1920
|
-
for (const r of taskRows) {
|
|
1921
|
-
if (!result[r.source_user_id])
|
|
1922
|
-
result[r.source_user_id] = { memoryCount: 0, taskCount: 0, skillCount: 0 };
|
|
1923
|
-
result[r.source_user_id].taskCount = r.cnt;
|
|
1924
|
-
}
|
|
1925
|
-
for (const r of skillRows) {
|
|
1926
|
-
if (!result[r.source_user_id])
|
|
1927
|
-
result[r.source_user_id] = { memoryCount: 0, taskCount: 0, skillCount: 0 };
|
|
1928
|
-
result[r.source_user_id].skillCount = r.cnt;
|
|
1929
|
-
}
|
|
1930
|
-
return result;
|
|
1931
|
-
}
|
|
1932
|
-
// ─── Hub Shared Data ───
|
|
1933
|
-
upsertHubTask(task) {
|
|
1934
|
-
this.db.prepare(`
|
|
1935
|
-
INSERT INTO hub_tasks (id, source_task_id, source_user_id, title, summary, group_id, visibility, created_at, updated_at)
|
|
1936
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1937
|
-
ON CONFLICT(source_user_id, source_task_id) DO UPDATE SET
|
|
1938
|
-
title = excluded.title,
|
|
1939
|
-
summary = excluded.summary,
|
|
1940
|
-
group_id = excluded.group_id,
|
|
1941
|
-
visibility = excluded.visibility,
|
|
1942
|
-
created_at = excluded.created_at,
|
|
1943
|
-
updated_at = excluded.updated_at
|
|
1944
|
-
`).run(task.id, task.sourceTaskId, task.sourceUserId, task.title, task.summary, task.groupId, task.visibility, task.createdAt, task.updatedAt);
|
|
1945
|
-
}
|
|
1946
|
-
getHubTaskBySource(sourceUserId, sourceTaskId) {
|
|
1947
|
-
const row = this.db.prepare('SELECT * FROM hub_tasks WHERE source_user_id = ? AND source_task_id = ?').get(sourceUserId, sourceTaskId);
|
|
1948
|
-
return row ? rowToHubTask(row) : null;
|
|
1949
|
-
}
|
|
1950
|
-
getHubTaskById(taskId) {
|
|
1951
|
-
const row = this.db.prepare('SELECT * FROM hub_tasks WHERE id = ?').get(taskId);
|
|
1952
|
-
return row ? rowToHubTask(row) : null;
|
|
1953
|
-
}
|
|
1954
|
-
upsertHubChunk(chunk) {
|
|
1955
|
-
if (!chunk.sourceTaskId)
|
|
1956
|
-
throw new Error("sourceTaskId is required for hub chunk upserts");
|
|
1957
|
-
const taskId = this.resolveCanonicalHubTaskId(chunk.hubTaskId, chunk.sourceUserId, chunk.sourceTaskId);
|
|
1958
|
-
this.db.prepare(`
|
|
1959
|
-
INSERT INTO hub_chunks (id, hub_task_id, source_chunk_id, source_user_id, role, content, summary, kind, created_at)
|
|
1960
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1961
|
-
ON CONFLICT(source_user_id, source_chunk_id) DO UPDATE SET
|
|
1962
|
-
hub_task_id = excluded.hub_task_id,
|
|
1963
|
-
role = excluded.role,
|
|
1964
|
-
content = excluded.content,
|
|
1965
|
-
summary = excluded.summary,
|
|
1966
|
-
kind = excluded.kind,
|
|
1967
|
-
created_at = excluded.created_at
|
|
1968
|
-
`).run(chunk.id, taskId, chunk.sourceChunkId, chunk.sourceUserId, chunk.role, chunk.content, chunk.summary, chunk.kind, chunk.createdAt);
|
|
1969
|
-
}
|
|
1970
|
-
getHubChunkBySource(sourceUserId, sourceChunkId) {
|
|
1971
|
-
const row = this.db.prepare('SELECT * FROM hub_chunks WHERE source_user_id = ? AND source_chunk_id = ?').get(sourceUserId, sourceChunkId);
|
|
1972
|
-
return row ? rowToHubChunk(row) : null;
|
|
1973
|
-
}
|
|
1974
|
-
deleteHubTaskBySource(sourceUserId, sourceTaskId) {
|
|
1975
|
-
this.db.prepare('DELETE FROM hub_tasks WHERE source_user_id = ? AND source_task_id = ?').run(sourceUserId, sourceTaskId);
|
|
1976
|
-
}
|
|
1977
|
-
upsertHubSkill(skill) {
|
|
1978
|
-
this.db.prepare(`
|
|
1979
|
-
INSERT INTO hub_skills (id, source_skill_id, source_user_id, name, description, version, group_id, visibility, bundle, quality_score, created_at, updated_at)
|
|
1980
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1981
|
-
ON CONFLICT(source_user_id, source_skill_id) DO UPDATE SET
|
|
1982
|
-
name = excluded.name,
|
|
1983
|
-
description = excluded.description,
|
|
1984
|
-
version = excluded.version,
|
|
1985
|
-
group_id = excluded.group_id,
|
|
1986
|
-
visibility = excluded.visibility,
|
|
1987
|
-
bundle = excluded.bundle,
|
|
1988
|
-
quality_score = excluded.quality_score,
|
|
1989
|
-
created_at = excluded.created_at,
|
|
1990
|
-
updated_at = excluded.updated_at
|
|
1991
|
-
`).run(skill.id, skill.sourceSkillId, skill.sourceUserId, skill.name, skill.description, skill.version, skill.groupId, skill.visibility, skill.bundle, skill.qualityScore, skill.createdAt, skill.updatedAt);
|
|
1992
|
-
}
|
|
1993
|
-
getHubSkillBySource(sourceUserId, sourceSkillId) {
|
|
1994
|
-
const row = this.db.prepare('SELECT * FROM hub_skills WHERE source_user_id = ? AND source_skill_id = ?').get(sourceUserId, sourceSkillId);
|
|
1995
|
-
return row ? rowToHubSkill(row) : null;
|
|
1996
|
-
}
|
|
1997
|
-
getHubSkillById(skillId) {
|
|
1998
|
-
const row = this.db.prepare('SELECT * FROM hub_skills WHERE id = ?').get(skillId);
|
|
1999
|
-
return row ? rowToHubSkill(row) : null;
|
|
2000
|
-
}
|
|
2001
|
-
upsertHubSkillEmbedding(skillId, vector, sourceUserId, sourceSkillId) {
|
|
2002
|
-
if (!sourceUserId || !sourceSkillId)
|
|
2003
|
-
throw new Error("sourceUserId and sourceSkillId are required for hub skill embedding upserts");
|
|
2004
|
-
const canonicalSkillId = this.resolveCanonicalHubSkillId(skillId, sourceUserId, sourceSkillId);
|
|
2005
|
-
const buf = Buffer.allocUnsafe(vector.length * 4);
|
|
2006
|
-
for (let i = 0; i < vector.length; i++)
|
|
2007
|
-
buf.writeFloatLE(vector[i], i * 4);
|
|
2008
|
-
this.db.prepare(`
|
|
2009
|
-
INSERT INTO hub_skill_embeddings (skill_id, vector, dimensions, updated_at)
|
|
2010
|
-
VALUES (?, ?, ?, ?)
|
|
2011
|
-
ON CONFLICT(skill_id) DO UPDATE SET
|
|
2012
|
-
vector = excluded.vector,
|
|
2013
|
-
dimensions = excluded.dimensions,
|
|
2014
|
-
updated_at = excluded.updated_at
|
|
2015
|
-
`).run(canonicalSkillId, buf, vector.length, Date.now());
|
|
2016
|
-
}
|
|
2017
|
-
getHubSkillEmbedding(skillId) {
|
|
2018
|
-
const row = this.db.prepare('SELECT vector, dimensions FROM hub_skill_embeddings WHERE skill_id = ?').get(skillId);
|
|
2019
|
-
if (!row)
|
|
2020
|
-
return null;
|
|
2021
|
-
const out = [];
|
|
2022
|
-
for (let i = 0; i < row.dimensions; i++)
|
|
2023
|
-
out.push(row.vector.readFloatLE(i * 4));
|
|
2024
|
-
return out;
|
|
2025
|
-
}
|
|
2026
|
-
getVisibleHubSkillEmbeddings() {
|
|
2027
|
-
const rows = this.db.prepare(`
|
|
2028
|
-
SELECT hse.skill_id, hse.vector, hse.dimensions
|
|
2029
|
-
FROM hub_skill_embeddings hse
|
|
2030
|
-
JOIN hub_skills hs ON hs.id = hse.skill_id
|
|
2031
|
-
`).all();
|
|
2032
|
-
return rows.map(r => ({
|
|
2033
|
-
skillId: r.skill_id,
|
|
2034
|
-
vector: new Float32Array(r.vector.buffer, r.vector.byteOffset, r.dimensions),
|
|
2035
|
-
}));
|
|
2036
|
-
}
|
|
2037
|
-
upsertHubMemoryEmbedding(memoryId, vector) {
|
|
2038
|
-
const buf = Buffer.from(vector.buffer, vector.byteOffset, vector.byteLength);
|
|
2039
|
-
this.db.prepare(`
|
|
2040
|
-
INSERT INTO hub_memory_embeddings (memory_id, vector, dimensions, updated_at)
|
|
2041
|
-
VALUES (?, ?, ?, ?)
|
|
2042
|
-
ON CONFLICT(memory_id) DO UPDATE SET vector = excluded.vector, dimensions = excluded.dimensions, updated_at = excluded.updated_at
|
|
2043
|
-
`).run(memoryId, buf, vector.length, Date.now());
|
|
2044
|
-
}
|
|
2045
|
-
getHubMemoryEmbedding(memoryId) {
|
|
2046
|
-
const row = this.db.prepare('SELECT vector, dimensions FROM hub_memory_embeddings WHERE memory_id = ?').get(memoryId);
|
|
2047
|
-
if (!row)
|
|
2048
|
-
return null;
|
|
2049
|
-
return new Float32Array(row.vector.buffer, row.vector.byteOffset, row.dimensions);
|
|
2050
|
-
}
|
|
2051
|
-
getVisibleHubMemoryEmbeddings(userId) {
|
|
2052
|
-
const rows = this.db.prepare(`
|
|
2053
|
-
SELECT hme.memory_id, hme.vector, hme.dimensions
|
|
2054
|
-
FROM hub_memory_embeddings hme
|
|
2055
|
-
JOIN hub_memories hm ON hm.id = hme.memory_id
|
|
2056
|
-
WHERE hm.visibility = 'public'
|
|
2057
|
-
OR hm.source_user_id = ?
|
|
2058
|
-
OR EXISTS (SELECT 1 FROM hub_group_members gm WHERE gm.group_id = hm.group_id AND gm.user_id = ?)
|
|
2059
|
-
`).all(userId, userId);
|
|
2060
|
-
return rows.map(r => ({
|
|
2061
|
-
memoryId: r.memory_id,
|
|
2062
|
-
vector: new Float32Array(r.vector.buffer, r.vector.byteOffset, r.dimensions),
|
|
2063
|
-
}));
|
|
2064
|
-
}
|
|
2065
|
-
searchHubChunks(query, options) {
|
|
2066
|
-
const limit = options?.maxResults ?? 10;
|
|
2067
|
-
const userId = options?.userId ?? "";
|
|
2068
|
-
const rows = this.db.prepare(`
|
|
2069
|
-
SELECT hc.id, hc.content, hc.summary, hc.role, hc.created_at, ht.title as task_title, ht.visibility,
|
|
2070
|
-
COALESCE(hg.name, '') as group_name, hu.username as owner_name,
|
|
2071
|
-
bm25(hub_chunks_fts) as rank
|
|
2072
|
-
FROM hub_chunks_fts f
|
|
2073
|
-
JOIN hub_chunks hc ON hc.rowid = f.rowid
|
|
2074
|
-
JOIN hub_tasks ht ON ht.id = hc.hub_task_id
|
|
2075
|
-
LEFT JOIN hub_users hu ON hu.id = ht.source_user_id
|
|
2076
|
-
LEFT JOIN hub_groups hg ON hg.id = ht.group_id
|
|
2077
|
-
WHERE hub_chunks_fts MATCH ?
|
|
2078
|
-
AND (ht.visibility = 'public'
|
|
2079
|
-
OR ht.source_user_id = ?
|
|
2080
|
-
OR EXISTS (SELECT 1 FROM hub_group_members gm WHERE gm.group_id = ht.group_id AND gm.user_id = ?))
|
|
2081
|
-
ORDER BY rank
|
|
2082
|
-
LIMIT ?
|
|
2083
|
-
`).all(sanitizeFtsQuery(query), userId, userId, limit);
|
|
2084
|
-
return rows.map((row, idx) => ({ hit: row, rank: idx + 1 }));
|
|
2085
|
-
}
|
|
2086
|
-
upsertHubEmbedding(chunkId, vector) {
|
|
2087
|
-
const buf = Buffer.from(vector.buffer, vector.byteOffset, vector.byteLength);
|
|
2088
|
-
this.db.prepare(`
|
|
2089
|
-
INSERT INTO hub_embeddings (chunk_id, vector, dimensions, updated_at)
|
|
2090
|
-
VALUES (?, ?, ?, ?)
|
|
2091
|
-
ON CONFLICT(chunk_id) DO UPDATE SET vector = excluded.vector, dimensions = excluded.dimensions, updated_at = excluded.updated_at
|
|
2092
|
-
`).run(chunkId, buf, vector.length, Date.now());
|
|
2093
|
-
}
|
|
2094
|
-
getHubEmbedding(chunkId) {
|
|
2095
|
-
const row = this.db.prepare('SELECT vector, dimensions FROM hub_embeddings WHERE chunk_id = ?').get(chunkId);
|
|
2096
|
-
if (!row)
|
|
2097
|
-
return null;
|
|
2098
|
-
return new Float32Array(row.vector.buffer, row.vector.byteOffset, row.dimensions);
|
|
2099
|
-
}
|
|
2100
|
-
getVisibleHubEmbeddings(userId) {
|
|
2101
|
-
const rows = this.db.prepare(`
|
|
2102
|
-
SELECT he.chunk_id, he.vector, he.dimensions
|
|
2103
|
-
FROM hub_embeddings he
|
|
2104
|
-
JOIN hub_chunks hc ON hc.id = he.chunk_id
|
|
2105
|
-
JOIN hub_tasks ht ON ht.id = hc.hub_task_id
|
|
2106
|
-
WHERE ht.visibility = 'public'
|
|
2107
|
-
OR ht.source_user_id = ?
|
|
2108
|
-
OR EXISTS (SELECT 1 FROM hub_group_members gm WHERE gm.group_id = ht.group_id AND gm.user_id = ?)
|
|
2109
|
-
`).all(userId, userId);
|
|
2110
|
-
return rows.map(r => ({
|
|
2111
|
-
chunkId: r.chunk_id,
|
|
2112
|
-
vector: new Float32Array(r.vector.buffer, r.vector.byteOffset, r.dimensions),
|
|
2113
|
-
}));
|
|
2114
|
-
}
|
|
2115
|
-
getVisibleHubSearchHitByChunkId(chunkId, userId) {
|
|
2116
|
-
const row = this.db.prepare(`
|
|
2117
|
-
SELECT hc.id, hc.content, hc.summary, hc.role, hc.created_at, ht.title as task_title, ht.visibility,
|
|
2118
|
-
COALESCE(hg.name, '') as group_name, hu.username as owner_name,
|
|
2119
|
-
0 as rank
|
|
2120
|
-
FROM hub_chunks hc
|
|
2121
|
-
JOIN hub_tasks ht ON ht.id = hc.hub_task_id
|
|
2122
|
-
LEFT JOIN hub_users hu ON hu.id = ht.source_user_id
|
|
2123
|
-
LEFT JOIN hub_groups hg ON hg.id = ht.group_id
|
|
2124
|
-
WHERE hc.id = ?
|
|
2125
|
-
AND (ht.visibility = 'public'
|
|
2126
|
-
OR ht.source_user_id = ?
|
|
2127
|
-
OR EXISTS (SELECT 1 FROM hub_group_members gm WHERE gm.group_id = ht.group_id AND gm.user_id = ?))
|
|
2128
|
-
LIMIT 1
|
|
2129
|
-
`).get(chunkId, userId, userId);
|
|
2130
|
-
return row ?? null;
|
|
2131
|
-
}
|
|
2132
|
-
getHubChunkById(chunkId) {
|
|
2133
|
-
const row = this.db.prepare('SELECT * FROM hub_chunks WHERE id = ?').get(chunkId);
|
|
2134
|
-
return row ? rowToHubChunk(row) : null;
|
|
2135
|
-
}
|
|
2136
|
-
searchHubSkills(query, options) {
|
|
2137
|
-
const limit = options?.maxResults ?? 10;
|
|
2138
|
-
const userId = options?.userId ?? "";
|
|
2139
|
-
const sanitized = sanitizeFtsQuery(query);
|
|
2140
|
-
let rows;
|
|
2141
|
-
if (sanitized) {
|
|
2142
|
-
rows = this.db.prepare(`
|
|
2143
|
-
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,
|
|
2144
|
-
bm25(hub_skills_fts) as rank
|
|
2145
|
-
FROM hub_skills_fts f
|
|
2146
|
-
JOIN hub_skills hs ON hs.rowid = f.rowid
|
|
2147
|
-
LEFT JOIN hub_users hu ON hu.id = hs.source_user_id
|
|
2148
|
-
WHERE hub_skills_fts MATCH ?
|
|
2149
|
-
ORDER BY rank
|
|
2150
|
-
LIMIT ?
|
|
2151
|
-
`).all(sanitized, limit);
|
|
2152
|
-
}
|
|
2153
|
-
else {
|
|
2154
|
-
rows = this.db.prepare(`
|
|
2155
|
-
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,
|
|
2156
|
-
0 as rank
|
|
2157
|
-
FROM hub_skills hs
|
|
2158
|
-
LEFT JOIN hub_users hu ON hu.id = hs.source_user_id
|
|
2159
|
-
ORDER BY hs.updated_at DESC
|
|
2160
|
-
LIMIT ?
|
|
2161
|
-
`).all(limit);
|
|
2162
|
-
}
|
|
2163
|
-
return rows.map((row, idx) => ({ hit: row, rank: idx + 1 }));
|
|
2164
|
-
}
|
|
2165
|
-
deleteHubSkillBySource(sourceUserId, sourceSkillId) {
|
|
2166
|
-
this.db.prepare('DELETE FROM hub_skills WHERE source_user_id = ? AND source_skill_id = ?').run(sourceUserId, sourceSkillId);
|
|
2167
|
-
}
|
|
2168
|
-
listVisibleHubTasks(userId, limit = 40) {
|
|
2169
|
-
const rows = this.db.prepare(`
|
|
2170
|
-
SELECT t.*, u.username AS owner_name, u.status AS owner_status, NULL AS group_name,
|
|
2171
|
-
(SELECT COUNT(*) FROM hub_chunks c WHERE c.hub_task_id = t.id) AS chunk_count
|
|
2172
|
-
FROM hub_tasks t
|
|
2173
|
-
LEFT JOIN hub_users u ON u.id = t.source_user_id
|
|
2174
|
-
ORDER BY t.updated_at DESC
|
|
2175
|
-
LIMIT ?
|
|
2176
|
-
`).all(limit);
|
|
2177
|
-
return rows.map(r => ({
|
|
2178
|
-
id: r.id, sourceTaskId: r.source_task_id, sourceUserId: r.source_user_id,
|
|
2179
|
-
title: r.title, summary: r.summary, groupId: r.group_id, groupName: r.group_name ?? null,
|
|
2180
|
-
visibility: r.visibility, ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", chunkCount: r.chunk_count ?? 0,
|
|
2181
|
-
createdAt: r.created_at, updatedAt: r.updated_at,
|
|
2182
|
-
}));
|
|
2183
|
-
}
|
|
2184
|
-
listAllHubTasks() {
|
|
2185
|
-
const rows = this.db.prepare(`
|
|
2186
|
-
SELECT t.*, u.username AS owner_name, u.status AS owner_status,
|
|
2187
|
-
(SELECT COUNT(*) FROM hub_chunks c WHERE c.hub_task_id = t.id) AS chunk_count
|
|
2188
|
-
FROM hub_tasks t
|
|
2189
|
-
LEFT JOIN hub_users u ON u.id = t.source_user_id
|
|
2190
|
-
ORDER BY t.updated_at DESC
|
|
2191
|
-
`).all();
|
|
2192
|
-
return rows.map(r => ({
|
|
2193
|
-
id: r.id, sourceTaskId: r.source_task_id, sourceUserId: r.source_user_id,
|
|
2194
|
-
title: r.title, summary: r.summary, groupId: r.group_id, groupName: null,
|
|
2195
|
-
visibility: r.visibility, ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", chunkCount: r.chunk_count ?? 0,
|
|
2196
|
-
createdAt: r.created_at, updatedAt: r.updated_at,
|
|
2197
|
-
}));
|
|
2198
|
-
}
|
|
2199
|
-
listHubChunksByTaskId(hubTaskId) {
|
|
2200
|
-
const rows = this.db.prepare('SELECT * FROM hub_chunks WHERE hub_task_id = ? ORDER BY created_at ASC').all(hubTaskId);
|
|
2201
|
-
return rows.map(rowToHubChunk);
|
|
2202
|
-
}
|
|
2203
|
-
deleteHubTaskById(taskId) {
|
|
2204
|
-
const info = this.db.prepare('DELETE FROM hub_tasks WHERE id = ?').run(taskId);
|
|
2205
|
-
return info.changes > 0;
|
|
2206
|
-
}
|
|
2207
|
-
listVisibleHubSkills(userId, limit = 40) {
|
|
2208
|
-
const rows = this.db.prepare(`
|
|
2209
|
-
SELECT s.*, u.username AS owner_name, u.status AS owner_status, NULL AS group_name
|
|
2210
|
-
FROM hub_skills s
|
|
2211
|
-
LEFT JOIN hub_users u ON u.id = s.source_user_id
|
|
2212
|
-
ORDER BY s.updated_at DESC
|
|
2213
|
-
LIMIT ?
|
|
2214
|
-
`).all(limit);
|
|
2215
|
-
return rows.map(r => ({
|
|
2216
|
-
id: r.id, sourceSkillId: r.source_skill_id, sourceUserId: r.source_user_id,
|
|
2217
|
-
name: r.name, description: r.description, version: r.version,
|
|
2218
|
-
groupId: r.group_id, groupName: r.group_name ?? null, visibility: r.visibility,
|
|
2219
|
-
ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", qualityScore: r.quality_score,
|
|
2220
|
-
createdAt: r.created_at, updatedAt: r.updated_at,
|
|
2221
|
-
}));
|
|
2222
|
-
}
|
|
2223
|
-
listAllHubSkills() {
|
|
2224
|
-
const rows = this.db.prepare(`
|
|
2225
|
-
SELECT s.*, u.username AS owner_name, u.status AS owner_status
|
|
2226
|
-
FROM hub_skills s
|
|
2227
|
-
LEFT JOIN hub_users u ON u.id = s.source_user_id
|
|
2228
|
-
ORDER BY s.updated_at DESC
|
|
2229
|
-
`).all();
|
|
2230
|
-
return rows.map(r => ({
|
|
2231
|
-
id: r.id, sourceSkillId: r.source_skill_id, sourceUserId: r.source_user_id,
|
|
2232
|
-
name: r.name, description: r.description, version: r.version,
|
|
2233
|
-
groupId: r.group_id, groupName: null, visibility: r.visibility,
|
|
2234
|
-
ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", qualityScore: r.quality_score,
|
|
2235
|
-
createdAt: r.created_at, updatedAt: r.updated_at,
|
|
2236
|
-
}));
|
|
2237
|
-
}
|
|
2238
|
-
deleteHubSkillById(skillId) {
|
|
2239
|
-
const info = this.db.prepare('DELETE FROM hub_skills WHERE id = ?').run(skillId);
|
|
2240
|
-
return info.changes > 0;
|
|
2241
|
-
}
|
|
2242
|
-
// ─── Hub Shared Memories (independent) ───
|
|
2243
|
-
upsertHubMemory(memory) {
|
|
2244
|
-
this.db.prepare(`
|
|
2245
|
-
INSERT INTO hub_memories (id, source_chunk_id, source_user_id, role, content, summary, kind, group_id, visibility, created_at, updated_at)
|
|
2246
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2247
|
-
ON CONFLICT(source_user_id, source_chunk_id) DO UPDATE SET
|
|
2248
|
-
role = excluded.role,
|
|
2249
|
-
content = excluded.content,
|
|
2250
|
-
summary = excluded.summary,
|
|
2251
|
-
kind = excluded.kind,
|
|
2252
|
-
group_id = excluded.group_id,
|
|
2253
|
-
visibility = excluded.visibility,
|
|
2254
|
-
created_at = excluded.created_at,
|
|
2255
|
-
updated_at = excluded.updated_at
|
|
2256
|
-
`).run(memory.id, memory.sourceChunkId, memory.sourceUserId, memory.role, memory.content, memory.summary, memory.kind, memory.groupId, memory.visibility, memory.createdAt, memory.updatedAt);
|
|
2257
|
-
}
|
|
2258
|
-
getHubMemoryBySource(sourceUserId, sourceChunkId) {
|
|
2259
|
-
const row = this.db.prepare('SELECT * FROM hub_memories WHERE source_user_id = ? AND source_chunk_id = ?').get(sourceUserId, sourceChunkId);
|
|
2260
|
-
return row ? rowToHubMemory(row) : null;
|
|
2261
|
-
}
|
|
2262
|
-
getHubMemoryById(memoryId) {
|
|
2263
|
-
const row = this.db.prepare('SELECT * FROM hub_memories WHERE id = ?').get(memoryId);
|
|
2264
|
-
return row ? rowToHubMemory(row) : null;
|
|
2265
|
-
}
|
|
2266
|
-
deleteHubMemoryBySource(sourceUserId, sourceChunkId) {
|
|
2267
|
-
this.db.prepare('DELETE FROM hub_memories WHERE source_user_id = ? AND source_chunk_id = ?').run(sourceUserId, sourceChunkId);
|
|
2268
|
-
}
|
|
2269
|
-
deleteHubMemoryById(memoryId) {
|
|
2270
|
-
const info = this.db.prepare('DELETE FROM hub_memories WHERE id = ?').run(memoryId);
|
|
2271
|
-
return info.changes > 0;
|
|
2272
|
-
}
|
|
2273
|
-
// ─── Team share metadata (Client role — UI only, not used for local recall / FTS) ───
|
|
2274
|
-
upsertTeamSharedChunk(chunkId, row) {
|
|
2275
|
-
const now = Date.now();
|
|
2276
|
-
const vis = row.visibility === "group" ? "group" : "public";
|
|
2277
|
-
const gid = vis === "group" ? (row.groupId ?? null) : null;
|
|
2278
|
-
this.db.prepare(`
|
|
2279
|
-
INSERT INTO team_shared_chunks (chunk_id, hub_memory_id, visibility, group_id, hub_instance_id, shared_at)
|
|
2280
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
2281
|
-
ON CONFLICT(chunk_id) DO UPDATE SET
|
|
2282
|
-
hub_memory_id = excluded.hub_memory_id,
|
|
2283
|
-
visibility = excluded.visibility,
|
|
2284
|
-
group_id = excluded.group_id,
|
|
2285
|
-
hub_instance_id = excluded.hub_instance_id,
|
|
2286
|
-
shared_at = excluded.shared_at
|
|
2287
|
-
`).run(chunkId, row.hubMemoryId ?? "", vis, gid, row.hubInstanceId ?? "", now);
|
|
2288
|
-
}
|
|
2289
|
-
getTeamSharedChunk(chunkId) {
|
|
2290
|
-
const r = this.db.prepare("SELECT chunk_id, hub_memory_id, visibility, group_id, hub_instance_id, shared_at FROM team_shared_chunks WHERE chunk_id = ?").get(chunkId);
|
|
2291
|
-
if (!r)
|
|
2292
|
-
return null;
|
|
2293
|
-
return {
|
|
2294
|
-
chunkId: r.chunk_id,
|
|
2295
|
-
hubMemoryId: r.hub_memory_id,
|
|
2296
|
-
visibility: r.visibility,
|
|
2297
|
-
groupId: r.group_id,
|
|
2298
|
-
hubInstanceId: r.hub_instance_id || "",
|
|
2299
|
-
sharedAt: r.shared_at,
|
|
2300
|
-
};
|
|
2301
|
-
}
|
|
2302
|
-
deleteTeamSharedChunk(chunkId) {
|
|
2303
|
-
const info = this.db.prepare("DELETE FROM team_shared_chunks WHERE chunk_id = ?").run(chunkId);
|
|
2304
|
-
return info.changes > 0;
|
|
2305
|
-
}
|
|
2306
|
-
// ─── Team Shared Skills (Client role — UI metadata only) ───
|
|
2307
|
-
upsertTeamSharedSkill(skillId, row) {
|
|
2308
|
-
const now = Date.now();
|
|
2309
|
-
const vis = row.visibility === "group" ? "group" : "public";
|
|
2310
|
-
const gid = vis === "group" ? (row.groupId ?? null) : null;
|
|
2311
|
-
this.db.prepare(`
|
|
2312
|
-
INSERT INTO team_shared_skills (skill_id, hub_skill_id, visibility, group_id, hub_instance_id, shared_at)
|
|
2313
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
2314
|
-
ON CONFLICT(skill_id) DO UPDATE SET
|
|
2315
|
-
hub_skill_id = excluded.hub_skill_id,
|
|
2316
|
-
visibility = excluded.visibility,
|
|
2317
|
-
group_id = excluded.group_id,
|
|
2318
|
-
hub_instance_id = excluded.hub_instance_id,
|
|
2319
|
-
shared_at = excluded.shared_at
|
|
2320
|
-
`).run(skillId, row.hubSkillId ?? "", vis, gid, row.hubInstanceId ?? "", now);
|
|
2321
|
-
}
|
|
2322
|
-
getTeamSharedSkill(skillId) {
|
|
2323
|
-
const r = this.db.prepare("SELECT * FROM team_shared_skills WHERE skill_id = ?").get(skillId);
|
|
2324
|
-
if (!r)
|
|
2325
|
-
return null;
|
|
2326
|
-
return { skillId: r.skill_id, hubSkillId: r.hub_skill_id, visibility: r.visibility, groupId: r.group_id, hubInstanceId: r.hub_instance_id || "", sharedAt: r.shared_at };
|
|
2327
|
-
}
|
|
2328
|
-
deleteTeamSharedSkill(skillId) {
|
|
2329
|
-
return this.db.prepare("DELETE FROM team_shared_skills WHERE skill_id = ?").run(skillId).changes > 0;
|
|
2330
|
-
}
|
|
2331
|
-
// ─── Team sharing cleanup (role switch / leave) ───
|
|
2332
|
-
clearTeamSharedChunks() {
|
|
2333
|
-
this.db.prepare("DELETE FROM team_shared_chunks").run();
|
|
2334
|
-
}
|
|
2335
|
-
clearTeamSharedSkills() {
|
|
2336
|
-
this.db.prepare("DELETE FROM team_shared_skills").run();
|
|
2337
|
-
}
|
|
2338
|
-
downgradeTeamSharedTasksToLocal() {
|
|
2339
|
-
this.db.prepare("UPDATE local_shared_tasks SET hub_task_id = '', hub_instance_id = '', visibility = 'public', group_id = NULL, synced_chunks = 0").run();
|
|
2340
|
-
}
|
|
2341
|
-
downgradeTeamSharedTaskToLocal(taskId) {
|
|
2342
|
-
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);
|
|
2343
|
-
}
|
|
2344
|
-
clearAllTeamSharingState() {
|
|
2345
|
-
this.clearTeamSharedChunks();
|
|
2346
|
-
this.clearTeamSharedSkills();
|
|
2347
|
-
this.downgradeTeamSharedTasksToLocal();
|
|
2348
|
-
}
|
|
2349
|
-
// ─── Hub Notifications ───
|
|
2350
|
-
insertHubNotification(n) {
|
|
2351
|
-
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());
|
|
2352
|
-
}
|
|
2353
|
-
hasRecentHubNotification(userId, type, resource, windowMs = 300_000) {
|
|
2354
|
-
const since = Date.now() - windowMs;
|
|
2355
|
-
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);
|
|
2356
|
-
return row.cnt > 0;
|
|
2357
|
-
}
|
|
2358
|
-
listHubNotifications(userId, opts) {
|
|
2359
|
-
const where = opts?.unreadOnly ? 'WHERE user_id = ? AND read = 0' : 'WHERE user_id = ?';
|
|
2360
|
-
const limit = opts?.limit ?? 50;
|
|
2361
|
-
const rows = this.db.prepare(`SELECT * FROM hub_notifications ${where} ORDER BY created_at DESC LIMIT ?`).all(userId, limit);
|
|
2362
|
-
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 }));
|
|
2363
|
-
}
|
|
2364
|
-
countUnreadHubNotifications(userId) {
|
|
2365
|
-
const row = this.db.prepare('SELECT COUNT(*) AS cnt FROM hub_notifications WHERE user_id = ? AND read = 0').get(userId);
|
|
2366
|
-
return row.cnt;
|
|
2367
|
-
}
|
|
2368
|
-
markHubNotificationsRead(userId, ids) {
|
|
2369
|
-
if (ids && ids.length > 0) {
|
|
2370
|
-
const placeholders = ids.map(() => '?').join(',');
|
|
2371
|
-
this.db.prepare(`UPDATE hub_notifications SET read = 1 WHERE user_id = ? AND id IN (${placeholders})`).run(userId, ...ids);
|
|
2372
|
-
}
|
|
2373
|
-
else {
|
|
2374
|
-
this.db.prepare('UPDATE hub_notifications SET read = 1 WHERE user_id = ?').run(userId);
|
|
2375
|
-
}
|
|
2376
|
-
}
|
|
2377
|
-
clearHubNotifications(userId) {
|
|
2378
|
-
this.db.prepare('DELETE FROM hub_notifications WHERE user_id = ?').run(userId);
|
|
2379
|
-
}
|
|
2380
|
-
// upsertHubMemoryEmbedding / getHubMemoryEmbedding removed:
|
|
2381
|
-
// hub memory vectors are now computed on-the-fly at search time.
|
|
2382
|
-
searchHubMemories(query, options) {
|
|
2383
|
-
const limit = options?.maxResults ?? 10;
|
|
2384
|
-
const userId = options?.userId ?? "";
|
|
2385
|
-
const sanitized = sanitizeFtsQuery(query);
|
|
2386
|
-
if (!sanitized)
|
|
2387
|
-
return [];
|
|
2388
|
-
const rows = this.db.prepare(`
|
|
2389
|
-
SELECT hm.id, hm.content, hm.summary, hm.role, hm.created_at, hm.visibility, '' as group_name, hu.username as owner_name,
|
|
2390
|
-
bm25(hub_memories_fts) as rank
|
|
2391
|
-
FROM hub_memories_fts f
|
|
2392
|
-
JOIN hub_memories hm ON hm.rowid = f.rowid
|
|
2393
|
-
LEFT JOIN hub_users hu ON hu.id = hm.source_user_id
|
|
2394
|
-
WHERE hub_memories_fts MATCH ?
|
|
2395
|
-
ORDER BY rank
|
|
2396
|
-
LIMIT ?
|
|
2397
|
-
`).all(sanitized, limit);
|
|
2398
|
-
return rows.map((row, idx) => ({ hit: row, rank: idx + 1 }));
|
|
2399
|
-
}
|
|
2400
|
-
// getVisibleHubMemoryEmbeddings removed: vectors computed on-the-fly at search time.
|
|
2401
|
-
getVisibleHubSearchHitByMemoryId(memoryId, userId) {
|
|
2402
|
-
const row = this.db.prepare(`
|
|
2403
|
-
SELECT hm.id, hm.content, hm.summary, hm.role, hm.created_at, hm.visibility, '' as group_name, hu.username as owner_name,
|
|
2404
|
-
0 as rank
|
|
2405
|
-
FROM hub_memories hm
|
|
2406
|
-
LEFT JOIN hub_users hu ON hu.id = hm.source_user_id
|
|
2407
|
-
WHERE hm.id = ?
|
|
2408
|
-
LIMIT 1
|
|
2409
|
-
`).get(memoryId);
|
|
2410
|
-
return row ?? null;
|
|
2411
|
-
}
|
|
2412
|
-
listVisibleHubMemories(userId, limit = 40) {
|
|
2413
|
-
const rows = this.db.prepare(`
|
|
2414
|
-
SELECT m.*, u.username AS owner_name, u.status AS owner_status, NULL AS group_name
|
|
2415
|
-
FROM hub_memories m
|
|
2416
|
-
LEFT JOIN hub_users u ON u.id = m.source_user_id
|
|
2417
|
-
ORDER BY m.updated_at DESC
|
|
2418
|
-
LIMIT ?
|
|
2419
|
-
`).all(limit);
|
|
2420
|
-
return rows.map(r => ({
|
|
2421
|
-
id: r.id, sourceChunkId: r.source_chunk_id, sourceUserId: r.source_user_id,
|
|
2422
|
-
role: r.role, content: r.content ?? "", summary: r.summary, kind: r.kind,
|
|
2423
|
-
groupId: r.group_id, groupName: r.group_name ?? null, visibility: r.visibility,
|
|
2424
|
-
ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", createdAt: r.created_at, updatedAt: r.updated_at,
|
|
2425
|
-
}));
|
|
2426
|
-
}
|
|
2427
|
-
listAllHubMemories() {
|
|
2428
|
-
const rows = this.db.prepare(`
|
|
2429
|
-
SELECT m.*, u.username AS owner_name, u.status AS owner_status
|
|
2430
|
-
FROM hub_memories m
|
|
2431
|
-
LEFT JOIN hub_users u ON u.id = m.source_user_id
|
|
2432
|
-
ORDER BY m.updated_at DESC
|
|
2433
|
-
`).all();
|
|
2434
|
-
return rows.map(r => ({
|
|
2435
|
-
id: r.id, sourceChunkId: r.source_chunk_id, sourceUserId: r.source_user_id,
|
|
2436
|
-
role: r.role, content: r.content ?? "", summary: r.summary, kind: r.kind,
|
|
2437
|
-
groupId: r.group_id, groupName: null, visibility: r.visibility,
|
|
2438
|
-
ownerName: r.owner_name ?? "unknown", ownerStatus: r.owner_status ?? "", createdAt: r.created_at, updatedAt: r.updated_at,
|
|
2439
|
-
}));
|
|
2440
|
-
}
|
|
2441
|
-
resolveCanonicalHubTaskId(taskId, sourceUserId, sourceTaskId) {
|
|
2442
|
-
if (sourceTaskId) {
|
|
2443
|
-
const bySource = this.db.prepare('SELECT id FROM hub_tasks WHERE source_user_id = ? AND source_task_id = ?').get(sourceUserId, sourceTaskId);
|
|
2444
|
-
if (!bySource)
|
|
2445
|
-
throw new Error(`source task not found for user=${sourceUserId} sourceTaskId=${sourceTaskId}`);
|
|
2446
|
-
if (bySource.id != taskId)
|
|
2447
|
-
throw new Error(`mismatch between source task and hubTaskId: expected ${bySource.id}, got ${taskId}`);
|
|
2448
|
-
return bySource.id;
|
|
2449
|
-
}
|
|
2450
|
-
throw new Error(`source task not found for user=${sourceUserId} taskId=${taskId}`);
|
|
2451
|
-
}
|
|
2452
|
-
resolveCanonicalHubSkillId(skillId, sourceUserId, sourceSkillId) {
|
|
2453
|
-
if (sourceUserId && sourceSkillId) {
|
|
2454
|
-
const bySource = this.db.prepare('SELECT id FROM hub_skills WHERE source_user_id = ? AND source_skill_id = ?').get(sourceUserId, sourceSkillId);
|
|
2455
|
-
if (!bySource)
|
|
2456
|
-
throw new Error(`source skill not found for user=${sourceUserId} sourceSkillId=${sourceSkillId}`);
|
|
2457
|
-
if (bySource.id != skillId)
|
|
2458
|
-
throw new Error(`mismatch between source skill and skillId: expected ${bySource.id}, got ${skillId}`);
|
|
2459
|
-
return bySource.id;
|
|
2460
|
-
}
|
|
2461
|
-
throw new Error(`source skill not found for skillId=${skillId}`);
|
|
2462
|
-
}
|
|
2463
|
-
getSessionOwnerMap(sessionKeys) {
|
|
2464
|
-
const result = new Map();
|
|
2465
|
-
if (sessionKeys.length === 0)
|
|
2466
|
-
return result;
|
|
2467
|
-
const placeholders = sessionKeys.map(() => "?").join(",");
|
|
2468
|
-
const rows = this.db.prepare(`SELECT session_key, owner FROM chunks WHERE session_key IN (${placeholders}) AND owner IS NOT NULL GROUP BY session_key`).all(...sessionKeys);
|
|
2469
|
-
for (const r of rows)
|
|
2470
|
-
result.set(r.session_key, r.owner);
|
|
2471
|
-
return result;
|
|
2472
|
-
}
|
|
2473
|
-
close() {
|
|
2474
|
-
this.db.close();
|
|
2475
|
-
}
|
|
2476
|
-
}
|
|
2477
|
-
exports.SqliteStore = SqliteStore;
|
|
2478
|
-
// ─── FTS helpers ───
|
|
2479
|
-
/**
|
|
2480
|
-
* Sanitize user input for FTS5 MATCH queries.
|
|
2481
|
-
* Strip FTS operators and special characters, then join tokens
|
|
2482
|
-
* with implicit AND (space-separated) for safe querying.
|
|
2483
|
-
*/
|
|
2484
|
-
function sanitizeFtsQuery(raw) {
|
|
2485
|
-
const tokens = raw
|
|
2486
|
-
.replace(/[."""(){}[\]*:^~!@#$%&\\/<>,;'`-]/g, " ")
|
|
2487
|
-
.split(/\s+/)
|
|
2488
|
-
.map((t) => t.trim().replace(/^-+|-+$/g, ""))
|
|
2489
|
-
.filter((t) => t.length > 1)
|
|
2490
|
-
.filter((t) => !FTS_RESERVED.has(t.toUpperCase()));
|
|
2491
|
-
return tokens.join(" ");
|
|
2492
|
-
}
|
|
2493
|
-
const FTS_RESERVED = new Set(["AND", "OR", "NOT", "NEAR"]);
|
|
2494
|
-
function rowToChunk(row) {
|
|
2495
|
-
return {
|
|
2496
|
-
id: row.id,
|
|
2497
|
-
sessionKey: row.session_key,
|
|
2498
|
-
turnId: row.turn_id,
|
|
2499
|
-
seq: row.seq,
|
|
2500
|
-
role: row.role,
|
|
2501
|
-
content: row.content,
|
|
2502
|
-
kind: row.kind,
|
|
2503
|
-
summary: row.summary,
|
|
2504
|
-
embedding: null,
|
|
2505
|
-
taskId: row.task_id,
|
|
2506
|
-
skillId: row.skill_id ?? null,
|
|
2507
|
-
owner: row.owner ?? "agent:main",
|
|
2508
|
-
dedupStatus: (row.dedup_status ?? "active"),
|
|
2509
|
-
dedupTarget: row.dedup_target ?? null,
|
|
2510
|
-
dedupReason: row.dedup_reason ?? null,
|
|
2511
|
-
mergeCount: row.merge_count ?? 0,
|
|
2512
|
-
lastHitAt: row.last_hit_at ?? null,
|
|
2513
|
-
mergeHistory: row.merge_history ?? "[]",
|
|
2514
|
-
createdAt: row.created_at,
|
|
2515
|
-
updatedAt: row.updated_at,
|
|
2516
|
-
};
|
|
2517
|
-
}
|
|
2518
|
-
function rowToTask(row) {
|
|
2519
|
-
return {
|
|
2520
|
-
id: row.id,
|
|
2521
|
-
sessionKey: row.session_key,
|
|
2522
|
-
title: row.title,
|
|
2523
|
-
summary: row.summary,
|
|
2524
|
-
status: row.status,
|
|
2525
|
-
owner: row.owner ?? "agent:main",
|
|
2526
|
-
startedAt: row.started_at,
|
|
2527
|
-
endedAt: row.ended_at,
|
|
2528
|
-
updatedAt: row.updated_at,
|
|
2529
|
-
};
|
|
2530
|
-
}
|
|
2531
|
-
function rowToSkill(row) {
|
|
2532
|
-
return {
|
|
2533
|
-
id: row.id,
|
|
2534
|
-
name: row.name,
|
|
2535
|
-
description: row.description,
|
|
2536
|
-
version: row.version,
|
|
2537
|
-
status: row.status,
|
|
2538
|
-
tags: row.tags,
|
|
2539
|
-
sourceType: row.source_type,
|
|
2540
|
-
dirPath: row.dir_path,
|
|
2541
|
-
installed: row.installed,
|
|
2542
|
-
owner: row.owner ?? "agent:main",
|
|
2543
|
-
visibility: (row.visibility ?? "private"),
|
|
2544
|
-
qualityScore: row.quality_score ?? null,
|
|
2545
|
-
createdAt: row.created_at,
|
|
2546
|
-
updatedAt: row.updated_at,
|
|
2547
|
-
};
|
|
2548
|
-
}
|
|
2549
|
-
function rowToSkillVersion(row) {
|
|
2550
|
-
return {
|
|
2551
|
-
id: row.id,
|
|
2552
|
-
skillId: row.skill_id,
|
|
2553
|
-
version: row.version,
|
|
2554
|
-
content: row.content,
|
|
2555
|
-
changelog: row.changelog,
|
|
2556
|
-
changeSummary: row.change_summary ?? "",
|
|
2557
|
-
upgradeType: row.upgrade_type,
|
|
2558
|
-
sourceTaskId: row.source_task_id,
|
|
2559
|
-
metrics: row.metrics,
|
|
2560
|
-
qualityScore: row.quality_score ?? null,
|
|
2561
|
-
createdAt: row.created_at,
|
|
2562
|
-
};
|
|
2563
|
-
}
|
|
2564
|
-
function rowToClientHubConnection(row) {
|
|
2565
|
-
return {
|
|
2566
|
-
hubUrl: row.hub_url,
|
|
2567
|
-
userId: row.user_id,
|
|
2568
|
-
username: row.username,
|
|
2569
|
-
userToken: row.user_token,
|
|
2570
|
-
role: row.role,
|
|
2571
|
-
connectedAt: row.connected_at,
|
|
2572
|
-
identityKey: row.identity_key || "",
|
|
2573
|
-
lastKnownStatus: row.last_known_status || "",
|
|
2574
|
-
hubInstanceId: row.hub_instance_id || "",
|
|
2575
|
-
};
|
|
2576
|
-
}
|
|
2577
|
-
function rowToHubUser(row) {
|
|
2578
|
-
return {
|
|
2579
|
-
id: row.id,
|
|
2580
|
-
username: row.username,
|
|
2581
|
-
deviceName: row.device_name || undefined,
|
|
2582
|
-
role: row.role,
|
|
2583
|
-
status: row.status,
|
|
2584
|
-
groups: [],
|
|
2585
|
-
tokenHash: row.token_hash,
|
|
2586
|
-
createdAt: row.created_at,
|
|
2587
|
-
approvedAt: row.approved_at,
|
|
2588
|
-
lastIp: row.last_ip || "",
|
|
2589
|
-
lastActiveAt: row.last_active_at ?? null,
|
|
2590
|
-
identityKey: row.identity_key || "",
|
|
2591
|
-
leftAt: row.left_at ?? null,
|
|
2592
|
-
removedAt: row.removed_at ?? null,
|
|
2593
|
-
rejectedAt: row.rejected_at ?? null,
|
|
2594
|
-
rejoinRequestedAt: row.rejoin_requested_at ?? null,
|
|
2595
|
-
};
|
|
2596
|
-
}
|
|
2597
|
-
function rowToHubTask(row) {
|
|
2598
|
-
return {
|
|
2599
|
-
id: row.id,
|
|
2600
|
-
sourceTaskId: row.source_task_id,
|
|
2601
|
-
sourceUserId: row.source_user_id,
|
|
2602
|
-
title: row.title,
|
|
2603
|
-
summary: row.summary,
|
|
2604
|
-
groupId: row.group_id,
|
|
2605
|
-
visibility: row.visibility,
|
|
2606
|
-
createdAt: row.created_at,
|
|
2607
|
-
updatedAt: row.updated_at,
|
|
2608
|
-
};
|
|
2609
|
-
}
|
|
2610
|
-
function rowToHubChunk(row) {
|
|
2611
|
-
return {
|
|
2612
|
-
id: row.id,
|
|
2613
|
-
hubTaskId: row.hub_task_id,
|
|
2614
|
-
sourceChunkId: row.source_chunk_id,
|
|
2615
|
-
sourceUserId: row.source_user_id,
|
|
2616
|
-
role: row.role,
|
|
2617
|
-
content: row.content,
|
|
2618
|
-
summary: row.summary,
|
|
2619
|
-
kind: row.kind,
|
|
2620
|
-
createdAt: row.created_at,
|
|
2621
|
-
};
|
|
2622
|
-
}
|
|
2623
|
-
function rowToHubSkill(row) {
|
|
2624
|
-
return {
|
|
2625
|
-
id: row.id,
|
|
2626
|
-
sourceSkillId: row.source_skill_id,
|
|
2627
|
-
sourceUserId: row.source_user_id,
|
|
2628
|
-
name: row.name,
|
|
2629
|
-
description: row.description,
|
|
2630
|
-
version: row.version,
|
|
2631
|
-
groupId: row.group_id,
|
|
2632
|
-
visibility: row.visibility,
|
|
2633
|
-
bundle: row.bundle,
|
|
2634
|
-
qualityScore: row.quality_score,
|
|
2635
|
-
createdAt: row.created_at,
|
|
2636
|
-
updatedAt: row.updated_at,
|
|
2637
|
-
};
|
|
2638
|
-
}
|
|
2639
|
-
function rowToHubMemory(row) {
|
|
2640
|
-
return {
|
|
2641
|
-
id: row.id,
|
|
2642
|
-
sourceChunkId: row.source_chunk_id,
|
|
2643
|
-
sourceUserId: row.source_user_id,
|
|
2644
|
-
role: row.role,
|
|
2645
|
-
content: row.content,
|
|
2646
|
-
summary: row.summary,
|
|
2647
|
-
kind: row.kind,
|
|
2648
|
-
groupId: row.group_id,
|
|
2649
|
-
visibility: row.visibility,
|
|
2650
|
-
createdAt: row.created_at,
|
|
2651
|
-
updatedAt: row.updated_at,
|
|
2652
|
-
};
|
|
2653
|
-
}
|
|
2654
|
-
function contentHash(content) {
|
|
2655
|
-
return (0, crypto_1.createHash)("sha256").update(content).digest("hex").slice(0, 16);
|
|
2656
|
-
}
|
|
2657
|
-
//# sourceMappingURL=sqlite.js.map
|