@mastra/mysql 0.7.1-alpha.1 → 0.8.0-alpha.3
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/CHANGELOG.md +20 -0
- package/dist/index.cjs +1003 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1004 -35
- package/dist/index.js.map +1 -1
- package/dist/storage/db/schema-snapshot.d.ts +55 -0
- package/dist/storage/db/schema-snapshot.d.ts.map +1 -0
- package/dist/storage/domains/knowledge/index.d.ts +86 -0
- package/dist/storage/domains/knowledge/index.d.ts.map +1 -0
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/domains/operations/index.d.ts +16 -0
- package/dist/storage/domains/operations/index.d.ts.map +1 -1
- package/dist/storage/index.d.ts +3 -1
- package/dist/storage/index.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,10 +1,50 @@
|
|
|
1
1
|
import { ErrorCategory, ErrorDomain, MastraError } from "@mastra/core/error";
|
|
2
|
-
import { AGENTS_SCHEMA, AGENT_VERSIONS_SCHEMA, AgentsStorage, BackgroundTasksStorage, BlobStore, ChannelsStorage, DATASETS_SCHEMA, DATASET_ITEMS_SCHEMA, DATASET_VERSIONS_SCHEMA, DatasetsStorage, EXPERIMENTS_SCHEMA, EXPERIMENT_RESULTS_SCHEMA, ExperimentsStorage, FAVORITES_SCHEMA, FavoritesStorage, MCPClientsStorage, MCPServersStorage, MCP_CLIENTS_SCHEMA, MCP_CLIENT_VERSIONS_SCHEMA, MCP_SERVERS_SCHEMA, MCP_SERVER_VERSIONS_SCHEMA, MastraCompositeStore, MemoryStorage, OBSERVATIONAL_MEMORY_TABLE_SCHEMA, ObservabilityStorage, PROMPT_BLOCKS_SCHEMA, PROMPT_BLOCK_VERSIONS_SCHEMA, PromptBlocksStorage, SCORERS_SCHEMA, SCORER_DEFINITIONS_SCHEMA, SCORER_DEFINITION_VERSIONS_SCHEMA, SKILLS_SCHEMA, SKILL_BLOBS_SCHEMA, SKILL_VERSIONS_SCHEMA, SPAN_SCHEMA, SchedulesStorage, ScorerDefinitionsStorage, ScoresStorage, SkillsStorage, StoreOperations, TABLE_AGENTS, TABLE_AGENT_VERSIONS, TABLE_BACKGROUND_TASKS, TABLE_CHANNEL_CONFIG, TABLE_CHANNEL_INSTALLATIONS, TABLE_CONFIGS, TABLE_DATASETS, TABLE_DATASET_ITEMS, TABLE_DATASET_VERSIONS, TABLE_EXPERIMENTS, TABLE_EXPERIMENT_RESULTS, TABLE_FAVORITES, TABLE_MCP_CLIENTS, TABLE_MCP_CLIENT_VERSIONS, TABLE_MCP_SERVERS, TABLE_MCP_SERVER_VERSIONS, TABLE_MESSAGES, TABLE_PROMPT_BLOCKS, TABLE_PROMPT_BLOCK_VERSIONS, TABLE_RESOURCES, TABLE_SCHEDULES, TABLE_SCHEDULE_TRIGGERS, TABLE_SCHEMAS, TABLE_SCORERS, TABLE_SCORER_DEFINITIONS, TABLE_SCORER_DEFINITION_VERSIONS, TABLE_SKILLS, TABLE_SKILL_BLOBS, TABLE_SKILL_VERSIONS, TABLE_SPANS, TABLE_THREADS, TABLE_TOOL_PROVIDER_CONNECTIONS, TABLE_WORKFLOW_DEFINITIONS, TABLE_WORKFLOW_SNAPSHOT, TABLE_WORKSPACES, TABLE_WORKSPACE_VERSIONS, TOOL_PROVIDER_CONNECTIONS_SCHEMA, ToolProviderConnectionsStorage, TraceStatus, WORKFLOW_DEFINITIONS_SCHEMA, WORKSPACES_SCHEMA, WORKSPACE_VERSIONS_SCHEMA, WorkflowDefinitionsStorage, WorkflowsStorage, WorkspacesStorage, calculatePagination, createStorageErrorId, hasErrorCode, listTracesArgsSchema, normalizePerPage, normalizeScheduleTarget, toTraceSpans, validateStorageMetadataFilter } from "@mastra/core/storage";
|
|
2
|
+
import { AGENTS_SCHEMA, AGENT_VERSIONS_SCHEMA, AgentsStorage, BackgroundTasksStorage, BlobStore, ChannelsStorage, DATASETS_SCHEMA, DATASET_ITEMS_SCHEMA, DATASET_VERSIONS_SCHEMA, DatasetsStorage, EXPERIMENTS_SCHEMA, EXPERIMENT_RESULTS_SCHEMA, ExperimentsStorage, FAVORITES_SCHEMA, FavoritesStorage, KNOWLEDGE_ACTIVITY_SCHEMA, KNOWLEDGE_CURSORS_SCHEMA, KNOWLEDGE_MENTIONS_SCHEMA, KNOWLEDGE_NODES_SCHEMA, KNOWLEDGE_RECORDS_SCHEMA, KNOWLEDGE_SEMANTIC_OUTBOX_SCHEMA, KnowledgeConflictError, KnowledgeNotFoundError, KnowledgeStorage, MCPClientsStorage, MCPServersStorage, MCP_CLIENTS_SCHEMA, MCP_CLIENT_VERSIONS_SCHEMA, MCP_SERVERS_SCHEMA, MCP_SERVER_VERSIONS_SCHEMA, MastraCompositeStore, MemoryStorage, OBSERVATIONAL_MEMORY_TABLE_SCHEMA, ObservabilityStorage, PROMPT_BLOCKS_SCHEMA, PROMPT_BLOCK_VERSIONS_SCHEMA, PromptBlocksStorage, SCORERS_SCHEMA, SCORER_DEFINITIONS_SCHEMA, SCORER_DEFINITION_VERSIONS_SCHEMA, SKILLS_SCHEMA, SKILL_BLOBS_SCHEMA, SKILL_VERSIONS_SCHEMA, SPAN_SCHEMA, SchedulesStorage, ScorerDefinitionsStorage, ScoresStorage, SkillsStorage, StoreOperations, TABLE_AGENTS, TABLE_AGENT_VERSIONS, TABLE_BACKGROUND_TASKS, TABLE_CHANNEL_CONFIG, TABLE_CHANNEL_INSTALLATIONS, TABLE_CONFIGS, TABLE_DATASETS, TABLE_DATASET_ITEMS, TABLE_DATASET_VERSIONS, TABLE_EXPERIMENTS, TABLE_EXPERIMENT_RESULTS, TABLE_FAVORITES, TABLE_KNOWLEDGE_ACTIVITY, TABLE_KNOWLEDGE_CURSORS, TABLE_KNOWLEDGE_MENTIONS, TABLE_KNOWLEDGE_NODES, TABLE_KNOWLEDGE_RECORDS, TABLE_KNOWLEDGE_SEMANTIC_OUTBOX, TABLE_MCP_CLIENTS, TABLE_MCP_CLIENT_VERSIONS, TABLE_MCP_SERVERS, TABLE_MCP_SERVER_VERSIONS, TABLE_MESSAGES, TABLE_PROMPT_BLOCKS, TABLE_PROMPT_BLOCK_VERSIONS, TABLE_RESOURCES, TABLE_SCHEDULES, TABLE_SCHEDULE_TRIGGERS, TABLE_SCHEMAS, TABLE_SCORERS, TABLE_SCORER_DEFINITIONS, TABLE_SCORER_DEFINITION_VERSIONS, TABLE_SKILLS, TABLE_SKILL_BLOBS, TABLE_SKILL_VERSIONS, TABLE_SPANS, TABLE_THREADS, TABLE_TOOL_PROVIDER_CONNECTIONS, TABLE_WORKFLOW_DEFINITIONS, TABLE_WORKFLOW_SNAPSHOT, TABLE_WORKSPACES, TABLE_WORKSPACE_VERSIONS, TOOL_PROVIDER_CONNECTIONS_SCHEMA, ToolProviderConnectionsStorage, TraceStatus, WORKFLOW_DEFINITIONS_SCHEMA, WORKSPACES_SCHEMA, WORKSPACE_VERSIONS_SCHEMA, WorkflowDefinitionsStorage, WorkflowsStorage, WorkspacesStorage, assertKnowledgeCeilingRaised, assertKnowledgeScopeWithinCeiling, calculatePagination, canonicalizeKnowledgeScope, createKnowledgeUlid, createStorageErrorId, hasErrorCode, isKnowledgeScopeVisible, knowledgeScopeKey, knowledgeSemanticDocumentId, knowledgeSemanticIdempotencyKey, listTracesArgsSchema, normalizePerPage, normalizeScheduleTarget, parseKnowledgeNodeCursor, parseKnowledgeWikilinks, toTraceSpans, validateStorageMetadataFilter } from "@mastra/core/storage";
|
|
3
3
|
import { createPool } from "mysql2/promise";
|
|
4
4
|
import { parseSqlIdentifier } from "@mastra/core/utils";
|
|
5
5
|
import { randomUUID } from "crypto";
|
|
6
6
|
import { MessageList } from "@mastra/core/agent";
|
|
7
7
|
import { skillSnapshotFieldValuesEqual } from "@mastra/core/storage/domains/skills";
|
|
8
|
+
//#region src/storage/db/schema-snapshot.ts
|
|
9
|
+
/** Builds the `table.index` presence key the snapshot uses. */
|
|
10
|
+
function indexKey(table, index) {
|
|
11
|
+
return `${table.toLowerCase()}.${index.toLowerCase()}`;
|
|
12
|
+
}
|
|
13
|
+
const lower = (value) => String(value).toLowerCase();
|
|
14
|
+
/**
|
|
15
|
+
* Reads the catalog for `schemaName` in three queries. Returns null when no
|
|
16
|
+
* schema name is available (a pool with no default database): correctness over
|
|
17
|
+
* optimization, callers fall back to today's per-probe behavior.
|
|
18
|
+
*/
|
|
19
|
+
async function loadSchemaSnapshot(pool, schemaName) {
|
|
20
|
+
if (!schemaName) return null;
|
|
21
|
+
const [[tableRows], [columnRows], [indexRows]] = await Promise.all([
|
|
22
|
+
pool.execute(`SELECT table_name FROM information_schema.tables WHERE table_schema = ?`, [schemaName]),
|
|
23
|
+
pool.execute(`SELECT table_name, column_name FROM information_schema.columns WHERE table_schema = ?`, [schemaName]),
|
|
24
|
+
pool.execute(`SELECT DISTINCT table_name, index_name FROM information_schema.statistics WHERE table_schema = ?`, [schemaName])
|
|
25
|
+
]);
|
|
26
|
+
const tables = /* @__PURE__ */ new Set();
|
|
27
|
+
for (const row of tableRows ?? []) tables.add(lower(row.table_name ?? row.TABLE_NAME));
|
|
28
|
+
const columns = /* @__PURE__ */ new Map();
|
|
29
|
+
for (const row of columnRows ?? []) {
|
|
30
|
+
const table = lower(row.table_name ?? row.TABLE_NAME);
|
|
31
|
+
let set = columns.get(table);
|
|
32
|
+
if (!set) {
|
|
33
|
+
set = /* @__PURE__ */ new Set();
|
|
34
|
+
columns.set(table, set);
|
|
35
|
+
}
|
|
36
|
+
set.add(lower(row.column_name ?? row.COLUMN_NAME));
|
|
37
|
+
}
|
|
38
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
39
|
+
for (const row of indexRows ?? []) indexes.add(indexKey(lower(row.table_name ?? row.TABLE_NAME), lower(row.index_name ?? row.INDEX_NAME)));
|
|
40
|
+
return {
|
|
41
|
+
schemaName,
|
|
42
|
+
tables,
|
|
43
|
+
columns,
|
|
44
|
+
indexes
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
8
48
|
//#region src/storage/domains/utils.ts
|
|
9
49
|
function quoteIdentifier(value, context) {
|
|
10
50
|
return `\`${parseSqlIdentifier(value, context)}\``;
|
|
@@ -144,12 +184,38 @@ var StoreOperationsMySQL = class extends StoreOperations {
|
|
|
144
184
|
pool;
|
|
145
185
|
database;
|
|
146
186
|
resolvedDatabase;
|
|
187
|
+
/**
|
|
188
|
+
* Init-scoped catalog snapshot (see db/schema-snapshot.ts). Installed by
|
|
189
|
+
* MySQLStore.init() for exactly the init window and cleared in its finally;
|
|
190
|
+
* null at runtime so non-init callers keep probing the live catalog.
|
|
191
|
+
*/
|
|
192
|
+
schemaSnapshot = null;
|
|
147
193
|
constructor({ pool, database }) {
|
|
148
194
|
super();
|
|
149
195
|
this.pool = pool;
|
|
150
196
|
this.database = database;
|
|
151
197
|
this.resolvedDatabase = database ?? null;
|
|
152
198
|
}
|
|
199
|
+
/**
|
|
200
|
+
* Loads and installs the init-scoped snapshot. A null load (no default
|
|
201
|
+
* database) or a failed load leaves the snapshot uninstalled, so init
|
|
202
|
+
* proceeds with today's per-probe behavior: correctness over optimization.
|
|
203
|
+
*/
|
|
204
|
+
async loadInitSchemaSnapshot() {
|
|
205
|
+
try {
|
|
206
|
+
this.schemaSnapshot = await loadSchemaSnapshot(this.pool, await this.getDatabase());
|
|
207
|
+
} catch (error) {
|
|
208
|
+
console.warn("Failed to load init schema snapshot, falling back to per-object probing:", error);
|
|
209
|
+
this.schemaSnapshot = null;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
clearInitSchemaSnapshot() {
|
|
213
|
+
this.schemaSnapshot = null;
|
|
214
|
+
}
|
|
215
|
+
/** Read by sibling domains (memory's raw index DDL) so snapshot state lives in exactly one place. */
|
|
216
|
+
getInitSchemaSnapshot() {
|
|
217
|
+
return this.schemaSnapshot;
|
|
218
|
+
}
|
|
153
219
|
getPool() {
|
|
154
220
|
return this.pool;
|
|
155
221
|
}
|
|
@@ -165,6 +231,8 @@ var StoreOperationsMySQL = class extends StoreOperations {
|
|
|
165
231
|
return this.resolvedDatabase ?? void 0;
|
|
166
232
|
}
|
|
167
233
|
async hasColumn(table, column) {
|
|
234
|
+
const snapshotColumns = this.schemaSnapshot?.columns.get(table.toLowerCase());
|
|
235
|
+
if (snapshotColumns) return snapshotColumns.has(column.toLowerCase());
|
|
168
236
|
const db = await this.getDatabase();
|
|
169
237
|
const params = [table, column];
|
|
170
238
|
let sql = "SELECT COUNT(*) as count FROM information_schema.columns WHERE table_name = ? AND (column_name = ? OR column_name = ? )";
|
|
@@ -242,13 +310,23 @@ var StoreOperationsMySQL = class extends StoreOperations {
|
|
|
242
310
|
return false;
|
|
243
311
|
}
|
|
244
312
|
async createTable({ tableName, schema }) {
|
|
313
|
+
const snapshot = this.schemaSnapshot;
|
|
314
|
+
if (snapshot?.tables.has(tableName.toLowerCase())) return;
|
|
245
315
|
const connection = await this.pool.getConnection();
|
|
246
316
|
try {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
317
|
+
if (!snapshot) {
|
|
318
|
+
const db = await this.getDatabase();
|
|
319
|
+
const [t_rows] = await connection.query("SELECT COUNT(*) AS count FROM information_schema.tables WHERE table_schema = ? AND table_name = ?", [db ?? "", tableName]);
|
|
320
|
+
if (Array.isArray(t_rows) && t_rows.length > 0 && t_rows[0].count > 0) return;
|
|
321
|
+
}
|
|
250
322
|
const sql = this.getCreateTableSQL(tableName, schema);
|
|
251
|
-
await connection.execute(sql);
|
|
323
|
+
const [result] = await connection.execute(sql);
|
|
324
|
+
const created = result?.warningStatus === 0;
|
|
325
|
+
if (snapshot && created) {
|
|
326
|
+
const table = tableName.toLowerCase();
|
|
327
|
+
snapshot.tables.add(table);
|
|
328
|
+
snapshot.columns.set(table, new Set(Object.keys(schema).map((column) => column.toLowerCase())));
|
|
329
|
+
}
|
|
252
330
|
} catch (error) {
|
|
253
331
|
throw new MastraError({
|
|
254
332
|
id: "MYSQL_STORE_CREATE_TABLE_FAILED",
|
|
@@ -304,17 +382,22 @@ var StoreOperationsMySQL = class extends StoreOperations {
|
|
|
304
382
|
const tableName = formatTableName(table, this.database);
|
|
305
383
|
const indexName = quoteIdentifier(name, "index name");
|
|
306
384
|
try {
|
|
307
|
-
const
|
|
308
|
-
|
|
385
|
+
const snapshot = this.schemaSnapshot;
|
|
386
|
+
if (snapshot) {
|
|
387
|
+
if (snapshot.indexes.has(indexKey(table, name))) return;
|
|
388
|
+
} else {
|
|
389
|
+
const db = await this.getDatabase() ?? "";
|
|
390
|
+
const [existing] = await this.pool.execute(`SELECT 1 FROM information_schema.STATISTICS
|
|
309
391
|
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND INDEX_NAME = ?
|
|
310
392
|
LIMIT 1`, [
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
393
|
+
db,
|
|
394
|
+
table,
|
|
395
|
+
name
|
|
396
|
+
]);
|
|
397
|
+
if (existing.length > 0) return;
|
|
398
|
+
}
|
|
316
399
|
const [columnMeta] = await this.pool.execute(`SELECT COLUMN_NAME, DATA_TYPE FROM information_schema.COLUMNS
|
|
317
|
-
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?`, [
|
|
400
|
+
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?`, [await this.getDatabase() ?? "", table]);
|
|
318
401
|
const dataTypeByColumn = new Map(columnMeta.map((row) => [String(row.COLUMN_NAME).toLowerCase(), String(row.DATA_TYPE).toLowerCase()]));
|
|
319
402
|
const PREFIX_TYPES = /* @__PURE__ */ new Set([
|
|
320
403
|
"tinytext",
|
|
@@ -340,6 +423,7 @@ var StoreOperationsMySQL = class extends StoreOperations {
|
|
|
340
423
|
}).join(", ");
|
|
341
424
|
const sql = `CREATE ${unique ? "UNIQUE " : ""}INDEX ${indexName} ON ${tableName} (${columnsStr})`;
|
|
342
425
|
await this.pool.execute(sql);
|
|
426
|
+
this.schemaSnapshot?.indexes.add(indexKey(table, name));
|
|
343
427
|
} catch (error) {
|
|
344
428
|
console.warn(`Failed to create index ${name}:`, error);
|
|
345
429
|
}
|
|
@@ -571,17 +655,22 @@ var StoreOperationsMySQL = class extends StoreOperations {
|
|
|
571
655
|
}
|
|
572
656
|
async alterTable({ tableName, schema, ifNotExists }) {
|
|
573
657
|
if (!ifNotExists.length) return;
|
|
574
|
-
const
|
|
575
|
-
|
|
576
|
-
if (
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
params
|
|
658
|
+
const snapshotColumns = this.schemaSnapshot?.columns.get(tableName.toLowerCase());
|
|
659
|
+
let existing;
|
|
660
|
+
if (snapshotColumns) existing = snapshotColumns;
|
|
661
|
+
else {
|
|
662
|
+
const db = await this.getDatabase();
|
|
663
|
+
const [tableRows] = await this.pool.execute("SELECT COUNT(*) AS count FROM information_schema.tables WHERE table_schema = ? AND table_name = ?", [db ?? "", tableName]);
|
|
664
|
+
if (!(Array.isArray(tableRows) && tableRows.length > 0 && tableRows[0].count > 0)) return;
|
|
665
|
+
const params = [tableName];
|
|
666
|
+
let sql = "SELECT column_name FROM information_schema.columns WHERE table_name = ?";
|
|
667
|
+
if (db) {
|
|
668
|
+
sql += " AND table_schema = ?";
|
|
669
|
+
params.push(db);
|
|
670
|
+
}
|
|
671
|
+
const [rows] = await this.pool.execute(sql, params);
|
|
672
|
+
existing = new Set((rows || []).map((row) => String(row.column_name ?? row.COLUMN_NAME).toLowerCase()));
|
|
582
673
|
}
|
|
583
|
-
const [rows] = await this.pool.execute(sql, params);
|
|
584
|
-
const existing = new Set((rows || []).map((row) => String(row.column_name ?? row.COLUMN_NAME).toLowerCase()));
|
|
585
674
|
for (const columnName of ifNotExists) {
|
|
586
675
|
if (existing.has(columnName.toLowerCase())) continue;
|
|
587
676
|
const column = schema[columnName];
|
|
@@ -603,6 +692,7 @@ var StoreOperationsMySQL = class extends StoreOperations {
|
|
|
603
692
|
const alterSql = `ALTER TABLE ${formatTableName(tableName, this.database)} ADD COLUMN ${parts.join(" ")}`;
|
|
604
693
|
try {
|
|
605
694
|
await this.pool.execute(alterSql);
|
|
695
|
+
existing.add(columnName.toLowerCase());
|
|
606
696
|
} catch (error) {
|
|
607
697
|
if (error?.code === "ER_DUP_FIELDNAME") continue;
|
|
608
698
|
throw new MastraError({
|
|
@@ -640,7 +730,7 @@ function generateTableSQL({ tableName, schema, compositePrimaryKey }) {
|
|
|
640
730
|
const constraints = [];
|
|
641
731
|
if (def.primaryKey && !compositePrimaryKey?.includes(name)) constraints.push("PRIMARY KEY");
|
|
642
732
|
if (!def.nullable) constraints.push("NOT NULL");
|
|
643
|
-
return `${colName} ${mapToMySqlType(def.type)} ${constraints.join(" ")}`;
|
|
733
|
+
return `${colName} ${mapToMySqlType(def.type, Boolean(def.primaryKey || compositePrimaryKey?.includes(name)))} ${constraints.join(" ")}`;
|
|
644
734
|
});
|
|
645
735
|
const tableConstraints = [];
|
|
646
736
|
if (compositePrimaryKey) {
|
|
@@ -664,9 +754,9 @@ function generateIndexSQL(options) {
|
|
|
664
754
|
return quoteIdentifier(col, "column name");
|
|
665
755
|
}).join(", ")});`;
|
|
666
756
|
}
|
|
667
|
-
function mapToMySqlType(type) {
|
|
757
|
+
function mapToMySqlType(type, isKey = false) {
|
|
668
758
|
switch (type) {
|
|
669
|
-
case "text": return "TEXT";
|
|
759
|
+
case "text": return isKey ? "VARCHAR(191)" : "TEXT";
|
|
670
760
|
case "timestamp": return "DATETIME(3)";
|
|
671
761
|
case "bigint": return "BIGINT";
|
|
672
762
|
case "integer": return "INT";
|
|
@@ -1281,7 +1371,7 @@ function serializeJson(v) {
|
|
|
1281
1371
|
if (typeof v === "object" && v != null) return JSON.stringify(v);
|
|
1282
1372
|
return v ?? null;
|
|
1283
1373
|
}
|
|
1284
|
-
function parseJson$
|
|
1374
|
+
function parseJson$2(val) {
|
|
1285
1375
|
if (val == null) return void 0;
|
|
1286
1376
|
if (typeof val === "string") try {
|
|
1287
1377
|
return JSON.parse(val);
|
|
@@ -1296,14 +1386,14 @@ function rowToTask(row) {
|
|
|
1296
1386
|
status: String(row.status),
|
|
1297
1387
|
toolName: String(row.tool_name),
|
|
1298
1388
|
toolCallId: String(row.tool_call_id),
|
|
1299
|
-
args: parseJson$
|
|
1389
|
+
args: parseJson$2(row.args) ?? {},
|
|
1300
1390
|
agentId: String(row.agent_id),
|
|
1301
1391
|
threadId: row.thread_id != null ? String(row.thread_id) : void 0,
|
|
1302
1392
|
resourceId: row.resource_id != null ? String(row.resource_id) : void 0,
|
|
1303
1393
|
runId: String(row.run_id),
|
|
1304
|
-
result: parseJson$
|
|
1305
|
-
error: parseJson$
|
|
1306
|
-
suspendPayload: parseJson$
|
|
1394
|
+
result: parseJson$2(row.result),
|
|
1395
|
+
error: parseJson$2(row.error),
|
|
1396
|
+
suspendPayload: parseJson$2(row.suspend_payload),
|
|
1307
1397
|
retryCount: Number(row.retry_count),
|
|
1308
1398
|
maxRetries: Number(row.max_retries),
|
|
1309
1399
|
timeoutMs: Number(row.timeout_ms),
|
|
@@ -3731,6 +3821,871 @@ var FavoritesMySQL = class FavoritesMySQL extends FavoritesStorage {
|
|
|
3731
3821
|
}
|
|
3732
3822
|
};
|
|
3733
3823
|
//#endregion
|
|
3824
|
+
//#region src/storage/domains/knowledge/index.ts
|
|
3825
|
+
function mysqlSql(sql) {
|
|
3826
|
+
return sql.replaceAll("jsonb(?)", "CAST(? AS JSON)").replaceAll("INSERT OR IGNORE", "INSERT IGNORE").replaceAll("\"", "`");
|
|
3827
|
+
}
|
|
3828
|
+
function createExecutor(client) {
|
|
3829
|
+
return { async execute(statement) {
|
|
3830
|
+
const sql = typeof statement === "string" ? statement : statement.sql;
|
|
3831
|
+
const args = (typeof statement === "string" ? [] : statement.args ?? []).map((value) => typeof value === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value) ? value.replace("T", " ").replace("Z", "") : value);
|
|
3832
|
+
const [result] = await client.query(mysqlSql(sql), args);
|
|
3833
|
+
if (Array.isArray(result)) return {
|
|
3834
|
+
rows: result,
|
|
3835
|
+
rowsAffected: 0
|
|
3836
|
+
};
|
|
3837
|
+
return {
|
|
3838
|
+
rows: [],
|
|
3839
|
+
rowsAffected: result.affectedRows
|
|
3840
|
+
};
|
|
3841
|
+
} };
|
|
3842
|
+
}
|
|
3843
|
+
const visibleSql = `(scopeKey = ? OR LEFT(?, CHAR_LENGTH(scopeKey) + 1) = CONCAT(scopeKey, char(31)))`;
|
|
3844
|
+
function parseJson$1(value) {
|
|
3845
|
+
if (typeof value === "string") return JSON.parse(value);
|
|
3846
|
+
if (value instanceof Uint8Array) return JSON.parse(new TextDecoder().decode(value));
|
|
3847
|
+
if (value instanceof ArrayBuffer) return JSON.parse(new TextDecoder().decode(value));
|
|
3848
|
+
return value;
|
|
3849
|
+
}
|
|
3850
|
+
function toDate(value) {
|
|
3851
|
+
return value instanceof Date ? new Date(value) : new Date(String(value));
|
|
3852
|
+
}
|
|
3853
|
+
function optionalDate(value) {
|
|
3854
|
+
return value == null ? void 0 : toDate(value);
|
|
3855
|
+
}
|
|
3856
|
+
function databaseTimestamp(value) {
|
|
3857
|
+
const pad = (part, width = 2) => String(part).padStart(width, "0");
|
|
3858
|
+
return `${value.getFullYear()}-${pad(value.getMonth() + 1)}-${pad(value.getDate())} ${pad(value.getHours())}:${pad(value.getMinutes())}:${pad(value.getSeconds())}.${pad(value.getMilliseconds(), 3)}`;
|
|
3859
|
+
}
|
|
3860
|
+
function canonicalName(name) {
|
|
3861
|
+
return name.trim().toLocaleLowerCase();
|
|
3862
|
+
}
|
|
3863
|
+
function nodeReferenceId(node) {
|
|
3864
|
+
return typeof node === "string" ? node : node.id;
|
|
3865
|
+
}
|
|
3866
|
+
function escapeLikePattern(value) {
|
|
3867
|
+
return value.replaceAll("=", "==").replaceAll("%", "=%").replaceAll("_", "=_");
|
|
3868
|
+
}
|
|
3869
|
+
function parseNode(row) {
|
|
3870
|
+
return {
|
|
3871
|
+
id: String(row.id),
|
|
3872
|
+
type: "node",
|
|
3873
|
+
name: String(row.name),
|
|
3874
|
+
kind: String(row.kind),
|
|
3875
|
+
content: row.content == null ? void 0 : String(row.content),
|
|
3876
|
+
scope: parseJson$1(row.scopeJson ?? row.scope),
|
|
3877
|
+
version: Number(row.version),
|
|
3878
|
+
mergedInto: row.mergedInto == null ? void 0 : String(row.mergedInto),
|
|
3879
|
+
createdAt: toDate(row.createdAt),
|
|
3880
|
+
updatedAt: toDate(row.updatedAt)
|
|
3881
|
+
};
|
|
3882
|
+
}
|
|
3883
|
+
function parseKnowledge(row) {
|
|
3884
|
+
return {
|
|
3885
|
+
id: String(row.id),
|
|
3886
|
+
node: String(row.node),
|
|
3887
|
+
text: String(row.text),
|
|
3888
|
+
scope: parseJson$1(row.scopeJson ?? row.scope),
|
|
3889
|
+
sourceThreadId: String(row.sourceThreadId),
|
|
3890
|
+
capturedAt: toDate(row.capturedAt),
|
|
3891
|
+
when: optionalDate(row.when),
|
|
3892
|
+
maxScope: row.maxScope == null ? void 0 : String(row.maxScope),
|
|
3893
|
+
metadata: row.metadata == null ? void 0 : parseJson$1(row.metadata),
|
|
3894
|
+
deletedAt: optionalDate(row.deletedAt),
|
|
3895
|
+
deletedBy: row.deletedBy == null ? void 0 : String(row.deletedBy)
|
|
3896
|
+
};
|
|
3897
|
+
}
|
|
3898
|
+
function parseOutbox(row) {
|
|
3899
|
+
return {
|
|
3900
|
+
id: String(row.id),
|
|
3901
|
+
idempotencyKey: String(row.idempotencyKey),
|
|
3902
|
+
documentId: String(row.documentId),
|
|
3903
|
+
documentType: String(row.documentType),
|
|
3904
|
+
operation: String(row.operation),
|
|
3905
|
+
scope: parseJson$1(row.scopeJson ?? row.scope),
|
|
3906
|
+
status: String(row.status),
|
|
3907
|
+
attempts: Number(row.attempts),
|
|
3908
|
+
availableAt: toDate(row.availableAt),
|
|
3909
|
+
claimedAt: optionalDate(row.claimedAt),
|
|
3910
|
+
claimedBy: row.claimedBy == null ? void 0 : String(row.claimedBy),
|
|
3911
|
+
createdAt: toDate(row.createdAt),
|
|
3912
|
+
completedAt: optionalDate(row.completedAt)
|
|
3913
|
+
};
|
|
3914
|
+
}
|
|
3915
|
+
const KNOWLEDGE_INDEX_DDL = [
|
|
3916
|
+
`CREATE UNIQUE INDEX idx_knowledge_nodes_identity ON "${TABLE_KNOWLEDGE_NODES}" (type(32), scopeKey(255), canonicalName(255))`,
|
|
3917
|
+
`CREATE INDEX idx_knowledge_nodes_scope ON "${TABLE_KNOWLEDGE_NODES}" (scopeKey(255), type(32))`,
|
|
3918
|
+
`CREATE INDEX idx_knowledge_records_node_latest ON "${TABLE_KNOWLEDGE_RECORDS}" (node(191), id(26) DESC)`,
|
|
3919
|
+
`CREATE INDEX idx_knowledge_records_thread_latest ON "${TABLE_KNOWLEDGE_RECORDS}" (sourceThreadId(191), id(26) DESC)`,
|
|
3920
|
+
`CREATE INDEX idx_knowledge_mentions_record ON "${TABLE_KNOWLEDGE_MENTIONS}" (recordId(191), sourceType(32), sourceId(191))`,
|
|
3921
|
+
`CREATE INDEX idx_knowledge_activity_latest ON "${TABLE_KNOWLEDGE_ACTIVITY}" (id(26) DESC)`,
|
|
3922
|
+
`CREATE UNIQUE INDEX idx_knowledge_outbox_idempotency ON "${TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}" (idempotencyKey(255))`,
|
|
3923
|
+
`CREATE INDEX idx_knowledge_outbox_claim ON "${TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}" (status(32), availableAt, createdAt)`
|
|
3924
|
+
];
|
|
3925
|
+
var KnowledgeMySQL = class extends KnowledgeStorage {
|
|
3926
|
+
static getExportDDL() {
|
|
3927
|
+
return [
|
|
3928
|
+
generateTableSQL({
|
|
3929
|
+
tableName: TABLE_KNOWLEDGE_NODES,
|
|
3930
|
+
schema: KNOWLEDGE_NODES_SCHEMA
|
|
3931
|
+
}),
|
|
3932
|
+
generateTableSQL({
|
|
3933
|
+
tableName: TABLE_KNOWLEDGE_RECORDS,
|
|
3934
|
+
schema: KNOWLEDGE_RECORDS_SCHEMA
|
|
3935
|
+
}),
|
|
3936
|
+
generateTableSQL({
|
|
3937
|
+
tableName: TABLE_KNOWLEDGE_MENTIONS,
|
|
3938
|
+
schema: KNOWLEDGE_MENTIONS_SCHEMA,
|
|
3939
|
+
compositePrimaryKey: [
|
|
3940
|
+
"sourceType",
|
|
3941
|
+
"sourceId",
|
|
3942
|
+
"recordId"
|
|
3943
|
+
]
|
|
3944
|
+
}),
|
|
3945
|
+
generateTableSQL({
|
|
3946
|
+
tableName: TABLE_KNOWLEDGE_CURSORS,
|
|
3947
|
+
schema: KNOWLEDGE_CURSORS_SCHEMA,
|
|
3948
|
+
compositePrimaryKey: ["sourceThreadId", "agent"]
|
|
3949
|
+
}),
|
|
3950
|
+
generateTableSQL({
|
|
3951
|
+
tableName: TABLE_KNOWLEDGE_ACTIVITY,
|
|
3952
|
+
schema: KNOWLEDGE_ACTIVITY_SCHEMA
|
|
3953
|
+
}),
|
|
3954
|
+
generateTableSQL({
|
|
3955
|
+
tableName: TABLE_KNOWLEDGE_SEMANTIC_OUTBOX,
|
|
3956
|
+
schema: KNOWLEDGE_SEMANTIC_OUTBOX_SCHEMA
|
|
3957
|
+
}),
|
|
3958
|
+
...KNOWLEDGE_INDEX_DDL.map(mysqlSql)
|
|
3959
|
+
];
|
|
3960
|
+
}
|
|
3961
|
+
#pool;
|
|
3962
|
+
#client;
|
|
3963
|
+
#operations;
|
|
3964
|
+
constructor({ pool, operations }) {
|
|
3965
|
+
super();
|
|
3966
|
+
this.#pool = pool;
|
|
3967
|
+
this.#client = createExecutor(pool);
|
|
3968
|
+
this.#operations = operations;
|
|
3969
|
+
}
|
|
3970
|
+
async init() {
|
|
3971
|
+
await this.#operations.createTable({
|
|
3972
|
+
tableName: TABLE_KNOWLEDGE_NODES,
|
|
3973
|
+
schema: KNOWLEDGE_NODES_SCHEMA
|
|
3974
|
+
});
|
|
3975
|
+
await this.#operations.createTable({
|
|
3976
|
+
tableName: TABLE_KNOWLEDGE_RECORDS,
|
|
3977
|
+
schema: KNOWLEDGE_RECORDS_SCHEMA
|
|
3978
|
+
});
|
|
3979
|
+
await this.#operations.createTable({
|
|
3980
|
+
tableName: TABLE_KNOWLEDGE_MENTIONS,
|
|
3981
|
+
schema: KNOWLEDGE_MENTIONS_SCHEMA
|
|
3982
|
+
});
|
|
3983
|
+
await this.#operations.createTable({
|
|
3984
|
+
tableName: TABLE_KNOWLEDGE_CURSORS,
|
|
3985
|
+
schema: KNOWLEDGE_CURSORS_SCHEMA
|
|
3986
|
+
});
|
|
3987
|
+
await this.#operations.createTable({
|
|
3988
|
+
tableName: TABLE_KNOWLEDGE_ACTIVITY,
|
|
3989
|
+
schema: KNOWLEDGE_ACTIVITY_SCHEMA
|
|
3990
|
+
});
|
|
3991
|
+
await this.#operations.createTable({
|
|
3992
|
+
tableName: TABLE_KNOWLEDGE_SEMANTIC_OUTBOX,
|
|
3993
|
+
schema: KNOWLEDGE_SEMANTIC_OUTBOX_SCHEMA
|
|
3994
|
+
});
|
|
3995
|
+
for (const sql of KNOWLEDGE_INDEX_DDL) try {
|
|
3996
|
+
await this.#client.execute(sql);
|
|
3997
|
+
} catch (error) {
|
|
3998
|
+
if (error.code !== "ER_DUP_KEYNAME") throw error;
|
|
3999
|
+
}
|
|
4000
|
+
}
|
|
4001
|
+
async dangerouslyClearAll() {
|
|
4002
|
+
await this.#transaction(async (tx) => {
|
|
4003
|
+
for (const table of [
|
|
4004
|
+
TABLE_KNOWLEDGE_MENTIONS,
|
|
4005
|
+
TABLE_KNOWLEDGE_RECORDS,
|
|
4006
|
+
TABLE_KNOWLEDGE_NODES,
|
|
4007
|
+
TABLE_KNOWLEDGE_CURSORS,
|
|
4008
|
+
TABLE_KNOWLEDGE_ACTIVITY,
|
|
4009
|
+
TABLE_KNOWLEDGE_SEMANTIC_OUTBOX
|
|
4010
|
+
]) await tx.execute(`DELETE FROM "${table}"`);
|
|
4011
|
+
});
|
|
4012
|
+
}
|
|
4013
|
+
async createNode(input) {
|
|
4014
|
+
const scope = canonicalizeKnowledgeScope(input.scope);
|
|
4015
|
+
return this.#transaction(async (tx) => {
|
|
4016
|
+
const existing = await this.#getNodeByName(tx, input.name, scope);
|
|
4017
|
+
if (existing) {
|
|
4018
|
+
const terminal = await this.#resolveTerminalNode(tx, existing.id);
|
|
4019
|
+
if (!isKnowledgeScopeVisible(terminal.scope, scope)) throw new Error(`Merged knowledge node is not visible from scope: ${input.name}`);
|
|
4020
|
+
return terminal;
|
|
4021
|
+
}
|
|
4022
|
+
const now = /* @__PURE__ */ new Date();
|
|
4023
|
+
const node = {
|
|
4024
|
+
id: input.id ?? crypto.randomUUID(),
|
|
4025
|
+
type: "node",
|
|
4026
|
+
name: input.name.trim(),
|
|
4027
|
+
kind: input.kind,
|
|
4028
|
+
content: input.content,
|
|
4029
|
+
scope,
|
|
4030
|
+
version: 1,
|
|
4031
|
+
createdAt: now,
|
|
4032
|
+
updatedAt: now
|
|
4033
|
+
};
|
|
4034
|
+
await tx.execute({
|
|
4035
|
+
sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODES}" (id,type,name,canonicalName,kind,content,scope,scopeKey,version,mergedInto,createdAt,updatedAt) VALUES (?,?,?,?,?,?,jsonb(?),?,?,NULL,?,?)`,
|
|
4036
|
+
args: [
|
|
4037
|
+
node.id,
|
|
4038
|
+
"node",
|
|
4039
|
+
node.name,
|
|
4040
|
+
canonicalName(node.name),
|
|
4041
|
+
node.kind,
|
|
4042
|
+
node.content ?? null,
|
|
4043
|
+
JSON.stringify(scope),
|
|
4044
|
+
knowledgeScopeKey(scope),
|
|
4045
|
+
node.version,
|
|
4046
|
+
now.toISOString(),
|
|
4047
|
+
now.toISOString()
|
|
4048
|
+
]
|
|
4049
|
+
});
|
|
4050
|
+
await this.#replaceMentions(tx, "node", node.id, node.content ?? "", input.resolutionScope ?? scope, scope);
|
|
4051
|
+
await this.#activity(tx, "node-created", "node", node.id, scope);
|
|
4052
|
+
await this.#outbox(tx, "node", node.id, "upsert", node.version, scope);
|
|
4053
|
+
return node;
|
|
4054
|
+
});
|
|
4055
|
+
}
|
|
4056
|
+
async getNode(id) {
|
|
4057
|
+
return this.#getNode(this.#client, id);
|
|
4058
|
+
}
|
|
4059
|
+
async getNodeByName(input) {
|
|
4060
|
+
return this.#getNodeByName(this.#client, input.name, canonicalizeKnowledgeScope(input.scope));
|
|
4061
|
+
}
|
|
4062
|
+
async resolveNode(input) {
|
|
4063
|
+
return this.#resolveNode(this.#client, input.name, canonicalizeKnowledgeScope(input.scope));
|
|
4064
|
+
}
|
|
4065
|
+
async listNodes(input) {
|
|
4066
|
+
const key = knowledgeScopeKey(canonicalizeKnowledgeScope(input.scope));
|
|
4067
|
+
const clauses = [
|
|
4068
|
+
`type = 'node'`,
|
|
4069
|
+
"mergedInto IS NULL",
|
|
4070
|
+
visibleSql
|
|
4071
|
+
];
|
|
4072
|
+
const args = [key, key];
|
|
4073
|
+
if (input.namePrefix) {
|
|
4074
|
+
clauses.push("canonicalName LIKE ? ESCAPE '='");
|
|
4075
|
+
args.push(`${escapeLikePattern(canonicalName(input.namePrefix))}%`);
|
|
4076
|
+
}
|
|
4077
|
+
if (input.kind) {
|
|
4078
|
+
clauses.push("kind = ?");
|
|
4079
|
+
args.push(input.kind);
|
|
4080
|
+
}
|
|
4081
|
+
if (input.hasContent !== void 0) clauses.push(input.hasContent ? "content IS NOT NULL AND content <> ''" : "(content IS NULL OR content = '')");
|
|
4082
|
+
if (input.cursor) {
|
|
4083
|
+
const cursor = parseKnowledgeNodeCursor(input.cursor, {
|
|
4084
|
+
namePrefix: input.namePrefix,
|
|
4085
|
+
kind: input.kind,
|
|
4086
|
+
hasContent: input.hasContent
|
|
4087
|
+
});
|
|
4088
|
+
const updatedAt = databaseTimestamp(cursor.updatedAt);
|
|
4089
|
+
clauses.push("(updatedAt < ? OR (updatedAt = ? AND (name > ? OR (name = ? AND id > ?))))");
|
|
4090
|
+
args.push(updatedAt, updatedAt, cursor.name, cursor.name, cursor.id);
|
|
4091
|
+
}
|
|
4092
|
+
args.push(input.limit ?? 100);
|
|
4093
|
+
return (await this.#client.execute({
|
|
4094
|
+
sql: `SELECT *, scope AS scopeJson FROM "${TABLE_KNOWLEDGE_NODES}" WHERE ${clauses.join(" AND ")} ORDER BY updatedAt DESC, name ASC, id ASC LIMIT ?`,
|
|
4095
|
+
args
|
|
4096
|
+
})).rows.map(parseNode);
|
|
4097
|
+
}
|
|
4098
|
+
async updateNode(input) {
|
|
4099
|
+
return this.#transaction(async (tx) => {
|
|
4100
|
+
const existing = await this.#getNode(tx, input.id);
|
|
4101
|
+
if (!existing) throw new KnowledgeNotFoundError("node", input.id);
|
|
4102
|
+
if (existing.mergedInto) throw new Error(`Cannot update merged knowledge node: ${input.id}`);
|
|
4103
|
+
const scope = canonicalizeKnowledgeScope(input.scope ?? existing.scope);
|
|
4104
|
+
const name = (input.name ?? existing.name).trim();
|
|
4105
|
+
const content = input.content ?? existing.content;
|
|
4106
|
+
const now = /* @__PURE__ */ new Date();
|
|
4107
|
+
if ((await tx.execute({
|
|
4108
|
+
sql: `UPDATE "${TABLE_KNOWLEDGE_NODES}" SET name=?,canonicalName=?,kind=?,content=?,scope=jsonb(?),scopeKey=?,version=version+1,updatedAt=? WHERE id=? AND type='node' AND version=?`,
|
|
4109
|
+
args: [
|
|
4110
|
+
name,
|
|
4111
|
+
canonicalName(name),
|
|
4112
|
+
input.kind ?? existing.kind,
|
|
4113
|
+
content ?? null,
|
|
4114
|
+
JSON.stringify(scope),
|
|
4115
|
+
knowledgeScopeKey(scope),
|
|
4116
|
+
now.toISOString(),
|
|
4117
|
+
input.id,
|
|
4118
|
+
input.version
|
|
4119
|
+
]
|
|
4120
|
+
})).rowsAffected === 0) throw new KnowledgeConflictError(input.id);
|
|
4121
|
+
if (input.content !== void 0 || input.name !== void 0 || input.scope !== void 0) await this.#replaceMentions(tx, "node", input.id, content ?? "", input.resolutionScope ?? scope, scope);
|
|
4122
|
+
await this.#activity(tx, "node-updated", "node", input.id, scope);
|
|
4123
|
+
if (knowledgeScopeKey(existing.scope) !== knowledgeScopeKey(scope)) {
|
|
4124
|
+
await this.#outbox(tx, "node", input.id, "delete", createKnowledgeUlid(), existing.scope);
|
|
4125
|
+
const records = await tx.execute({
|
|
4126
|
+
sql: `SELECT id,scope AS scopeJson,deletedAt FROM "${TABLE_KNOWLEDGE_RECORDS}" WHERE node=?`,
|
|
4127
|
+
args: [input.id]
|
|
4128
|
+
});
|
|
4129
|
+
for (const row of records.rows) {
|
|
4130
|
+
const factScope = parseJson$1(row.scopeJson);
|
|
4131
|
+
await this.#outbox(tx, "record", String(row.id), "delete", createKnowledgeUlid(), factScope);
|
|
4132
|
+
if (row.deletedAt == null) await this.#outbox(tx, "record", String(row.id), "upsert", createKnowledgeUlid(), factScope);
|
|
4133
|
+
}
|
|
4134
|
+
}
|
|
4135
|
+
await this.#outbox(tx, "node", input.id, "upsert", input.version + 1, scope);
|
|
4136
|
+
return {
|
|
4137
|
+
...existing,
|
|
4138
|
+
name,
|
|
4139
|
+
kind: input.kind ?? existing.kind,
|
|
4140
|
+
content,
|
|
4141
|
+
scope,
|
|
4142
|
+
version: input.version + 1,
|
|
4143
|
+
updatedAt: now
|
|
4144
|
+
};
|
|
4145
|
+
});
|
|
4146
|
+
}
|
|
4147
|
+
async mergeNodes(input) {
|
|
4148
|
+
if (input.sourceId === input.targetId) throw new Error("Cannot merge a knowledge node into itself");
|
|
4149
|
+
return this.#transaction(async (tx) => {
|
|
4150
|
+
const source = await this.#getNode(tx, input.sourceId);
|
|
4151
|
+
if (!source) throw new KnowledgeNotFoundError("node", input.sourceId);
|
|
4152
|
+
const target = await this.#resolveTerminalNode(tx, input.targetId);
|
|
4153
|
+
if (!target) throw new KnowledgeNotFoundError("node", input.targetId);
|
|
4154
|
+
if (target.id === source.id) throw new Error("Cannot create a knowledge merge cycle");
|
|
4155
|
+
if (!isKnowledgeScopeVisible(target.scope, source.scope)) throw new Error("Cannot merge a knowledge node into a target that is narrower than its source scope");
|
|
4156
|
+
const affected = await tx.execute({
|
|
4157
|
+
sql: `SELECT DISTINCT m.sourceType,m.sourceId,COALESCE(f.scope,r.scope) AS scopeJson,CASE WHEN f.deletedAt IS NULL THEN 0 ELSE 1 END AS deleted FROM "${TABLE_KNOWLEDGE_MENTIONS}" m LEFT JOIN "${TABLE_KNOWLEDGE_RECORDS}" f ON m.sourceType='record' AND f.id=m.sourceId LEFT JOIN "${TABLE_KNOWLEDGE_NODES}" r ON m.sourceType='node' AND r.id=m.sourceId WHERE m.recordId=?`,
|
|
4158
|
+
args: [source.id]
|
|
4159
|
+
});
|
|
4160
|
+
const movedFacts = await tx.execute({
|
|
4161
|
+
sql: `SELECT id,scope AS scopeJson,deletedAt FROM "${TABLE_KNOWLEDGE_RECORDS}" WHERE node=?`,
|
|
4162
|
+
args: [source.id]
|
|
4163
|
+
});
|
|
4164
|
+
if ((await tx.execute({
|
|
4165
|
+
sql: `UPDATE "${TABLE_KNOWLEDGE_NODES}" SET mergedInto=?,version=version+1,updatedAt=? WHERE id=? AND type='node' AND version=? AND mergedInto IS NULL`,
|
|
4166
|
+
args: [
|
|
4167
|
+
target.id,
|
|
4168
|
+
(/* @__PURE__ */ new Date()).toISOString(),
|
|
4169
|
+
source.id,
|
|
4170
|
+
input.sourceVersion
|
|
4171
|
+
]
|
|
4172
|
+
})).rowsAffected === 0) throw new KnowledgeConflictError(source.id);
|
|
4173
|
+
await tx.execute({
|
|
4174
|
+
sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET node=? WHERE node=?`,
|
|
4175
|
+
args: [target.id, source.id]
|
|
4176
|
+
});
|
|
4177
|
+
await tx.execute({
|
|
4178
|
+
sql: `DELETE source FROM "${TABLE_KNOWLEDGE_MENTIONS}" source JOIN "${TABLE_KNOWLEDGE_MENTIONS}" target ON target.sourceType=source.sourceType AND target.sourceId=source.sourceId WHERE source.recordId=? AND target.recordId=?`,
|
|
4179
|
+
args: [source.id, target.id]
|
|
4180
|
+
});
|
|
4181
|
+
await tx.execute({
|
|
4182
|
+
sql: `UPDATE "${TABLE_KNOWLEDGE_MENTIONS}" SET recordId=? WHERE recordId=?`,
|
|
4183
|
+
args: [target.id, source.id]
|
|
4184
|
+
});
|
|
4185
|
+
for (const row of movedFacts.rows) await this.#outbox(tx, "record", String(row.id), row.deletedAt == null ? "upsert" : "delete", createKnowledgeUlid(), parseJson$1(row.scopeJson));
|
|
4186
|
+
for (const row of affected.rows) await this.#outbox(tx, String(row.sourceType), String(row.sourceId), Number(row.deleted) ? "delete" : "upsert", createKnowledgeUlid(), parseJson$1(row.scopeJson));
|
|
4187
|
+
await this.#activity(tx, "node-merged", "node", source.id, source.scope);
|
|
4188
|
+
await this.#outbox(tx, "node", source.id, "delete", input.sourceVersion + 1, source.scope);
|
|
4189
|
+
await this.#outbox(tx, "node", target.id, "upsert", createKnowledgeUlid(), target.scope);
|
|
4190
|
+
return target;
|
|
4191
|
+
});
|
|
4192
|
+
}
|
|
4193
|
+
async appendKnowledge(input) {
|
|
4194
|
+
const scope = canonicalizeKnowledgeScope(input.scope);
|
|
4195
|
+
const resolutionScope = canonicalizeKnowledgeScope(input.resolutionScope);
|
|
4196
|
+
const defaultScope = canonicalizeKnowledgeScope(input.defaultScope);
|
|
4197
|
+
assertKnowledgeScopeWithinCeiling(scope, input.maxScope);
|
|
4198
|
+
return this.#transaction(async (tx) => {
|
|
4199
|
+
const parent = await this.#resolveTerminalNode(tx, nodeReferenceId(input.node));
|
|
4200
|
+
if (!parent) throw new KnowledgeNotFoundError("node", nodeReferenceId(input.node));
|
|
4201
|
+
const record = {
|
|
4202
|
+
id: input.id ?? createKnowledgeUlid(),
|
|
4203
|
+
node: parent.id,
|
|
4204
|
+
text: input.text,
|
|
4205
|
+
scope,
|
|
4206
|
+
sourceThreadId: input.sourceThreadId,
|
|
4207
|
+
capturedAt: /* @__PURE__ */ new Date(),
|
|
4208
|
+
when: input.when ? new Date(input.when) : void 0,
|
|
4209
|
+
maxScope: input.maxScope,
|
|
4210
|
+
metadata: input.metadata
|
|
4211
|
+
};
|
|
4212
|
+
await tx.execute({
|
|
4213
|
+
sql: `INSERT INTO "${TABLE_KNOWLEDGE_RECORDS}" (id,node,text,scope,scopeKey,sourceThreadId,capturedAt,"when",maxScope,metadata,deletedAt,deletedBy) VALUES (?,?,?,jsonb(?),?,?,?,?,?,jsonb(?),NULL,NULL)`,
|
|
4214
|
+
args: [
|
|
4215
|
+
record.id,
|
|
4216
|
+
record.node,
|
|
4217
|
+
record.text,
|
|
4218
|
+
JSON.stringify(scope),
|
|
4219
|
+
knowledgeScopeKey(scope),
|
|
4220
|
+
record.sourceThreadId,
|
|
4221
|
+
record.capturedAt.toISOString(),
|
|
4222
|
+
record.when?.toISOString() ?? null,
|
|
4223
|
+
record.maxScope ?? null,
|
|
4224
|
+
record.metadata ? JSON.stringify(record.metadata) : null
|
|
4225
|
+
]
|
|
4226
|
+
});
|
|
4227
|
+
await this.#replaceMentions(tx, "record", record.id, record.text, resolutionScope, defaultScope);
|
|
4228
|
+
await this.#activity(tx, "record-created", "record", record.id, scope, record.sourceThreadId);
|
|
4229
|
+
await this.#outbox(tx, "record", record.id, "upsert", record.id, scope);
|
|
4230
|
+
return record;
|
|
4231
|
+
});
|
|
4232
|
+
}
|
|
4233
|
+
async getKnowledge(input) {
|
|
4234
|
+
const result = await this.#client.execute({
|
|
4235
|
+
sql: `SELECT *,scope AS scopeJson FROM "${TABLE_KNOWLEDGE_RECORDS}" WHERE id=?${input.includeDeleted ? "" : " AND deletedAt IS NULL"}`,
|
|
4236
|
+
args: [input.id]
|
|
4237
|
+
});
|
|
4238
|
+
return result.rows[0] ? parseKnowledge(result.rows[0]) : null;
|
|
4239
|
+
}
|
|
4240
|
+
async listKnowledgeAbout(input) {
|
|
4241
|
+
return this.#queryKnowledge(input, "about");
|
|
4242
|
+
}
|
|
4243
|
+
async listKnowledgeMentioning(input) {
|
|
4244
|
+
return this.#queryKnowledge(input, "mentioning");
|
|
4245
|
+
}
|
|
4246
|
+
async listKnowledgeRelatedTo(input) {
|
|
4247
|
+
return this.#queryKnowledge(input, "related");
|
|
4248
|
+
}
|
|
4249
|
+
async knowledgeBySource(input) {
|
|
4250
|
+
const key = knowledgeScopeKey(canonicalizeKnowledgeScope(input.scope));
|
|
4251
|
+
const args = [
|
|
4252
|
+
input.sourceThreadId,
|
|
4253
|
+
key,
|
|
4254
|
+
key
|
|
4255
|
+
];
|
|
4256
|
+
if (input.after) args.push(input.after);
|
|
4257
|
+
const limit = input.limit ?? 100;
|
|
4258
|
+
args.push(limit + 1);
|
|
4259
|
+
const records = (await this.#client.execute({
|
|
4260
|
+
sql: `SELECT *,scope AS scopeJson FROM "${TABLE_KNOWLEDGE_RECORDS}" WHERE sourceThreadId=? AND ${visibleSql}${input.includeDeleted ? "" : " AND deletedAt IS NULL"}${input.after ? " AND id > ?" : ""} ORDER BY id ASC LIMIT ?`,
|
|
4261
|
+
args
|
|
4262
|
+
})).rows.map(parseKnowledge);
|
|
4263
|
+
return {
|
|
4264
|
+
records: records.slice(0, limit),
|
|
4265
|
+
nextCursor: records.length > limit ? records[limit - 1]?.id : void 0
|
|
4266
|
+
};
|
|
4267
|
+
}
|
|
4268
|
+
async removeKnowledge(input) {
|
|
4269
|
+
return this.#transaction(async (tx) => {
|
|
4270
|
+
const record = await this.#getKnowledge(tx, input.id, true);
|
|
4271
|
+
if (!record) throw new KnowledgeNotFoundError("record", input.id);
|
|
4272
|
+
if (record.deletedAt) return record;
|
|
4273
|
+
const deletedAt = /* @__PURE__ */ new Date();
|
|
4274
|
+
await tx.execute({
|
|
4275
|
+
sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET deletedAt=?,deletedBy=? WHERE id=? AND deletedAt IS NULL`,
|
|
4276
|
+
args: [
|
|
4277
|
+
deletedAt.toISOString(),
|
|
4278
|
+
input.deletedBy,
|
|
4279
|
+
input.id
|
|
4280
|
+
]
|
|
4281
|
+
});
|
|
4282
|
+
await this.#activity(tx, "record-deleted", "record", input.id, record.scope, record.sourceThreadId);
|
|
4283
|
+
await this.#outbox(tx, "record", input.id, "delete", deletedAt.toISOString(), record.scope);
|
|
4284
|
+
return {
|
|
4285
|
+
...record,
|
|
4286
|
+
deletedAt,
|
|
4287
|
+
deletedBy: input.deletedBy
|
|
4288
|
+
};
|
|
4289
|
+
});
|
|
4290
|
+
}
|
|
4291
|
+
async restoreKnowledge(input) {
|
|
4292
|
+
return this.#transaction(async (tx) => {
|
|
4293
|
+
const record = await this.#getKnowledge(tx, input.id, true);
|
|
4294
|
+
if (!record) throw new KnowledgeNotFoundError("record", input.id);
|
|
4295
|
+
if (!record.deletedAt) return record;
|
|
4296
|
+
await tx.execute({
|
|
4297
|
+
sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET deletedAt=NULL,deletedBy=NULL WHERE id=?`,
|
|
4298
|
+
args: [input.id]
|
|
4299
|
+
});
|
|
4300
|
+
await this.#activity(tx, "record-restored", "record", input.id, record.scope, record.sourceThreadId);
|
|
4301
|
+
await this.#outbox(tx, "record", input.id, "upsert", createKnowledgeUlid(), record.scope);
|
|
4302
|
+
return {
|
|
4303
|
+
...record,
|
|
4304
|
+
deletedAt: void 0,
|
|
4305
|
+
deletedBy: void 0
|
|
4306
|
+
};
|
|
4307
|
+
});
|
|
4308
|
+
}
|
|
4309
|
+
async rescopeKnowledge(input) {
|
|
4310
|
+
const scope = canonicalizeKnowledgeScope(input.scope);
|
|
4311
|
+
return this.#transaction(async (tx) => {
|
|
4312
|
+
const record = await this.#getKnowledge(tx, input.id, true);
|
|
4313
|
+
if (!record) throw new KnowledgeNotFoundError("record", input.id);
|
|
4314
|
+
assertKnowledgeScopeWithinCeiling(scope, record.maxScope);
|
|
4315
|
+
await tx.execute({
|
|
4316
|
+
sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET scope=jsonb(?),scopeKey=? WHERE id=?`,
|
|
4317
|
+
args: [
|
|
4318
|
+
JSON.stringify(scope),
|
|
4319
|
+
knowledgeScopeKey(scope),
|
|
4320
|
+
input.id
|
|
4321
|
+
]
|
|
4322
|
+
});
|
|
4323
|
+
await this.#activity(tx, "record-rescoped", "record", input.id, scope, record.sourceThreadId);
|
|
4324
|
+
if (knowledgeScopeKey(record.scope) !== knowledgeScopeKey(scope)) await this.#outbox(tx, "record", input.id, "delete", createKnowledgeUlid(), record.scope);
|
|
4325
|
+
if (!record.deletedAt) await this.#outbox(tx, "record", input.id, "upsert", createKnowledgeUlid(), scope);
|
|
4326
|
+
return {
|
|
4327
|
+
...record,
|
|
4328
|
+
scope
|
|
4329
|
+
};
|
|
4330
|
+
});
|
|
4331
|
+
}
|
|
4332
|
+
async raiseKnowledgeCeiling(input) {
|
|
4333
|
+
return this.#transaction(async (tx) => {
|
|
4334
|
+
const record = await this.#getKnowledge(tx, input.id, true);
|
|
4335
|
+
if (!record) throw new KnowledgeNotFoundError("record", input.id);
|
|
4336
|
+
assertKnowledgeScopeWithinCeiling(record.scope, input.maxScope);
|
|
4337
|
+
assertKnowledgeCeilingRaised(record.maxScope, input.maxScope);
|
|
4338
|
+
await tx.execute({
|
|
4339
|
+
sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET maxScope=? WHERE id=?`,
|
|
4340
|
+
args: [input.maxScope ?? null, input.id]
|
|
4341
|
+
});
|
|
4342
|
+
return {
|
|
4343
|
+
...record,
|
|
4344
|
+
maxScope: input.maxScope
|
|
4345
|
+
};
|
|
4346
|
+
});
|
|
4347
|
+
}
|
|
4348
|
+
async search(input) {
|
|
4349
|
+
const scope = canonicalizeKnowledgeScope(input.scope);
|
|
4350
|
+
const key = knowledgeScopeKey(scope);
|
|
4351
|
+
const normalizedQuery = input.query.trim().toLocaleLowerCase();
|
|
4352
|
+
if (!normalizedQuery) return [];
|
|
4353
|
+
const query = `%${escapeLikePattern(normalizedQuery)}%`;
|
|
4354
|
+
const results = (await this.#client.execute({
|
|
4355
|
+
sql: `SELECT *,scope AS scopeJson FROM "${TABLE_KNOWLEDGE_NODES}" WHERE mergedInto IS NULL AND ${visibleSql} AND (canonicalName LIKE ? ESCAPE '=' OR lower(COALESCE(kind,'')) LIKE ? ESCAPE '=' OR lower(COALESCE(content,'')) LIKE ? ESCAPE '=') ORDER BY updatedAt DESC LIMIT ?`,
|
|
4356
|
+
args: [
|
|
4357
|
+
key,
|
|
4358
|
+
key,
|
|
4359
|
+
query,
|
|
4360
|
+
query,
|
|
4361
|
+
query,
|
|
4362
|
+
input.limit ?? 20
|
|
4363
|
+
]
|
|
4364
|
+
})).rows.map((row) => ({
|
|
4365
|
+
type: String(row.type),
|
|
4366
|
+
id: String(row.id),
|
|
4367
|
+
recordId: String(row.id),
|
|
4368
|
+
name: String(row.name),
|
|
4369
|
+
text: row.content ? `${String(row.name)}\n${String(row.content)}` : String(row.name),
|
|
4370
|
+
scope: parseJson$1(row.scopeJson)
|
|
4371
|
+
}));
|
|
4372
|
+
if (results.length < (input.limit ?? 20)) {
|
|
4373
|
+
const records = await this.#client.execute({
|
|
4374
|
+
sql: `SELECT f.*,f.scope AS scopeJson,r.name,r.scope AS parentScopeJson FROM "${TABLE_KNOWLEDGE_RECORDS}" f JOIN "${TABLE_KNOWLEDGE_NODES}" r ON r.id=f.node AND r.type='node' AND r.mergedInto IS NULL WHERE f.deletedAt IS NULL AND ${visibleSql.replaceAll("scopeKey", "f.scopeKey")} AND lower(f.text) LIKE ? ESCAPE '=' ORDER BY f.id DESC LIMIT ?`,
|
|
4375
|
+
args: [
|
|
4376
|
+
key,
|
|
4377
|
+
key,
|
|
4378
|
+
query,
|
|
4379
|
+
(input.limit ?? 20) - results.length
|
|
4380
|
+
]
|
|
4381
|
+
});
|
|
4382
|
+
results.push(...records.rows.map((row) => {
|
|
4383
|
+
const parentVisible = isKnowledgeScopeVisible(parseJson$1(row.parentScopeJson), scope);
|
|
4384
|
+
return {
|
|
4385
|
+
type: "record",
|
|
4386
|
+
id: String(row.id),
|
|
4387
|
+
recordId: String(row.node),
|
|
4388
|
+
name: parentVisible ? String(row.name) : "(private node)",
|
|
4389
|
+
text: String(row.text),
|
|
4390
|
+
scope: parseJson$1(row.scopeJson)
|
|
4391
|
+
};
|
|
4392
|
+
}));
|
|
4393
|
+
}
|
|
4394
|
+
return results;
|
|
4395
|
+
}
|
|
4396
|
+
async getCurationCursor(input) {
|
|
4397
|
+
const row = (await this.#client.execute({
|
|
4398
|
+
sql: `SELECT * FROM "${TABLE_KNOWLEDGE_CURSORS}" WHERE sourceThreadId=? AND agent=?`,
|
|
4399
|
+
args: [input.sourceThreadId, input.agent]
|
|
4400
|
+
})).rows[0];
|
|
4401
|
+
return row ? {
|
|
4402
|
+
sourceThreadId: String(row.sourceThreadId),
|
|
4403
|
+
agent: String(row.agent),
|
|
4404
|
+
lastKnowledgeId: String(row.lastKnowledgeId),
|
|
4405
|
+
updatedAt: toDate(row.updatedAt)
|
|
4406
|
+
} : null;
|
|
4407
|
+
}
|
|
4408
|
+
async advanceCurationCursor(input) {
|
|
4409
|
+
const updatedAt = /* @__PURE__ */ new Date();
|
|
4410
|
+
if ((await this.#client.execute({
|
|
4411
|
+
sql: `INSERT INTO "${TABLE_KNOWLEDGE_CURSORS}" (sourceThreadId,agent,lastKnowledgeId,updatedAt) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE lastKnowledgeId=IF(VALUES(lastKnowledgeId) >= lastKnowledgeId, VALUES(lastKnowledgeId), lastKnowledgeId),updatedAt=IF(VALUES(lastKnowledgeId) >= lastKnowledgeId, VALUES(updatedAt), updatedAt)`,
|
|
4412
|
+
args: [
|
|
4413
|
+
input.sourceThreadId,
|
|
4414
|
+
input.agent,
|
|
4415
|
+
input.lastKnowledgeId,
|
|
4416
|
+
updatedAt.toISOString()
|
|
4417
|
+
]
|
|
4418
|
+
})).rowsAffected === 0) throw new Error("Knowledge curation cursor cannot move backwards");
|
|
4419
|
+
return {
|
|
4420
|
+
...input,
|
|
4421
|
+
updatedAt
|
|
4422
|
+
};
|
|
4423
|
+
}
|
|
4424
|
+
async listActivity(input) {
|
|
4425
|
+
const key = knowledgeScopeKey(canonicalizeKnowledgeScope(input.scope));
|
|
4426
|
+
return (await this.#client.execute({
|
|
4427
|
+
sql: `SELECT *,scope AS scopeJson FROM "${TABLE_KNOWLEDGE_ACTIVITY}" WHERE ${visibleSql}${input.after ? " AND id < ?" : ""} ORDER BY id DESC LIMIT ?`,
|
|
4428
|
+
args: [
|
|
4429
|
+
key,
|
|
4430
|
+
key,
|
|
4431
|
+
...input.after ? [input.after] : [],
|
|
4432
|
+
input.limit ?? 100
|
|
4433
|
+
]
|
|
4434
|
+
})).rows.map((row) => ({
|
|
4435
|
+
id: String(row.id),
|
|
4436
|
+
action: String(row.action),
|
|
4437
|
+
recordType: String(row.recordType),
|
|
4438
|
+
recordId: String(row.recordId),
|
|
4439
|
+
scope: parseJson$1(row.scopeJson),
|
|
4440
|
+
sourceThreadId: row.sourceThreadId == null ? void 0 : String(row.sourceThreadId),
|
|
4441
|
+
createdAt: toDate(row.createdAt)
|
|
4442
|
+
}));
|
|
4443
|
+
}
|
|
4444
|
+
async listSemanticOutbox(input = {}) {
|
|
4445
|
+
const clauses = [];
|
|
4446
|
+
const args = [];
|
|
4447
|
+
if (input.status) {
|
|
4448
|
+
clauses.push("status=?");
|
|
4449
|
+
args.push(input.status);
|
|
4450
|
+
}
|
|
4451
|
+
if (input.scope) {
|
|
4452
|
+
const key = knowledgeScopeKey(canonicalizeKnowledgeScope(input.scope));
|
|
4453
|
+
clauses.push(visibleSql);
|
|
4454
|
+
args.push(key, key);
|
|
4455
|
+
}
|
|
4456
|
+
args.push(input.limit ?? 100);
|
|
4457
|
+
return (await this.#client.execute({
|
|
4458
|
+
sql: `SELECT *,scope AS scopeJson FROM "${TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}"${clauses.length ? ` WHERE ${clauses.join(" AND ")}` : ""} ORDER BY createdAt ASC,id ASC LIMIT ?`,
|
|
4459
|
+
args
|
|
4460
|
+
})).rows.map(parseOutbox);
|
|
4461
|
+
}
|
|
4462
|
+
async claimSemanticOutbox(input) {
|
|
4463
|
+
const now = input.now ?? /* @__PURE__ */ new Date();
|
|
4464
|
+
const stale = new Date(now.getTime() - (input.claimTimeoutMs ?? 6e4));
|
|
4465
|
+
return this.#transaction(async (tx) => {
|
|
4466
|
+
const clauses = [
|
|
4467
|
+
`availableAt <= ?`,
|
|
4468
|
+
`(status='pending' OR (status='processing' AND claimedAt <= ?))`,
|
|
4469
|
+
`NOT EXISTS (SELECT 1 FROM "${TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}" AS earlier WHERE earlier.documentId = "${TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}".documentId AND earlier.status != 'completed' AND (earlier.createdAt < "${TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}".createdAt OR (earlier.createdAt = "${TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}".createdAt AND earlier.id < "${TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}".id)))`
|
|
4470
|
+
];
|
|
4471
|
+
const args = [now.toISOString(), stale.toISOString()];
|
|
4472
|
+
if (input.scope) {
|
|
4473
|
+
const key = knowledgeScopeKey(canonicalizeKnowledgeScope(input.scope));
|
|
4474
|
+
clauses.push(visibleSql);
|
|
4475
|
+
args.push(key, key);
|
|
4476
|
+
}
|
|
4477
|
+
args.push(input.limit ?? 100);
|
|
4478
|
+
const ids = (await tx.execute({
|
|
4479
|
+
sql: `SELECT id FROM "${TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}" WHERE ${clauses.join(" AND ")} ORDER BY createdAt ASC,id ASC LIMIT ? FOR UPDATE SKIP LOCKED`,
|
|
4480
|
+
args
|
|
4481
|
+
})).rows.map((row) => String(row.id));
|
|
4482
|
+
for (const id of ids) await tx.execute({
|
|
4483
|
+
sql: `UPDATE "${TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}" SET status='processing',attempts=attempts+1,claimedAt=?,claimedBy=? WHERE id=?`,
|
|
4484
|
+
args: [
|
|
4485
|
+
now.toISOString(),
|
|
4486
|
+
input.workerId,
|
|
4487
|
+
id
|
|
4488
|
+
]
|
|
4489
|
+
});
|
|
4490
|
+
if (!ids.length) return [];
|
|
4491
|
+
return (await tx.execute({
|
|
4492
|
+
sql: `SELECT *,scope AS scopeJson FROM "${TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}" WHERE id IN (${ids.map(() => "?").join(",")}) ORDER BY createdAt ASC,id ASC`,
|
|
4493
|
+
args: ids
|
|
4494
|
+
})).rows.map(parseOutbox);
|
|
4495
|
+
});
|
|
4496
|
+
}
|
|
4497
|
+
async completeSemanticOutbox(input) {
|
|
4498
|
+
if (!input.ids.length) return;
|
|
4499
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4500
|
+
await this.#transaction(async (tx) => {
|
|
4501
|
+
for (const id of input.ids) await tx.execute({
|
|
4502
|
+
sql: `UPDATE "${TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}" SET status='completed',completedAt=? WHERE id=? AND status='processing' AND claimedBy=?`,
|
|
4503
|
+
args: [
|
|
4504
|
+
now,
|
|
4505
|
+
id,
|
|
4506
|
+
input.workerId
|
|
4507
|
+
]
|
|
4508
|
+
});
|
|
4509
|
+
});
|
|
4510
|
+
}
|
|
4511
|
+
async releaseSemanticOutbox(input) {
|
|
4512
|
+
if (!input.ids.length) return;
|
|
4513
|
+
await this.#transaction(async (tx) => {
|
|
4514
|
+
for (const id of input.ids) await tx.execute({
|
|
4515
|
+
sql: `UPDATE "${TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}" SET status='pending',availableAt=?,claimedAt=NULL,claimedBy=NULL WHERE id=? AND status='processing' AND claimedBy=?`,
|
|
4516
|
+
args: [
|
|
4517
|
+
(input.retryAt ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
4518
|
+
id,
|
|
4519
|
+
input.workerId
|
|
4520
|
+
]
|
|
4521
|
+
});
|
|
4522
|
+
});
|
|
4523
|
+
}
|
|
4524
|
+
async #transaction(operation) {
|
|
4525
|
+
const connection = await this.#pool.getConnection();
|
|
4526
|
+
try {
|
|
4527
|
+
await connection.beginTransaction();
|
|
4528
|
+
const result = await operation(createExecutor(connection));
|
|
4529
|
+
await connection.commit();
|
|
4530
|
+
return result;
|
|
4531
|
+
} catch (error) {
|
|
4532
|
+
await connection.rollback();
|
|
4533
|
+
throw error;
|
|
4534
|
+
} finally {
|
|
4535
|
+
connection.release();
|
|
4536
|
+
}
|
|
4537
|
+
}
|
|
4538
|
+
async #getNode(executor, id) {
|
|
4539
|
+
const result = await executor.execute({
|
|
4540
|
+
sql: `SELECT *,scope AS scopeJson FROM "${TABLE_KNOWLEDGE_NODES}" WHERE id=? AND type='node'`,
|
|
4541
|
+
args: [id]
|
|
4542
|
+
});
|
|
4543
|
+
return result.rows[0] ? parseNode(result.rows[0]) : null;
|
|
4544
|
+
}
|
|
4545
|
+
async #getNodeByName(executor, name, scope) {
|
|
4546
|
+
const result = await executor.execute({
|
|
4547
|
+
sql: `SELECT *,scope AS scopeJson FROM "${TABLE_KNOWLEDGE_NODES}" WHERE type='node' AND scopeKey=? AND canonicalName=?`,
|
|
4548
|
+
args: [knowledgeScopeKey(scope), canonicalName(name)]
|
|
4549
|
+
});
|
|
4550
|
+
return result.rows[0] ? parseNode(result.rows[0]) : null;
|
|
4551
|
+
}
|
|
4552
|
+
async #resolveNode(executor, name, scope) {
|
|
4553
|
+
for (let length = scope.length; length > 0; length--) {
|
|
4554
|
+
const node = await this.#getNodeByName(executor, name, scope.slice(0, length));
|
|
4555
|
+
if (node) {
|
|
4556
|
+
const terminal = await this.#resolveTerminalNode(executor, node.id);
|
|
4557
|
+
if (terminal && isKnowledgeScopeVisible(terminal.scope, scope)) return terminal;
|
|
4558
|
+
}
|
|
4559
|
+
}
|
|
4560
|
+
return null;
|
|
4561
|
+
}
|
|
4562
|
+
async #resolveTerminalNode(executor, id) {
|
|
4563
|
+
let node = await this.#getNode(executor, id);
|
|
4564
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4565
|
+
while (node?.mergedInto) {
|
|
4566
|
+
if (seen.has(node.id)) throw new Error(`Knowledge merge cycle detected at ${node.id}`);
|
|
4567
|
+
seen.add(node.id);
|
|
4568
|
+
node = await this.#getNode(executor, node.mergedInto);
|
|
4569
|
+
}
|
|
4570
|
+
return node;
|
|
4571
|
+
}
|
|
4572
|
+
async #getKnowledge(executor, id, includeDeleted) {
|
|
4573
|
+
const result = await executor.execute({
|
|
4574
|
+
sql: `SELECT *,scope AS scopeJson FROM "${TABLE_KNOWLEDGE_RECORDS}" WHERE id=?${includeDeleted ? "" : " AND deletedAt IS NULL"}`,
|
|
4575
|
+
args: [id]
|
|
4576
|
+
});
|
|
4577
|
+
return result.rows[0] ? parseKnowledge(result.rows[0]) : null;
|
|
4578
|
+
}
|
|
4579
|
+
async #queryKnowledge(input, relationship) {
|
|
4580
|
+
const scope = canonicalizeKnowledgeScope(input.scope);
|
|
4581
|
+
const node = await this.#resolveTerminalNode(this.#client, nodeReferenceId(input.node));
|
|
4582
|
+
if (!node) return { records: [] };
|
|
4583
|
+
const key = knowledgeScopeKey(scope);
|
|
4584
|
+
const args = [
|
|
4585
|
+
node.id,
|
|
4586
|
+
...relationship === "related" ? [node.id] : [],
|
|
4587
|
+
key,
|
|
4588
|
+
key
|
|
4589
|
+
];
|
|
4590
|
+
if (input.after) args.push(input.after);
|
|
4591
|
+
args.push((input.limit ?? 100) + 1);
|
|
4592
|
+
const records = (await this.#client.execute({
|
|
4593
|
+
sql: `SELECT DISTINCT f.*,f.scope AS scopeJson FROM "${TABLE_KNOWLEDGE_RECORDS}" f${relationship === "about" ? "" : ` LEFT JOIN "${TABLE_KNOWLEDGE_MENTIONS}" m ON m.sourceType='record' AND m.sourceId=f.id`} WHERE ${relationship === "about" ? "f.node=?" : relationship === "mentioning" ? "m.recordId=?" : "(f.node=? OR m.recordId=?)"} AND ${visibleSql.replaceAll("scopeKey", "f.scopeKey")}${input.includeDeleted ? "" : " AND f.deletedAt IS NULL"}${input.after ? " AND f.id < ?" : ""} ORDER BY f.id DESC LIMIT ?`,
|
|
4594
|
+
args
|
|
4595
|
+
})).rows.map(parseKnowledge);
|
|
4596
|
+
const limit = input.limit ?? 100;
|
|
4597
|
+
return {
|
|
4598
|
+
records: records.slice(0, limit),
|
|
4599
|
+
nextCursor: records.length > limit ? records[limit - 1]?.id : void 0
|
|
4600
|
+
};
|
|
4601
|
+
}
|
|
4602
|
+
async #replaceMentions(tx, sourceType, sourceId, text, resolutionScope, defaultScope) {
|
|
4603
|
+
await tx.execute({
|
|
4604
|
+
sql: `DELETE FROM "${TABLE_KNOWLEDGE_MENTIONS}" WHERE sourceType=? AND sourceId=?`,
|
|
4605
|
+
args: [sourceType, sourceId]
|
|
4606
|
+
});
|
|
4607
|
+
for (const name of parseKnowledgeWikilinks(text)) {
|
|
4608
|
+
let node = await this.#resolveNode(tx, name, resolutionScope);
|
|
4609
|
+
if (!node) {
|
|
4610
|
+
node = await this.#getNodeByName(tx, name, defaultScope);
|
|
4611
|
+
if (node) node = await this.#resolveTerminalNode(tx, node.id);
|
|
4612
|
+
if (!node) {
|
|
4613
|
+
const now = /* @__PURE__ */ new Date();
|
|
4614
|
+
node = {
|
|
4615
|
+
id: crypto.randomUUID(),
|
|
4616
|
+
type: "node",
|
|
4617
|
+
name,
|
|
4618
|
+
kind: "node",
|
|
4619
|
+
scope: defaultScope,
|
|
4620
|
+
version: 1,
|
|
4621
|
+
createdAt: now,
|
|
4622
|
+
updatedAt: now
|
|
4623
|
+
};
|
|
4624
|
+
await tx.execute({
|
|
4625
|
+
sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODES}" (id,type,name,canonicalName,kind,content,scope,scopeKey,version,mergedInto,createdAt,updatedAt) VALUES (?,?,?,?,?,NULL,jsonb(?),?,?,NULL,?,?)`,
|
|
4626
|
+
args: [
|
|
4627
|
+
node.id,
|
|
4628
|
+
"node",
|
|
4629
|
+
node.name,
|
|
4630
|
+
canonicalName(node.name),
|
|
4631
|
+
node.kind,
|
|
4632
|
+
JSON.stringify(defaultScope),
|
|
4633
|
+
knowledgeScopeKey(defaultScope),
|
|
4634
|
+
1,
|
|
4635
|
+
now.toISOString()
|
|
4636
|
+
]
|
|
4637
|
+
});
|
|
4638
|
+
await this.#activity(tx, "node-created", "node", node.id, defaultScope);
|
|
4639
|
+
await this.#outbox(tx, "node", node.id, "upsert", 1, defaultScope);
|
|
4640
|
+
}
|
|
4641
|
+
}
|
|
4642
|
+
await tx.execute({
|
|
4643
|
+
sql: `INSERT OR IGNORE INTO "${TABLE_KNOWLEDGE_MENTIONS}" (sourceType,sourceId,recordId) VALUES (?,?,?)`,
|
|
4644
|
+
args: [
|
|
4645
|
+
sourceType,
|
|
4646
|
+
sourceId,
|
|
4647
|
+
node.id
|
|
4648
|
+
]
|
|
4649
|
+
});
|
|
4650
|
+
}
|
|
4651
|
+
}
|
|
4652
|
+
async #activity(executor, action, recordType, recordId, scope, sourceThreadId) {
|
|
4653
|
+
const now = /* @__PURE__ */ new Date();
|
|
4654
|
+
await executor.execute({
|
|
4655
|
+
sql: `INSERT INTO "${TABLE_KNOWLEDGE_ACTIVITY}" (id,action,recordType,recordId,scope,scopeKey,sourceThreadId,createdAt) VALUES (?,?,?,?,jsonb(?),?,?,?)`,
|
|
4656
|
+
args: [
|
|
4657
|
+
createKnowledgeUlid(),
|
|
4658
|
+
action,
|
|
4659
|
+
recordType,
|
|
4660
|
+
recordId,
|
|
4661
|
+
JSON.stringify(scope),
|
|
4662
|
+
knowledgeScopeKey(scope),
|
|
4663
|
+
sourceThreadId ?? null,
|
|
4664
|
+
now.toISOString()
|
|
4665
|
+
]
|
|
4666
|
+
});
|
|
4667
|
+
}
|
|
4668
|
+
async #outbox(executor, documentType, id, operation, version, scope) {
|
|
4669
|
+
const documentId = knowledgeSemanticDocumentId(documentType, id);
|
|
4670
|
+
const idempotencyKey = knowledgeSemanticIdempotencyKey(documentId, operation, version);
|
|
4671
|
+
const now = /* @__PURE__ */ new Date();
|
|
4672
|
+
await executor.execute({
|
|
4673
|
+
sql: `INSERT OR IGNORE INTO "${TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}" (id,idempotencyKey,documentId,documentType,operation,scope,scopeKey,status,attempts,availableAt,claimedAt,claimedBy,createdAt,completedAt) VALUES (?,?,?,?,?,jsonb(?),?,'pending',0,?,NULL,NULL,?,NULL)`,
|
|
4674
|
+
args: [
|
|
4675
|
+
createKnowledgeUlid(),
|
|
4676
|
+
idempotencyKey,
|
|
4677
|
+
documentId,
|
|
4678
|
+
documentType,
|
|
4679
|
+
operation,
|
|
4680
|
+
JSON.stringify(scope),
|
|
4681
|
+
knowledgeScopeKey(scope),
|
|
4682
|
+
now.toISOString(),
|
|
4683
|
+
now.toISOString()
|
|
4684
|
+
]
|
|
4685
|
+
});
|
|
4686
|
+
}
|
|
4687
|
+
};
|
|
4688
|
+
//#endregion
|
|
3734
4689
|
//#region src/storage/domains/mcp-clients/index.ts
|
|
3735
4690
|
var MCPClientsMySQL = class MCPClientsMySQL extends MCPClientsStorage {
|
|
3736
4691
|
pool;
|
|
@@ -4835,10 +5790,14 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
|
|
|
4835
5790
|
schema: TABLE_SCHEMAS[TABLE_MESSAGES],
|
|
4836
5791
|
ifNotExists: ["resourceId"]
|
|
4837
5792
|
});
|
|
4838
|
-
if (omSchema)
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
5793
|
+
if (omSchema) {
|
|
5794
|
+
const snapshot = this.operations.getInitSchemaSnapshot();
|
|
5795
|
+
if (!snapshot?.indexes.has(indexKey(OM_TABLE, "idx_om_lookup_key"))) try {
|
|
5796
|
+
await this.pool.execute(`CREATE INDEX idx_om_lookup_key ON ${OM_TABLE_QUOTED} (${quoteIdentifier("lookupKey", "column name")}(191))`);
|
|
5797
|
+
snapshot?.indexes.add(indexKey(OM_TABLE, "idx_om_lookup_key"));
|
|
5798
|
+
} catch (err) {
|
|
5799
|
+
if (err?.errno !== 1061) throw err;
|
|
5800
|
+
}
|
|
4842
5801
|
}
|
|
4843
5802
|
await this.createDefaultIndexes();
|
|
4844
5803
|
await this.createCustomIndexes();
|
|
@@ -10357,6 +11316,7 @@ function parseConnectionString(connectionString, overrides) {
|
|
|
10357
11316
|
}
|
|
10358
11317
|
var MySQLStore = class extends MastraCompositeStore {
|
|
10359
11318
|
pool;
|
|
11319
|
+
operations;
|
|
10360
11320
|
stores;
|
|
10361
11321
|
constructor(config) {
|
|
10362
11322
|
super({
|
|
@@ -10371,12 +11331,17 @@ var MySQLStore = class extends MastraCompositeStore {
|
|
|
10371
11331
|
pool: this.pool,
|
|
10372
11332
|
database
|
|
10373
11333
|
});
|
|
11334
|
+
this.operations = operations;
|
|
10374
11335
|
const memory = new MemoryMySQL({
|
|
10375
11336
|
pool: this.pool,
|
|
10376
11337
|
operations,
|
|
10377
11338
|
skipDefaultIndexes: config.skipDefaultIndexes,
|
|
10378
11339
|
indexes: config.indexes
|
|
10379
11340
|
});
|
|
11341
|
+
const knowledge = new KnowledgeMySQL({
|
|
11342
|
+
pool: this.pool,
|
|
11343
|
+
operations
|
|
11344
|
+
});
|
|
10380
11345
|
const workflows = new WorkflowsMySQL({
|
|
10381
11346
|
operations,
|
|
10382
11347
|
pool: this.pool,
|
|
@@ -10489,6 +11454,7 @@ var MySQLStore = class extends MastraCompositeStore {
|
|
|
10489
11454
|
});
|
|
10490
11455
|
this.stores = {
|
|
10491
11456
|
memory,
|
|
11457
|
+
knowledge,
|
|
10492
11458
|
workflows,
|
|
10493
11459
|
scores,
|
|
10494
11460
|
observability,
|
|
@@ -10513,6 +11479,7 @@ var MySQLStore = class extends MastraCompositeStore {
|
|
|
10513
11479
|
async init() {
|
|
10514
11480
|
try {
|
|
10515
11481
|
(await this.pool.getConnection()).release();
|
|
11482
|
+
await this.operations.loadInitSchemaSnapshot();
|
|
10516
11483
|
await super.init();
|
|
10517
11484
|
} catch (error) {
|
|
10518
11485
|
throw new MastraError({
|
|
@@ -10520,6 +11487,8 @@ var MySQLStore = class extends MastraCompositeStore {
|
|
|
10520
11487
|
domain: ErrorDomain.STORAGE,
|
|
10521
11488
|
category: ErrorCategory.THIRD_PARTY
|
|
10522
11489
|
}, error);
|
|
11490
|
+
} finally {
|
|
11491
|
+
this.operations.clearInitSchemaSnapshot();
|
|
10523
11492
|
}
|
|
10524
11493
|
}
|
|
10525
11494
|
async close() {
|