@mastra/libsql 1.25.2-alpha.1 → 1.26.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.
Files changed (33) hide show
  1. package/dist/_types/@internal_core/dist/knowledge-compat/index.d.ts +594 -0
  2. package/dist/_types/@internal_core/package.json +1 -0
  3. package/dist/docs/SKILL.md +1 -1
  4. package/dist/docs/assets/SOURCE_MAP.json +1 -1
  5. package/dist/docs/references/docs-memory-message-history.md +15 -8
  6. package/dist/docs/references/reference-core-mastra-class.md +12 -4
  7. package/dist/docs/references/reference-storage-retention.md +21 -18
  8. package/dist/index.cjs +2048 -292
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.js +2049 -293
  11. package/dist/index.js.map +1 -1
  12. package/dist/shared/file-transaction-gate.d.ts +28 -0
  13. package/dist/shared/file-transaction-gate.d.ts.map +1 -0
  14. package/dist/shared/reset-after-busy-client.d.ts +34 -0
  15. package/dist/shared/reset-after-busy-client.d.ts.map +1 -0
  16. package/dist/storage/db/index.d.ts +14 -6
  17. package/dist/storage/db/index.d.ts.map +1 -1
  18. package/dist/storage/db/run-fencing.d.ts +41 -0
  19. package/dist/storage/db/run-fencing.d.ts.map +1 -0
  20. package/dist/storage/db/utils.d.ts.map +1 -1
  21. package/dist/storage/domains/knowledge/index.d.ts +30 -7
  22. package/dist/storage/domains/knowledge/index.d.ts.map +1 -1
  23. package/dist/storage/domains/memory/index.d.ts +21 -7
  24. package/dist/storage/domains/memory/index.d.ts.map +1 -1
  25. package/dist/storage/domains/workflows/index.d.ts +21 -6
  26. package/dist/storage/domains/workflows/index.d.ts.map +1 -1
  27. package/dist/storage/factory-storage.d.ts.map +1 -1
  28. package/dist/storage/index.d.ts +6 -0
  29. package/dist/storage/index.d.ts.map +1 -1
  30. package/dist/storage/retention.d.ts +3 -3
  31. package/dist/storage/retention.d.ts.map +1 -1
  32. package/dist/vector/index.d.ts.map +1 -1
  33. package/package.json +5 -4
package/dist/index.js CHANGED
@@ -1,15 +1,360 @@
1
1
  import { createClient } from "@libsql/client";
2
2
  import { ErrorCategory, ErrorDomain, MastraError } from "@mastra/core/error";
3
- 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, FactoryStorage, FavoritesStorage, HarnessStorage, 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, NotificationsStorage, 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, TABLE_AGENTS, TABLE_AGENT_VERSIONS, TABLE_BACKGROUND_TASKS, TABLE_CHANNEL_CONFIG, TABLE_CHANNEL_INSTALLATIONS, TABLE_DATASETS, TABLE_DATASET_ITEMS, TABLE_DATASET_VERSIONS, TABLE_EXPERIMENTS, TABLE_EXPERIMENT_RESULTS, TABLE_FAVORITES, TABLE_HARNESS_SESSIONS, 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_NOTIFICATIONS, 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_THREAD_STATE, TABLE_TOOL_PROVIDER_CONNECTIONS, TABLE_WORKFLOW_DEFINITIONS, TABLE_WORKFLOW_SNAPSHOT, TABLE_WORKSPACES, TABLE_WORKSPACE_VERSIONS, THREAD_STATE_SCHEMA, TOOL_PROVIDER_CONNECTIONS_SCHEMA, ThreadStateStorage, ToolProviderConnectionsStorage, TraceStatus, UniqueViolationError, WORKSPACES_SCHEMA, WORKSPACE_VERSIONS_SCHEMA, WorkflowDefinitionsStorage, WorkflowsStorage, WorkspacesStorage, assertKnowledgeCeilingRaised, assertKnowledgeScopeWithinCeiling, calculatePagination, canonicalizeKnowledgeScope, createKnowledgeUlid, createStorageErrorId, createVectorErrorId, ensureDate, executeRetentionPrune, getSqlType, hasErrorCode, isKnowledgeScopeVisible, knowledgeScopeKey, knowledgeSemanticDocumentId, knowledgeSemanticIdempotencyKey, listTracesArgsSchema, matchesExpectedWorkflowStatus, mergeWorkflowStepResult, normalizePerPage, normalizeScheduleTarget, parseKnowledgeNodeCursor, parseKnowledgeWikilinks, resolveListOrderBy, resolveRetentionTargets, retentionCutoffMs, runRetentionBatches, safelyParseJSON, toTraceSpans, transformScoreRow, validateStorageMetadataFilter } from "@mastra/core/storage";
3
+ 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, FactoryStorage, FavoritesStorage, HarnessStorage, 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, NotificationsStorage, OBSERVATIONAL_MEMORY_TABLE_SCHEMA, ObservabilityStorage, PROMPT_BLOCKS_SCHEMA, PROMPT_BLOCK_VERSIONS_SCHEMA, PromptBlocksStorage, RUN_FENCING_TABLE_SCHEMAS, RunFenceConflictError, SCORERS_SCHEMA, SCORER_DEFINITIONS_SCHEMA, SCORER_DEFINITION_VERSIONS_SCHEMA, SKILLS_SCHEMA, SKILL_BLOBS_SCHEMA, SKILL_VERSIONS_SCHEMA, SPAN_SCHEMA, SchedulesStorage, ScorerDefinitionsStorage, ScoresStorage, SkillsStorage, TABLE_AGENTS, TABLE_AGENT_VERSIONS, TABLE_BACKGROUND_TASKS, TABLE_CHANNEL_CONFIG, TABLE_CHANNEL_INSTALLATIONS, TABLE_DATASETS, TABLE_DATASET_ITEMS, TABLE_DATASET_VERSIONS, TABLE_EXPERIMENTS, TABLE_EXPERIMENT_RESULTS, TABLE_FAVORITES, TABLE_HARNESS_SESSIONS, TABLE_KNOWLEDGE_ACTIVITY, 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_MEMORY_RUN_FENCES, TABLE_MESSAGES, TABLE_NOTIFICATIONS, 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_THREAD_STATE, TABLE_TOOL_PROVIDER_CONNECTIONS, TABLE_WORKFLOW_DEFINITIONS, TABLE_WORKFLOW_RUN_OWNERS, TABLE_WORKFLOW_SNAPSHOT, TABLE_WORKSPACES, TABLE_WORKSPACE_VERSIONS, THREAD_STATE_SCHEMA, TOOL_PROVIDER_CONNECTIONS_SCHEMA, ThreadStateStorage, ToolProviderConnectionsStorage, TraceStatus, UniqueViolationError, WORKSPACES_SCHEMA, WORKSPACE_VERSIONS_SCHEMA, WorkflowDefinitionsStorage, WorkflowsStorage, WorkspacesStorage, calculatePagination, canonicalizeKnowledgeScope, createKnowledgeUlid, createStorageErrorId, createVectorErrorId, ensureDate, executeRetentionPrune, getSqlType, hasErrorCode, isKnowledgeScopeVisible, isRunFenceConflictError, knowledgeScopeKey, knowledgeSemanticDocumentId, knowledgeSemanticIdempotencyKey, listTracesArgsSchema, matchesExpectedWorkflowStatus, matchesRunFence, mergeWorkflowStepResult, normalizePerPage, normalizeScheduleTarget, parseKnowledgeNodeCursor, parseKnowledgeWikilinks, resolveListOrderBy, resolveRetentionTargets, resolveRunFence, retentionCutoffMs, runRetentionBatches, safelyParseJSON, toTraceSpans, transformScoreRow, validateStorageMetadataFilter } from "@mastra/core/storage";
4
4
  import { parseFieldKey, parseSqlIdentifier } from "@mastra/core/utils";
5
5
  import { MastraVector, validateTopK, validateUpsertInput } from "@mastra/core/vector";
6
+ import { basename, dirname, isAbsolute, join, resolve } from "path";
7
+ import { AsyncLocalStorage } from "async_hooks";
6
8
  import { BaseFilterTranslator } from "@mastra/core/vector/filter";
7
9
  import { realpath } from "fs/promises";
8
- import { basename, dirname, isAbsolute, join, resolve } from "path";
9
10
  import { MastraBase } from "@mastra/core/base";
11
+ import { randomUUID } from "crypto";
12
+ import { coreFeatures } from "@mastra/core/features";
10
13
  import { MessageList } from "@mastra/core/agent";
11
14
  import { saveScorePayloadSchema } from "@mastra/core/evals";
12
15
  import { skillSnapshotFieldValuesEqual } from "@mastra/core/storage/domains/skills";
16
+ //#region src/shared/file-transaction-gate.ts
17
+ /** Interactive transactions open in this process, per database file. */
18
+ const openTransactions = /* @__PURE__ */ new Map();
19
+ /** The database file a local `file:` url points at, or `undefined` for anything else. */
20
+ function localDatabaseFileKey(url) {
21
+ if (!url.startsWith("file:") || url.includes(":memory:")) return void 0;
22
+ const path = url.slice(5).split("?")[0];
23
+ return path ? resolve(path.replace(/^\/\/(?=\/)/, "")) : void 0;
24
+ }
25
+ /**
26
+ * Wraps a local `file:` client so its calls wait for interactive transactions
27
+ * that other clients in this process hold open on the same database file,
28
+ * instead of blocking the thread.
29
+ *
30
+ * `@libsql/client` runs local statements synchronously. When one client holds
31
+ * a write transaction open across an `await` and another client writes the same
32
+ * file, SQLite's busy handler spins on the event loop's only thread for
33
+ * `busy_timeout`, so the transaction holding the lock can't resume and the write
34
+ * fails with `SQLITE_BUSY`. Waiting lets the transaction finish first.
35
+ *
36
+ * Only calls on other clients wait; a client already orders its own writes
37
+ * behind its transactions (see `withClientWriteLock`). A call waits at most
38
+ * `maxWaitMs` (the busy timeout), then goes ahead as it would without the gate,
39
+ * so code that awaits another client from inside its own transaction can't hang.
40
+ * Clients in other processes are unaffected and still rely on `busy_timeout`.
41
+ */
42
+ function gateLocalFileClient(client, key, { maxWaitMs = 5e3 } = {}) {
43
+ let gated;
44
+ const othersOpen = () => [...openTransactions.get(key) ?? []].filter((open) => open.owner !== gated);
45
+ const waitForOthers = () => {
46
+ if (othersOpen().length === 0) return;
47
+ return (async () => {
48
+ let timer;
49
+ const gaveUp = new Promise((r) => timer = setTimeout(r, maxWaitMs, "timeout"));
50
+ try {
51
+ for (let open = othersOpen(); open.length > 0; open = othersOpen()) if (await Promise.race([Promise.all(open.map((o) => o.done)), gaveUp]) === "timeout") return false;
52
+ return true;
53
+ } finally {
54
+ clearTimeout(timer);
55
+ }
56
+ })();
57
+ };
58
+ const call = (run) => {
59
+ const waiting = waitForOthers();
60
+ return waiting ? waiting.then(run) : run();
61
+ };
62
+ const transaction = async (open, mode) => {
63
+ const deadline = Date.now() + maxWaitMs;
64
+ for (let waiting = waitForOthers(); waiting; waiting = Date.now() < deadline ? waitForOthers() : void 0) if (!await waiting) break;
65
+ let finish;
66
+ const entry = {
67
+ owner: gated,
68
+ done: new Promise((r) => finish = r)
69
+ };
70
+ let set = openTransactions.get(key);
71
+ if (!set) openTransactions.set(key, set = /* @__PURE__ */ new Set());
72
+ set.add(entry);
73
+ const end = () => {
74
+ if (!set.delete(entry)) return;
75
+ if (set.size === 0 && openTransactions.get(key) === set) openTransactions.delete(key);
76
+ finish();
77
+ };
78
+ let tx;
79
+ try {
80
+ tx = await open(mode);
81
+ } catch (error) {
82
+ end();
83
+ throw error;
84
+ }
85
+ return new Proxy(tx, { get(target, prop) {
86
+ switch (prop) {
87
+ case "execute":
88
+ case "batch":
89
+ case "executeMultiple": {
90
+ const method = Reflect.get(target, prop);
91
+ return async (...args) => {
92
+ try {
93
+ return await method.apply(target, args);
94
+ } finally {
95
+ if (target.closed) end();
96
+ }
97
+ };
98
+ }
99
+ case "commit":
100
+ case "rollback": {
101
+ const method = Reflect.get(target, prop);
102
+ return async () => {
103
+ try {
104
+ await method.call(target);
105
+ } finally {
106
+ end();
107
+ }
108
+ };
109
+ }
110
+ case "close": {
111
+ const method = Reflect.get(target, prop);
112
+ return () => {
113
+ try {
114
+ method.call(target);
115
+ } finally {
116
+ end();
117
+ }
118
+ };
119
+ }
120
+ default: {
121
+ const value = Reflect.get(target, prop);
122
+ return typeof value === "function" ? value.bind(target) : value;
123
+ }
124
+ }
125
+ } });
126
+ };
127
+ gated = new Proxy(client, { get(target, prop) {
128
+ switch (prop) {
129
+ case "transaction": {
130
+ const open = Reflect.get(target, prop);
131
+ return (mode) => transaction(open.bind(target), mode);
132
+ }
133
+ case "execute":
134
+ case "batch":
135
+ case "executeMultiple":
136
+ case "migrate": {
137
+ const method = Reflect.get(target, prop);
138
+ return (...args) => call(() => method.apply(target, args));
139
+ }
140
+ default: {
141
+ const value = Reflect.get(target, prop);
142
+ return typeof value === "function" ? value.bind(target) : value;
143
+ }
144
+ }
145
+ } });
146
+ return gated;
147
+ }
148
+ /** Applies {@link gateLocalFileClient} when `url` is a local database file; returns `client` otherwise. */
149
+ function gateIfLocalFile(client, url, options) {
150
+ const key = localDatabaseFileKey(url);
151
+ return key ? gateLocalFileClient(client, key, options) : client;
152
+ }
153
+ //#endregion
154
+ //#region src/shared/reset-after-busy-client.ts
155
+ function isBusy(error) {
156
+ return error?.code === "SQLITE_BUSY";
157
+ }
158
+ function firstOpen(hold) {
159
+ while (hold && !hold.open) hold = hold.outer;
160
+ return hold;
161
+ }
162
+ /**
163
+ * Wraps a pooled local `file:` client so a statement refused with `SQLITE_BUSY`
164
+ * doesn't leave its connection unusable.
165
+ *
166
+ * libsql leaves a write statement whose run failed busy in progress until it is
167
+ * garbage collected (tursodatabase/libsql-js#237). Until then no transaction on
168
+ * that connection can commit ("cannot commit transaction - SQL statements in
169
+ * progress"), and an autocommit write on it stays uncommitted while holding the
170
+ * database's write lock. The pool hands the same connection out again, so
171
+ * retrying the write doesn't help.
172
+ *
173
+ * After a busy failure the wrapper reopens the client's connections, which
174
+ * finalizes the stuck statement. Reopening closes every connection, including
175
+ * borrowed ones, so it waits until no call or transaction made through the
176
+ * wrapper is in flight. New calls wait for the reset rather than join the calls
177
+ * it waits for, so a client that is never idle still gets reset. A call made
178
+ * while its caller holds an open transaction, or from inside another call,
179
+ * doesn't wait: the reset waits for what its caller holds, so waiting would
180
+ * deadlock. Reopened connections start from the pool's connection options,
181
+ * so `afterReset` runs right after to restore any per-connection settings
182
+ * applied after opening, and calls that waited for the reset go on once it
183
+ * settles, even if it fails. A call waits at most `maxWaitMs` (default 5s, the
184
+ * busy timeout) so a long-held transaction can't stall every later call; after
185
+ * that it goes ahead on the current pool. A transaction SQLite rolled back on its
186
+ * own is released as soon as the failing call returns. It must use the client it was given, not the
187
+ * wrapper: calls through the wrapper would wait for the reset it is part of.
188
+ * Every other member passes through untouched.
189
+ */
190
+ function resetConnectionsAfterBusy(client, { afterReset, maxWaitMs = 5e3 } = {}) {
191
+ let inFlight = 0;
192
+ let pendingReset;
193
+ const holds = new AsyncLocalStorage();
194
+ const finishReset = () => {
195
+ const reset = pendingReset;
196
+ pendingReset = void 0;
197
+ reset?.finish();
198
+ };
199
+ const settle = () => {
200
+ inFlight--;
201
+ if (client.closed) return finishReset();
202
+ if (!pendingReset || inFlight > 0) return;
203
+ client.reconnect();
204
+ if (!afterReset) return finishReset();
205
+ const reset = pendingReset;
206
+ Promise.resolve().then(afterReset).catch(() => {}).finally(() => {
207
+ if (pendingReset === reset) finishReset();
208
+ });
209
+ };
210
+ const noteBusy = (error) => {
211
+ if (!isBusy(error) || pendingReset) return;
212
+ let finish;
213
+ pendingReset = {
214
+ done: new Promise((resolve) => finish = resolve),
215
+ finish
216
+ };
217
+ };
218
+ const enter = (callerHolds) => {
219
+ if (!pendingReset || callerHolds || client.closed) {
220
+ inFlight++;
221
+ return;
222
+ }
223
+ return (async () => {
224
+ let timer;
225
+ const gaveUp = new Promise((resolve) => timer = setTimeout(resolve, maxWaitMs, "timeout"));
226
+ try {
227
+ while (pendingReset && !client.closed) if (await Promise.race([pendingReset.done, gaveUp]) === "timeout") break;
228
+ } finally {
229
+ clearTimeout(timer);
230
+ }
231
+ inFlight++;
232
+ })();
233
+ };
234
+ const run = async (call) => {
235
+ const hold = {
236
+ open: true,
237
+ outer: firstOpen(holds.getStore())
238
+ };
239
+ const waiting = enter(hold.outer !== void 0);
240
+ if (waiting) await waiting;
241
+ try {
242
+ return await holds.run(hold, call);
243
+ } catch (error) {
244
+ noteBusy(error);
245
+ throw error;
246
+ } finally {
247
+ hold.open = false;
248
+ settle();
249
+ }
250
+ };
251
+ const transaction = (open, mode) => {
252
+ const hold = {
253
+ open: true,
254
+ outer: firstOpen(holds.getStore())
255
+ };
256
+ const waiting = enter(hold.outer !== void 0);
257
+ holds.enterWith(hold);
258
+ return openTransaction(open, mode, hold, waiting);
259
+ };
260
+ const openTransaction = async (open, mode, hold, waiting) => {
261
+ let tx;
262
+ try {
263
+ if (waiting) await waiting;
264
+ tx = await open(mode);
265
+ } catch (error) {
266
+ hold.open = false;
267
+ noteBusy(error);
268
+ settle();
269
+ throw error;
270
+ }
271
+ const end = () => {
272
+ if (!hold.open) return;
273
+ hold.open = false;
274
+ settle();
275
+ };
276
+ return new Proxy(tx, { get(target, prop) {
277
+ switch (prop) {
278
+ case "execute":
279
+ case "batch":
280
+ case "executeMultiple": {
281
+ const method = Reflect.get(target, prop);
282
+ return async (...args) => {
283
+ try {
284
+ return await method.apply(target, args);
285
+ } catch (error) {
286
+ noteBusy(error);
287
+ if (target.closed) try {
288
+ target.close();
289
+ } finally {
290
+ end();
291
+ }
292
+ throw error;
293
+ }
294
+ };
295
+ }
296
+ case "commit":
297
+ case "rollback": {
298
+ const method = Reflect.get(target, prop);
299
+ return async () => {
300
+ try {
301
+ await method.call(target);
302
+ } catch (error) {
303
+ noteBusy(error);
304
+ throw error;
305
+ } finally {
306
+ end();
307
+ }
308
+ };
309
+ }
310
+ case "close": {
311
+ const method = Reflect.get(target, prop);
312
+ return () => {
313
+ try {
314
+ method.call(target);
315
+ } finally {
316
+ end();
317
+ }
318
+ };
319
+ }
320
+ default: {
321
+ const value = Reflect.get(target, prop);
322
+ return typeof value === "function" ? value.bind(target) : value;
323
+ }
324
+ }
325
+ } });
326
+ };
327
+ return new Proxy(client, { get(target, prop) {
328
+ switch (prop) {
329
+ case "transaction": {
330
+ const open = Reflect.get(target, prop);
331
+ return (mode) => transaction(open.bind(target), mode);
332
+ }
333
+ case "execute":
334
+ case "batch":
335
+ case "executeMultiple":
336
+ case "migrate": {
337
+ const method = Reflect.get(target, prop);
338
+ return (...args) => run(() => method.apply(target, args));
339
+ }
340
+ case "close": {
341
+ const method = Reflect.get(target, prop);
342
+ return () => {
343
+ try {
344
+ method.call(target);
345
+ } finally {
346
+ finishReset();
347
+ }
348
+ };
349
+ }
350
+ default: {
351
+ const value = Reflect.get(target, prop);
352
+ return typeof value === "function" ? value.bind(target) : value;
353
+ }
354
+ }
355
+ } });
356
+ }
357
+ //#endregion
13
358
  //#region src/shared/single-connection-client.ts
14
359
  /**
15
360
  * Whether `@libsql/client` backs this database with exactly one connection.
@@ -518,7 +863,7 @@ var LibSQLVector = class extends MastraVector {
518
863
  this.turso = isSingleConnectionDatabase({
519
864
  url,
520
865
  syncUrl
521
- }) ? gateSingleConnectionClient(client) : client;
866
+ }) ? gateSingleConnectionClient(client) : isLocalDb ? gateIfLocalFile(resetConnectionsAfterBusy(client, { maxWaitMs: 5e3 }), url, { maxWaitMs: 5e3 }) : client;
522
867
  this.maxRetries = maxRetries;
523
868
  this.initialBackoffMs = initialBackoffMs;
524
869
  if (!Number.isInteger(vectorTopKOverFetchMultiplier) || vectorTopKOverFetchMultiplier < 1) throw new Error("vectorTopKOverFetchMultiplier must be a positive integer");
@@ -1109,6 +1454,110 @@ var LibSQLVector = class extends MastraVector {
1109
1454
  }
1110
1455
  };
1111
1456
  //#endregion
1457
+ //#region src/storage/db/write-lock.ts
1458
+ /**
1459
+ * Per-client write serialization.
1460
+ *
1461
+ * `@libsql/client` >= 0.18.0 pools connections for local `file:` databases, but
1462
+ * SQLite still admits one writer at a time: an interactive
1463
+ * `client.transaction('write')` holds `BEGIN` open across every
1464
+ * `await tx.execute(...)`, and any other write on the same database in that
1465
+ * window contends on the file lock and can fail with `SQLITE_BUSY` once
1466
+ * `busy_timeout` expires.
1467
+ *
1468
+ * This is dormant under the default engine but the evented engine runs many
1469
+ * concurrent workflow snapshot writes per agent run, so a write issued by an
1470
+ * unrelated domain (e.g. creating a dataset experiment) can fail spuriously.
1471
+ *
1472
+ * Serializing every write on a given client closes that window: writes — both
1473
+ * autocommit statements and full interactive transactions — run one at a time,
1474
+ * so none can interleave with an open transaction. Reads are intentionally not
1475
+ * gated; WAL readers never observe a partial write and must not queue behind a
1476
+ * long-running writer.
1477
+ *
1478
+ * `:memory:` databases and embedded replicas get a single pooled connection
1479
+ * instead; see `shared/single-connection-client.ts`, which gates *all* calls
1480
+ * (reads included) behind open transactions for those clients.
1481
+ */
1482
+ const clientWriteChains = /* @__PURE__ */ new WeakMap();
1483
+ /**
1484
+ * Runs `fn` after every previously-enqueued write on `client` has settled, and
1485
+ * returns its result. The chain advances regardless of whether `fn` resolves or
1486
+ * rejects, so one failed write never wedges the queue.
1487
+ */
1488
+ function withClientWriteLock(client, fn) {
1489
+ const result = (clientWriteChains.get(client) ?? Promise.resolve()).then(fn, fn);
1490
+ clientWriteChains.set(client, result.then(() => void 0, () => void 0));
1491
+ return result;
1492
+ }
1493
+ //#endregion
1494
+ //#region src/storage/db/run-fencing.ts
1495
+ /**
1496
+ * Epoch milliseconds on the database clock. `julianday('now')` rather than
1497
+ * `unixepoch('subsec')`, which needs SQLite 3.42 on the server.
1498
+ */
1499
+ const DB_NOW_MS = `CAST(ROUND((julianday('now') - 2440587.5) * 86400000) AS INTEGER)`;
1500
+ /**
1501
+ * Runs `fn` in a write transaction, behind every other write on `client`.
1502
+ * `'write'` opens the transaction with `BEGIN IMMEDIATE`, which takes the
1503
+ * database's write lock up front, so nothing written between a read and a
1504
+ * write inside `fn` can come from another connection.
1505
+ */
1506
+ function inWriteTransaction(client, fn) {
1507
+ return withClientWriteLock(client, async () => {
1508
+ const tx = await client.transaction("write");
1509
+ try {
1510
+ const result = await fn(tx);
1511
+ await tx.commit();
1512
+ return result;
1513
+ } catch (error) {
1514
+ if (!tx.closed) await tx.rollback();
1515
+ throw error;
1516
+ } finally {
1517
+ if (!tx.closed) tx.close();
1518
+ }
1519
+ });
1520
+ }
1521
+ /**
1522
+ * The check half of a fenced write; call it first inside the write's
1523
+ * transaction. The transaction already holds the database's write lock, so
1524
+ * a claim can't commit between this check and the write.
1525
+ */
1526
+ async function assertRunFence(tx, { claimsTable, fence, operation }) {
1527
+ const row = (await tx.execute({
1528
+ sql: `SELECT generation, ownerId FROM ${claimsTable} WHERE runId = ?`,
1529
+ args: [fence.runId]
1530
+ })).rows[0];
1531
+ if (!matchesRunFence(row ? {
1532
+ generation: Number(row.generation),
1533
+ ownerId: String(row.ownerId)
1534
+ } : null, fence)) throw new RunFenceConflictError(fence, operation);
1535
+ }
1536
+ /**
1537
+ * Runs `write` behind the client's write lock: directly when there is no
1538
+ * fence to check, or in a write transaction behind the check when there is.
1539
+ * `write` must issue every statement through the writer it is given; on a
1540
+ * single-connection client, a call on the client itself would wait for the
1541
+ * open transaction forever.
1542
+ */
1543
+ function withRunFence(client, check, write) {
1544
+ if (!check) return withClientWriteLock(client, () => write({
1545
+ execute: (statement) => client.execute(statement),
1546
+ batch: (statements) => client.batch(statements, "write")
1547
+ }));
1548
+ return inWriteTransaction(client, async (tx) => {
1549
+ await assertRunFence(tx, check);
1550
+ return write({
1551
+ execute: (statement) => tx.execute(statement),
1552
+ batch: async (statements) => {
1553
+ const results = [];
1554
+ for (const statement of statements) results.push(await tx.execute(statement));
1555
+ return results;
1556
+ }
1557
+ });
1558
+ });
1559
+ }
1560
+ //#endregion
1112
1561
  //#region src/storage/db/utils.ts
1113
1562
  const MAX_DEPTH = 100;
1114
1563
  /**
@@ -1201,6 +1650,7 @@ function createExecuteWriteOperationWithRetry({ logger, maxRetries, initialBacko
1201
1650
  while (attempts < maxRetries) try {
1202
1651
  return await operationFn();
1203
1652
  } catch (error) {
1653
+ if (isRunFenceConflictError(error)) throw error;
1204
1654
  logger.debug(`LibSQLStore: Error caught in retry loop for ${operationDescription}`, {
1205
1655
  errorType: error.constructor.name,
1206
1656
  errorCode: error.code,
@@ -1361,43 +1811,6 @@ function transformFromSqlRow({ tableName, sqlRow }) {
1361
1811
  return result;
1362
1812
  }
1363
1813
  //#endregion
1364
- //#region src/storage/db/write-lock.ts
1365
- /**
1366
- * Per-client write serialization.
1367
- *
1368
- * `@libsql/client` >= 0.18.0 pools connections for local `file:` databases, but
1369
- * SQLite still admits one writer at a time: an interactive
1370
- * `client.transaction('write')` holds `BEGIN` open across every
1371
- * `await tx.execute(...)`, and any other write on the same database in that
1372
- * window contends on the file lock and can fail with `SQLITE_BUSY` once
1373
- * `busy_timeout` expires.
1374
- *
1375
- * This is dormant under the default engine but the evented engine runs many
1376
- * concurrent workflow snapshot writes per agent run, so a write issued by an
1377
- * unrelated domain (e.g. creating a dataset experiment) can fail spuriously.
1378
- *
1379
- * Serializing every write on a given client closes that window: writes — both
1380
- * autocommit statements and full interactive transactions — run one at a time,
1381
- * so none can interleave with an open transaction. Reads are intentionally not
1382
- * gated; WAL readers never observe a partial write and must not queue behind a
1383
- * long-running writer.
1384
- *
1385
- * `:memory:` databases and embedded replicas get a single pooled connection
1386
- * instead; see `shared/single-connection-client.ts`, which gates *all* calls
1387
- * (reads included) behind open transactions for those clients.
1388
- */
1389
- const clientWriteChains = /* @__PURE__ */ new WeakMap();
1390
- /**
1391
- * Runs `fn` after every previously-enqueued write on `client` has settled, and
1392
- * returns its result. The chain advances regardless of whether `fn` resolves or
1393
- * rejects, so one failed write never wedges the queue.
1394
- */
1395
- function withClientWriteLock(client, fn) {
1396
- const result = (clientWriteChains.get(client) ?? Promise.resolve()).then(fn, fn);
1397
- clientWriteChains.set(client, result.then(() => void 0, () => void 0));
1398
- return result;
1399
- }
1400
- //#endregion
1401
1814
  //#region src/storage/db/index.ts
1402
1815
  /**
1403
1816
  * Default SQLite `busy_timeout` (ms) for local LibSQL connections. Chosen to
@@ -1498,10 +1911,10 @@ var LibSQLDB = class extends MastraBase {
1498
1911
  /**
1499
1912
  * Internal insert implementation without retry logic.
1500
1913
  */
1501
- async doInsert({ tableName, record }) {
1914
+ async doInsert({ tableName, record, check }) {
1502
1915
  const filteredRecord = await this.filterRecordToKnownColumns(tableName, record);
1503
1916
  if (Object.keys(filteredRecord).length === 0) return;
1504
- await withClientWriteLock(this.client, () => this.client.execute(prepareStatement({
1917
+ await withRunFence(this.client, check, (writer) => writer.execute(prepareStatement({
1505
1918
  tableName,
1506
1919
  record: filteredRecord
1507
1920
  })));
@@ -1512,6 +1925,7 @@ var LibSQLDB = class extends MastraBase {
1512
1925
  * @param args - The insert arguments
1513
1926
  * @param args.tableName - The name of the table to insert into
1514
1927
  * @param args.record - The record to insert (key-value pairs)
1928
+ * @param args.check - The run fence the insert must carry, if any
1515
1929
  */
1516
1930
  insert(args) {
1517
1931
  return this.executeWriteOperationWithRetry(() => this.doInsert(args), `insert into table ${args.tableName}`);
@@ -1786,7 +2200,7 @@ var LibSQLDB = class extends MastraBase {
1786
2200
  * @param args.tableName - The name of the table to create
1787
2201
  * @param args.schema - The schema definition for the table columns
1788
2202
  */
1789
- async createTable({ tableName, schema, compositePrimaryKey }) {
2203
+ async createTable({ tableName, schema, compositePrimaryKey, executor = this.client }) {
1790
2204
  try {
1791
2205
  const parsedTableName = parseSqlIdentifier(tableName, "table name");
1792
2206
  if (compositePrimaryKey) {
@@ -1804,7 +2218,7 @@ var LibSQLDB = class extends MastraBase {
1804
2218
  if (tableName === TABLE_WORKFLOW_SNAPSHOT) tableConstraints.push("UNIQUE (workflow_name, run_id)");
1805
2219
  if (tableName === TABLE_SPANS) tableConstraints.push("UNIQUE (spanId, traceId)");
1806
2220
  const sql = `CREATE TABLE IF NOT EXISTS ${parsedTableName} (\n ${[...columnDefinitions, ...tableConstraints].join(",\n ")}\n)`;
1807
- await this.client.execute(sql);
2221
+ await executor.execute(sql);
1808
2222
  this.logger.debug(`LibSQLDB: Created table ${tableName}`);
1809
2223
  if (tableName === TABLE_SPANS) await this.migrateSpansTable();
1810
2224
  } catch (error) {
@@ -6047,20 +6461,678 @@ var HarnessLibSQL = class HarnessLibSQL extends HarnessStorage {
6047
6461
  }
6048
6462
  };
6049
6463
  //#endregion
6464
+ //#region ../../packages/_internal-core/dist/knowledge-compat/index.js
6465
+ const KNOWLEDGE_V2_CORE_FEATURE = "knowledge-v2";
6466
+ /** Hard cap on scope nodes returned by one `listScopeNodes` read. */
6467
+ const MAX_KNOWLEDGE_SCOPE_NODES = 1e3;
6468
+ function knowledgeScopeNodeFilterKey(input) {
6469
+ return JSON.stringify([
6470
+ input.withinAddress ?? null,
6471
+ input.addresses ? [...input.addresses].sort() : null,
6472
+ input.ids ? [...input.ids].sort() : null
6473
+ ]);
6474
+ }
6475
+ function createKnowledgeScopeNodeCursor(scope, input) {
6476
+ return encodeURIComponent(JSON.stringify({
6477
+ version: 1,
6478
+ type: "scope",
6479
+ name: scope.name,
6480
+ id: scope.id,
6481
+ filter: knowledgeScopeNodeFilterKey(input)
6482
+ }));
6483
+ }
6484
+ /** Validates the page size and cursor of a `listScopeNodes` query; throws on a cursor from a different query. */
6485
+ function parseListKnowledgeScopeNodesInput(input = {}) {
6486
+ const requested = Number.isFinite(input.limit) ? Math.trunc(input.limit) : MAX_KNOWLEDGE_SCOPE_NODES;
6487
+ const limit = Math.min(Math.max(requested, 1), MAX_KNOWLEDGE_SCOPE_NODES);
6488
+ if (!input.cursor) return {
6489
+ limit,
6490
+ after: null
6491
+ };
6492
+ let value;
6493
+ try {
6494
+ value = JSON.parse(decodeURIComponent(input.cursor));
6495
+ } catch {
6496
+ throw new Error("Invalid Knowledge scope node cursor.");
6497
+ }
6498
+ const parsed = value && typeof value === "object" ? value : {};
6499
+ if (parsed.version !== 1 || parsed.type !== "scope" || typeof parsed.name !== "string" || typeof parsed.id !== "string" || parsed.filter !== knowledgeScopeNodeFilterKey(input)) throw new Error("Knowledge scope node cursor does not match this query.");
6500
+ return {
6501
+ limit,
6502
+ after: {
6503
+ name: parsed.name,
6504
+ id: parsed.id
6505
+ }
6506
+ };
6507
+ }
6508
+ /** Builds a page from up to `limit + 1` name/id-ordered scope nodes. */
6509
+ function pageKnowledgeScopeNodes(rows, limit, input) {
6510
+ const scopes = rows.slice(0, limit);
6511
+ const last = scopes.at(-1);
6512
+ return {
6513
+ scopes,
6514
+ nextCursor: rows.length > limit && last ? createKnowledgeScopeNodeCursor(last, input) : null
6515
+ };
6516
+ }
6517
+ /** Clamps a requested member limit to 1–{@link MAX_KNOWLEDGE_SCOPE_MEMBERS_LIMIT}. */
6518
+ function knowledgeScopeMembersLimit(limit) {
6519
+ if (limit === void 0 || !Number.isFinite(limit)) return 500;
6520
+ return Math.min(Math.max(Math.trunc(limit), 1), 500);
6521
+ }
6522
+ /** Validates the page size and cursor of a `listScopeMembers` query; throws on a cursor from another scope. */
6523
+ function parseListKnowledgeScopeMembersInput(input) {
6524
+ const limit = knowledgeScopeMembersLimit(input.limit);
6525
+ if (!input.cursor) return {
6526
+ limit,
6527
+ after: null
6528
+ };
6529
+ let value;
6530
+ try {
6531
+ value = JSON.parse(decodeURIComponent(input.cursor));
6532
+ } catch {
6533
+ throw new Error("Invalid Knowledge scope member cursor.");
6534
+ }
6535
+ const parsed = value && typeof value === "object" ? value : {};
6536
+ const updatedAt = typeof parsed.updatedAt === "string" ? new Date(parsed.updatedAt) : /* @__PURE__ */ new Date(NaN);
6537
+ if (parsed.version !== 1 || parsed.type !== "scope-member" || parsed.scopeNodeId !== input.scopeNodeId || typeof parsed.name !== "string" || typeof parsed.id !== "string" || Number.isNaN(updatedAt.getTime())) throw new Error("Knowledge scope member cursor does not match this query.");
6538
+ return {
6539
+ limit,
6540
+ after: {
6541
+ updatedAt,
6542
+ name: parsed.name,
6543
+ id: parsed.id
6544
+ }
6545
+ };
6546
+ }
6547
+ /** Builds a page from up to `limit + 1` members in `listScopeMembers` order. */
6548
+ function pageKnowledgeScopeMembers(rows, limit, input) {
6549
+ const members = rows.slice(0, limit);
6550
+ const last = members.at(-1);
6551
+ const hasMore = rows.length > limit;
6552
+ return {
6553
+ members,
6554
+ hasMore,
6555
+ nextCursor: hasMore && last ? encodeURIComponent(JSON.stringify({
6556
+ version: 1,
6557
+ type: "scope-member",
6558
+ scopeNodeId: input.scopeNodeId,
6559
+ updatedAt: last.updatedAt.toISOString(),
6560
+ name: last.name,
6561
+ id: last.id
6562
+ })) : null
6563
+ };
6564
+ }
6565
+ const TABLE_KNOWLEDGE_NODES$1 = "mastra_knowledge_nodes";
6566
+ const TABLE_KNOWLEDGE_RECORDS$1 = "mastra_knowledge_records";
6567
+ const TABLE_KNOWLEDGE_MENTIONS$1 = "mastra_knowledge_mentions";
6568
+ const TABLE_KNOWLEDGE_ACTIVITY$1 = "mastra_knowledge_activity";
6569
+ const TABLE_KNOWLEDGE_SEMANTIC_OUTBOX$1 = "mastra_knowledge_semantic_outbox";
6570
+ const TABLE_KNOWLEDGE_NODE_SCOPES = "mastra_knowledge_node_scopes";
6571
+ const TABLE_KNOWLEDGE_RECORD_SCOPES = "mastra_knowledge_record_scopes";
6572
+ const TABLE_KNOWLEDGE_SCOPE_GRANTS = "mastra_knowledge_scope_grants";
6573
+ const TABLE_KNOWLEDGE_ACCESS_STATE = "mastra_knowledge_access_state";
6574
+ const TABLE_KNOWLEDGE_SCOPE_ADDRESSES = "mastra_knowledge_scope_addresses";
6575
+ const TABLE_KNOWLEDGE_NODE_ADDRESSES = "mastra_knowledge_node_addresses";
6576
+ const TABLE_KNOWLEDGE_IMPORT_STATE = "mastra_knowledge_import_state";
6577
+ const TABLE_KNOWLEDGE_IMPORT_RUNS = "mastra_knowledge_import_runs";
6578
+ const TABLE_KNOWLEDGE_PROPOSALS = "mastra_knowledge_proposals";
6579
+ const KNOWLEDGE_TABLE_NAMES = [
6580
+ TABLE_KNOWLEDGE_NODES$1,
6581
+ TABLE_KNOWLEDGE_RECORDS$1,
6582
+ TABLE_KNOWLEDGE_MENTIONS$1,
6583
+ TABLE_KNOWLEDGE_ACTIVITY$1,
6584
+ TABLE_KNOWLEDGE_SEMANTIC_OUTBOX$1,
6585
+ TABLE_KNOWLEDGE_NODE_SCOPES,
6586
+ TABLE_KNOWLEDGE_RECORD_SCOPES,
6587
+ TABLE_KNOWLEDGE_SCOPE_GRANTS,
6588
+ TABLE_KNOWLEDGE_ACCESS_STATE,
6589
+ TABLE_KNOWLEDGE_SCOPE_ADDRESSES,
6590
+ TABLE_KNOWLEDGE_NODE_ADDRESSES,
6591
+ TABLE_KNOWLEDGE_IMPORT_STATE,
6592
+ TABLE_KNOWLEDGE_IMPORT_RUNS,
6593
+ TABLE_KNOWLEDGE_PROPOSALS
6594
+ ];
6595
+ /** Knowledge v1 tables with no v2 equivalent; an explicit schema reset drops them. */
6596
+ const RETIRED_KNOWLEDGE_TABLE_NAMES = ["mastra_knowledge_cursors"];
6597
+ /** Tables and indexes published v1 adapters created for every app, whether or not it used Knowledge. */
6598
+ const PUBLISHED_KNOWLEDGE_V1_TABLE_NAMES = /* @__PURE__ */ new Set([
6599
+ TABLE_KNOWLEDGE_NODES$1,
6600
+ TABLE_KNOWLEDGE_RECORDS$1,
6601
+ TABLE_KNOWLEDGE_MENTIONS$1,
6602
+ TABLE_KNOWLEDGE_ACTIVITY$1,
6603
+ TABLE_KNOWLEDGE_SEMANTIC_OUTBOX$1,
6604
+ ...RETIRED_KNOWLEDGE_TABLE_NAMES
6605
+ ]);
6606
+ const PUBLISHED_KNOWLEDGE_V1_INDEX_NAMES = /* @__PURE__ */ new Set([
6607
+ "idx_knowledge_nodes_identity",
6608
+ "idx_knowledge_nodes_scope",
6609
+ "idx_knowledge_records_node_latest",
6610
+ "idx_knowledge_records_thread_latest",
6611
+ "idx_knowledge_mentions_record",
6612
+ "idx_knowledge_activity_latest",
6613
+ "idx_knowledge_outbox_idempotency",
6614
+ "idx_knowledge_outbox_claim"
6615
+ ]);
6616
+ const KNOWLEDGE_V2_NODES_SCHEMA = {
6617
+ id: {
6618
+ type: "text",
6619
+ nullable: false,
6620
+ primaryKey: true
6621
+ },
6622
+ name: {
6623
+ type: "text",
6624
+ nullable: false
6625
+ },
6626
+ kind: {
6627
+ type: "text",
6628
+ nullable: true
6629
+ },
6630
+ isScope: {
6631
+ type: "boolean",
6632
+ nullable: false
6633
+ },
6634
+ metadata: {
6635
+ type: "jsonb",
6636
+ nullable: true
6637
+ },
6638
+ version: {
6639
+ type: "integer",
6640
+ nullable: false
6641
+ },
6642
+ createdAt: {
6643
+ type: "timestamp",
6644
+ nullable: false
6645
+ },
6646
+ updatedAt: {
6647
+ type: "timestamp",
6648
+ nullable: false
6649
+ },
6650
+ deletedAt: {
6651
+ type: "timestamp",
6652
+ nullable: true
6653
+ },
6654
+ deletedBy: {
6655
+ type: "text",
6656
+ nullable: true
6657
+ },
6658
+ type: {
6659
+ type: "text",
6660
+ nullable: true
6661
+ },
6662
+ canonicalName: {
6663
+ type: "text",
6664
+ nullable: true
6665
+ },
6666
+ content: {
6667
+ type: "text",
6668
+ nullable: true
6669
+ },
6670
+ description: {
6671
+ type: "text",
6672
+ nullable: true
6673
+ },
6674
+ scope: {
6675
+ type: "jsonb",
6676
+ nullable: true
6677
+ },
6678
+ scopeKey: {
6679
+ type: "text",
6680
+ nullable: true
6681
+ },
6682
+ mergedInto: {
6683
+ type: "text",
6684
+ nullable: true
6685
+ }
6686
+ };
6687
+ const KNOWLEDGE_V2_RECORDS_SCHEMA = {
6688
+ id: {
6689
+ type: "text",
6690
+ nullable: false,
6691
+ primaryKey: true
6692
+ },
6693
+ node: {
6694
+ type: "text",
6695
+ nullable: false,
6696
+ references: {
6697
+ table: TABLE_KNOWLEDGE_NODES$1,
6698
+ column: "id"
6699
+ }
6700
+ },
6701
+ text: {
6702
+ type: "text",
6703
+ nullable: false
6704
+ },
6705
+ metadata: {
6706
+ type: "jsonb",
6707
+ nullable: true
6708
+ },
6709
+ version: {
6710
+ type: "integer",
6711
+ nullable: false
6712
+ },
6713
+ capturedAt: {
6714
+ type: "timestamp",
6715
+ nullable: false
6716
+ },
6717
+ updatedAt: {
6718
+ type: "timestamp",
6719
+ nullable: false
6720
+ },
6721
+ deletedAt: {
6722
+ type: "timestamp",
6723
+ nullable: true
6724
+ },
6725
+ deletedBy: {
6726
+ type: "text",
6727
+ nullable: true
6728
+ },
6729
+ scope: {
6730
+ type: "jsonb",
6731
+ nullable: true
6732
+ },
6733
+ scopeKey: {
6734
+ type: "text",
6735
+ nullable: true
6736
+ },
6737
+ sourceThreadId: {
6738
+ type: "text",
6739
+ nullable: true
6740
+ },
6741
+ when: {
6742
+ type: "timestamp",
6743
+ nullable: true
6744
+ }
6745
+ };
6746
+ const KNOWLEDGE_V2_MENTIONS_SCHEMA = {
6747
+ recordId: {
6748
+ type: "text",
6749
+ nullable: false,
6750
+ references: {
6751
+ table: TABLE_KNOWLEDGE_RECORDS$1,
6752
+ column: "id"
6753
+ }
6754
+ },
6755
+ targetNodeId: {
6756
+ type: "text",
6757
+ nullable: true,
6758
+ references: {
6759
+ table: TABLE_KNOWLEDGE_NODES$1,
6760
+ column: "id"
6761
+ }
6762
+ },
6763
+ sourceType: {
6764
+ type: "text",
6765
+ nullable: true
6766
+ },
6767
+ sourceId: {
6768
+ type: "text",
6769
+ nullable: true
6770
+ }
6771
+ };
6772
+ const KNOWLEDGE_NODE_SCOPES_SCHEMA = {
6773
+ nodeId: {
6774
+ type: "text",
6775
+ nullable: false,
6776
+ references: {
6777
+ table: TABLE_KNOWLEDGE_NODES$1,
6778
+ column: "id"
6779
+ }
6780
+ },
6781
+ scopeNodeId: {
6782
+ type: "text",
6783
+ nullable: false,
6784
+ references: {
6785
+ table: TABLE_KNOWLEDGE_NODES$1,
6786
+ column: "id"
6787
+ }
6788
+ },
6789
+ addedAt: {
6790
+ type: "timestamp",
6791
+ nullable: false
6792
+ }
6793
+ };
6794
+ const KNOWLEDGE_RECORD_SCOPES_SCHEMA = {
6795
+ recordId: {
6796
+ type: "text",
6797
+ nullable: false,
6798
+ references: {
6799
+ table: TABLE_KNOWLEDGE_RECORDS$1,
6800
+ column: "id"
6801
+ }
6802
+ },
6803
+ scopeNodeId: {
6804
+ type: "text",
6805
+ nullable: false,
6806
+ references: {
6807
+ table: TABLE_KNOWLEDGE_NODES$1,
6808
+ column: "id"
6809
+ }
6810
+ },
6811
+ addedAt: {
6812
+ type: "timestamp",
6813
+ nullable: false
6814
+ }
6815
+ };
6816
+ const KNOWLEDGE_SCOPE_GRANTS_SCHEMA = {
6817
+ scopeNodeId: {
6818
+ type: "text",
6819
+ nullable: false,
6820
+ references: {
6821
+ table: TABLE_KNOWLEDGE_NODES$1,
6822
+ column: "id"
6823
+ }
6824
+ },
6825
+ scopeRefId: {
6826
+ type: "text",
6827
+ nullable: false,
6828
+ references: {
6829
+ table: TABLE_KNOWLEDGE_NODES$1,
6830
+ column: "id"
6831
+ }
6832
+ },
6833
+ role: {
6834
+ type: "text",
6835
+ nullable: false
6836
+ },
6837
+ canSuggest: {
6838
+ type: "boolean",
6839
+ nullable: true
6840
+ }
6841
+ };
6842
+ const KNOWLEDGE_ACCESS_STATE_SCHEMA = {
6843
+ id: {
6844
+ type: "text",
6845
+ nullable: false,
6846
+ primaryKey: true
6847
+ },
6848
+ epoch: {
6849
+ type: "integer",
6850
+ nullable: false
6851
+ },
6852
+ schemaVersion: {
6853
+ type: "integer",
6854
+ nullable: false
6855
+ }
6856
+ };
6857
+ const KNOWLEDGE_SCOPE_ADDRESSES_SCHEMA = {
6858
+ address: {
6859
+ type: "text",
6860
+ nullable: false,
6861
+ primaryKey: true
6862
+ },
6863
+ scopeNodeId: {
6864
+ type: "text",
6865
+ nullable: false,
6866
+ references: {
6867
+ table: TABLE_KNOWLEDGE_NODES$1,
6868
+ column: "id"
6869
+ }
6870
+ }
6871
+ };
6872
+ const KNOWLEDGE_NODE_ADDRESSES_SCHEMA = {
6873
+ source: {
6874
+ type: "text",
6875
+ nullable: false
6876
+ },
6877
+ address: {
6878
+ type: "text",
6879
+ nullable: false
6880
+ },
6881
+ nodeId: {
6882
+ type: "text",
6883
+ nullable: false,
6884
+ references: {
6885
+ table: TABLE_KNOWLEDGE_NODES$1,
6886
+ column: "id"
6887
+ }
6888
+ }
6889
+ };
6890
+ const KNOWLEDGE_IMPORT_STATE_SCHEMA = {
6891
+ importerId: {
6892
+ type: "text",
6893
+ nullable: false
6894
+ },
6895
+ binding: {
6896
+ type: "text",
6897
+ nullable: false
6898
+ },
6899
+ key: {
6900
+ type: "text",
6901
+ nullable: false
6902
+ },
6903
+ value: {
6904
+ type: "text",
6905
+ nullable: false
6906
+ }
6907
+ };
6908
+ const KNOWLEDGE_IMPORT_RUNS_SCHEMA = {
6909
+ id: {
6910
+ type: "text",
6911
+ nullable: false,
6912
+ primaryKey: true
6913
+ },
6914
+ importerId: {
6915
+ type: "text",
6916
+ nullable: false
6917
+ },
6918
+ binding: {
6919
+ type: "text",
6920
+ nullable: false
6921
+ },
6922
+ importKind: {
6923
+ type: "text",
6924
+ nullable: false
6925
+ },
6926
+ triggerKind: {
6927
+ type: "text",
6928
+ nullable: false
6929
+ },
6930
+ status: {
6931
+ type: "text",
6932
+ nullable: false
6933
+ },
6934
+ error: {
6935
+ type: "text",
6936
+ nullable: true
6937
+ },
6938
+ transcriptThreadId: {
6939
+ type: "text",
6940
+ nullable: true
6941
+ },
6942
+ traceId: {
6943
+ type: "text",
6944
+ nullable: true
6945
+ },
6946
+ queuedAt: {
6947
+ type: "timestamp",
6948
+ nullable: false
6949
+ },
6950
+ startedAt: {
6951
+ type: "timestamp",
6952
+ nullable: true
6953
+ },
6954
+ completedAt: {
6955
+ type: "timestamp",
6956
+ nullable: true
6957
+ }
6958
+ };
6959
+ const KNOWLEDGE_V2_ACTIVITY_SCHEMA = {
6960
+ id: {
6961
+ type: "text",
6962
+ nullable: false,
6963
+ primaryKey: true
6964
+ },
6965
+ action: {
6966
+ type: "text",
6967
+ nullable: false
6968
+ },
6969
+ targetType: {
6970
+ type: "text",
6971
+ nullable: true
6972
+ },
6973
+ targetId: {
6974
+ type: "text",
6975
+ nullable: true
6976
+ },
6977
+ contextScopeId: {
6978
+ type: "text",
6979
+ nullable: true,
6980
+ references: {
6981
+ table: TABLE_KNOWLEDGE_NODES$1,
6982
+ column: "id"
6983
+ }
6984
+ },
6985
+ importRunId: {
6986
+ type: "text",
6987
+ nullable: true,
6988
+ references: {
6989
+ table: TABLE_KNOWLEDGE_IMPORT_RUNS,
6990
+ column: "id"
6991
+ }
6992
+ },
6993
+ details: {
6994
+ type: "jsonb",
6995
+ nullable: true
6996
+ },
6997
+ createdAt: {
6998
+ type: "timestamp",
6999
+ nullable: false
7000
+ },
7001
+ recordType: {
7002
+ type: "text",
7003
+ nullable: true
7004
+ },
7005
+ recordId: {
7006
+ type: "text",
7007
+ nullable: true
7008
+ },
7009
+ scope: {
7010
+ type: "jsonb",
7011
+ nullable: true
7012
+ },
7013
+ scopeKey: {
7014
+ type: "text",
7015
+ nullable: true
7016
+ },
7017
+ sourceThreadId: {
7018
+ type: "text",
7019
+ nullable: true
7020
+ }
7021
+ };
7022
+ const KNOWLEDGE_PROPOSALS_SCHEMA = {
7023
+ id: {
7024
+ type: "text",
7025
+ nullable: false,
7026
+ primaryKey: true
7027
+ },
7028
+ targetType: {
7029
+ type: "text",
7030
+ nullable: false
7031
+ },
7032
+ targetId: {
7033
+ type: "text",
7034
+ nullable: false
7035
+ },
7036
+ action: {
7037
+ type: "text",
7038
+ nullable: false
7039
+ },
7040
+ changes: {
7041
+ type: "jsonb",
7042
+ nullable: false
7043
+ },
7044
+ reason: {
7045
+ type: "text",
7046
+ nullable: true
7047
+ },
7048
+ proposerContextScopeId: {
7049
+ type: "text",
7050
+ nullable: false
7051
+ },
7052
+ expectedVersion: {
7053
+ type: "integer",
7054
+ nullable: false
7055
+ },
7056
+ status: {
7057
+ type: "text",
7058
+ nullable: false
7059
+ },
7060
+ reviewerContextScopeId: {
7061
+ type: "text",
7062
+ nullable: true
7063
+ },
7064
+ reviewedAt: {
7065
+ type: "timestamp",
7066
+ nullable: true
7067
+ },
7068
+ createdAt: {
7069
+ type: "timestamp",
7070
+ nullable: false
7071
+ }
7072
+ };
7073
+ function assertKnowledgeV2CoreSupport(features) {
7074
+ if (!features.has("knowledge-v2")) throw new Error(`Knowledge v2 requires a @mastra/core release with the "${KNOWLEDGE_V2_CORE_FEATURE}" feature. Upgrade @mastra/core to use Knowledge; other storage domains keep working on this version.`);
7075
+ }
7076
+ function resolveKnowledgeV2Core(storageModule) {
7077
+ if (typeof storageModule !== "object" || storageModule === null) throw new Error("@mastra/core advertises Knowledge v2 without the required storage API");
7078
+ const { assertKnowledgeDescriptionWithinBound: assertDescription, assertKnowledgeRecordTextWithinBound: assertRecordText, assertKnowledgeSchemaCompatible: assertCompatible, inspectKnowledgeSchema: inspectSchema } = storageModule;
7079
+ if (typeof assertDescription !== "function" || typeof assertRecordText !== "function" || typeof assertCompatible !== "function" || typeof inspectSchema !== "function") throw new Error("@mastra/core advertises Knowledge v2 without the required storage API");
7080
+ return {
7081
+ assertKnowledgeDescriptionWithinBound: (description) => assertDescription(description),
7082
+ assertKnowledgeRecordTextWithinBound: (text) => assertRecordText(text),
7083
+ assertKnowledgeSchemaCompatible: (inspection) => assertCompatible(inspection),
7084
+ inspectKnowledgeSchema: (snapshot) => inspectSchema(snapshot)
7085
+ };
7086
+ }
7087
+ function createKnowledgeV2CoreLoader(features, loadStorageModule) {
7088
+ let knowledgeV2Core;
7089
+ return async () => {
7090
+ assertKnowledgeV2CoreSupport(features);
7091
+ knowledgeV2Core ??= loadStorageModule().then(resolveKnowledgeV2Core).catch((error) => {
7092
+ knowledgeV2Core = void 0;
7093
+ throw error;
7094
+ });
7095
+ return knowledgeV2Core;
7096
+ };
7097
+ }
7098
+ //#endregion
6050
7099
  //#region src/storage/domains/knowledge/index.ts
6051
- let assertDescriptionWithinBound;
7100
+ const loadKnowledgeV2Core = createKnowledgeV2CoreLoader(coreFeatures, () => import("@mastra/core/storage"));
6052
7101
  async function assertKnowledgeDescriptionWithinBoundCompat(description) {
6053
- let assertWithinBound = assertDescriptionWithinBound;
6054
- if (!assertWithinBound) {
6055
- const resolvedAssert = (await import("@mastra/core/storage")).assertKnowledgeDescriptionWithinBound ?? ((value) => {
6056
- if (value !== void 0 && value.length > 400) throw new Error("Knowledge node description exceeds the 400 UTF-16 code unit limit");
6057
- });
6058
- assertDescriptionWithinBound = resolvedAssert;
6059
- assertWithinBound = resolvedAssert;
6060
- }
6061
- assertWithinBound(description);
7102
+ const { assertKnowledgeDescriptionWithinBound } = await loadKnowledgeV2Core();
7103
+ assertKnowledgeDescriptionWithinBound(description);
7104
+ }
7105
+ async function assertKnowledgeRecordTextWithinBoundCompat(text) {
7106
+ const { assertKnowledgeRecordTextWithinBound } = await loadKnowledgeV2Core();
7107
+ assertKnowledgeRecordTextWithinBound(text);
7108
+ }
7109
+ const reconcileChains = /* @__PURE__ */ new Map();
7110
+ const unidentifiedClientReconcileKey = {};
7111
+ function withReconcileLock(key, operation) {
7112
+ const lockKey = typeof key === "string" ? key : unidentifiedClientReconcileKey;
7113
+ const result = (reconcileChains.get(lockKey) ?? Promise.resolve()).then(operation, operation);
7114
+ const tail = result.then(() => void 0, () => void 0);
7115
+ reconcileChains.set(lockKey, tail);
7116
+ tail.finally(() => {
7117
+ if (reconcileChains.get(lockKey) === tail) reconcileChains.delete(lockKey);
7118
+ });
7119
+ return result;
6062
7120
  }
6063
- const visibleSql = `(scopeKey = ? OR substr(?, 1, length(scopeKey) + 1) = scopeKey || char(31))`;
7121
+ /**
7122
+ * Scope keys visible to a caller holding `scope`: a row is visible when its scope is a subset of the
7123
+ * caller's (companion entries such as `resource:x:uncurated` make this more than a prefix match).
7124
+ * Matching rows by `scopeKey` keeps visibility filters on an index; a caller scope has only a few
7125
+ * entries, so the list stays small.
7126
+ */
7127
+ function visibleScopeKeys(scope) {
7128
+ const canonical = canonicalizeKnowledgeScope(scope);
7129
+ const keys = /* @__PURE__ */ new Set();
7130
+ for (let mask = 1; mask < 1 << canonical.length; mask++) try {
7131
+ keys.add(knowledgeScopeKey(canonical.filter((_, index) => mask & 1 << index)));
7132
+ } catch {}
7133
+ return [...keys];
7134
+ }
7135
+ const visibleSql = (keys, scopeKeyColumn = "scopeKey") => `${scopeKeyColumn} IN (${keys.map(() => "?").join(",")})`;
6064
7136
  function parseJson$3(value) {
6065
7137
  if (typeof value === "string") return JSON.parse(value);
6066
7138
  if (value instanceof Uint8Array) return JSON.parse(new TextDecoder().decode(value));
@@ -6090,6 +7162,7 @@ function parseNode(row) {
6090
7162
  kind: row.kind == null ? "" : String(row.kind),
6091
7163
  content: row.content == null ? void 0 : String(row.content),
6092
7164
  description: row.description == null ? void 0 : String(row.description),
7165
+ isScope: row.isScope === true || row.isScope === 1 ? true : void 0,
6093
7166
  scope: parseJson$3(row.scopeJson ?? row.scope),
6094
7167
  version: Number(row.version),
6095
7168
  mergedInto: row.mergedInto == null ? void 0 : String(row.mergedInto),
@@ -6106,7 +7179,6 @@ function parseKnowledge(row) {
6106
7179
  sourceThreadId: String(row.sourceThreadId),
6107
7180
  capturedAt: toDate(row.capturedAt),
6108
7181
  when: optionalDate(row.when),
6109
- maxScope: row.maxScope == null ? void 0 : String(row.maxScope),
6110
7182
  metadata: (row.metadataJson ?? row.metadata) == null ? void 0 : parseJson$3(row.metadataJson ?? row.metadata),
6111
7183
  deletedAt: optionalDate(row.deletedAt),
6112
7184
  deletedBy: row.deletedBy == null ? void 0 : String(row.deletedBy)
@@ -6129,100 +7201,458 @@ function parseOutbox(row) {
6129
7201
  completedAt: optionalDate(row.completedAt)
6130
7202
  };
6131
7203
  }
7204
+ const KNOWLEDGE_CURATION_CURSOR_REMOVED_MESSAGE = "Knowledge curation cursors were removed: observation-time curate is the only Knowledge writer and needs no cursor.";
7205
+ const knowledgeV2TableSchemas = /* @__PURE__ */ new Map([
7206
+ [TABLE_KNOWLEDGE_NODES, KNOWLEDGE_V2_NODES_SCHEMA],
7207
+ [TABLE_KNOWLEDGE_RECORDS, KNOWLEDGE_V2_RECORDS_SCHEMA],
7208
+ [TABLE_KNOWLEDGE_MENTIONS, KNOWLEDGE_V2_MENTIONS_SCHEMA],
7209
+ [TABLE_KNOWLEDGE_ACTIVITY, KNOWLEDGE_V2_ACTIVITY_SCHEMA],
7210
+ [TABLE_KNOWLEDGE_SEMANTIC_OUTBOX, KNOWLEDGE_SEMANTIC_OUTBOX_SCHEMA],
7211
+ [TABLE_KNOWLEDGE_NODE_SCOPES, KNOWLEDGE_NODE_SCOPES_SCHEMA],
7212
+ [TABLE_KNOWLEDGE_RECORD_SCOPES, KNOWLEDGE_RECORD_SCOPES_SCHEMA],
7213
+ [TABLE_KNOWLEDGE_SCOPE_GRANTS, KNOWLEDGE_SCOPE_GRANTS_SCHEMA],
7214
+ [TABLE_KNOWLEDGE_ACCESS_STATE, KNOWLEDGE_ACCESS_STATE_SCHEMA],
7215
+ [TABLE_KNOWLEDGE_SCOPE_ADDRESSES, KNOWLEDGE_SCOPE_ADDRESSES_SCHEMA],
7216
+ [TABLE_KNOWLEDGE_NODE_ADDRESSES, KNOWLEDGE_NODE_ADDRESSES_SCHEMA],
7217
+ [TABLE_KNOWLEDGE_IMPORT_STATE, KNOWLEDGE_IMPORT_STATE_SCHEMA],
7218
+ [TABLE_KNOWLEDGE_IMPORT_RUNS, KNOWLEDGE_IMPORT_RUNS_SCHEMA],
7219
+ [TABLE_KNOWLEDGE_PROPOSALS, KNOWLEDGE_PROPOSALS_SCHEMA]
7220
+ ]);
7221
+ function canonicalizeLibSQLUrl(url) {
7222
+ if (url.includes(":memory:")) return /[?&]cache=shared(?:&|$)/.test(url) ? "file::memory:?cache=shared" : void 0;
7223
+ if (url.startsWith("file:")) {
7224
+ const [path] = url.slice(5).split("?");
7225
+ if (url.startsWith("file://")) {
7226
+ const parsed = new URL(url);
7227
+ const host = parsed.hostname.toLocaleLowerCase();
7228
+ const filePath = host && host !== "localhost" ? `//${host}${parsed.pathname}` : parsed.pathname;
7229
+ return `file:${resolve(decodeURIComponent(filePath))}`;
7230
+ }
7231
+ return `file:${resolve(decodeURIComponent(path))}`;
7232
+ }
7233
+ try {
7234
+ const parsed = new URL(url);
7235
+ const port = parsed.port || (parsed.protocol === "libsql:" || parsed.protocol === "https:" ? "443" : "80");
7236
+ return `${parsed.protocol}//${parsed.hostname.toLocaleLowerCase()}:${port}${parsed.pathname}`;
7237
+ } catch {
7238
+ return url;
7239
+ }
7240
+ }
7241
+ const unidentifiedClientIsolationKey = {};
7242
+ function getLibSQLKnowledgeIsolationKey(config, client) {
7243
+ if (config.storageIsolationKey !== void 0) return config.storageIsolationKey;
7244
+ if (!config.url) return unidentifiedClientIsolationKey;
7245
+ const urlKey = canonicalizeLibSQLUrl(config.url);
7246
+ return urlKey ? `libsql:${urlKey}` : client ?? config.client ?? config;
7247
+ }
6132
7248
  var KnowledgeLibSQL = class extends KnowledgeStorage {
6133
7249
  #client;
6134
7250
  #db;
6135
7251
  constructor(config) {
6136
- super();
6137
- this.#client = resolveClient(config);
7252
+ const client = resolveClient(config);
7253
+ const storageIsolationKey = config.storageIsolationKey ?? getLibSQLKnowledgeIsolationKey(config, client);
7254
+ super({ storageIsolationKey });
7255
+ this.#client = client;
6138
7256
  this.#db = new LibSQLDB({
6139
7257
  client: this.#client,
6140
7258
  maxRetries: config.maxRetries,
6141
7259
  initialBackoffMs: config.initialBackoffMs
6142
7260
  });
6143
7261
  }
7262
+ getCapabilities() {
7263
+ return {
7264
+ contractVersion: 2,
7265
+ schemaVersion: 2,
7266
+ supportsV2: true,
7267
+ supportsSchemaInspection: true,
7268
+ supportsExplicitReset: true
7269
+ };
7270
+ }
7271
+ async inspectSchema() {
7272
+ return this.#inspectSchema(this.#client);
7273
+ }
7274
+ async #inspectSchema(executor) {
7275
+ const { inspectKnowledgeSchema } = await loadKnowledgeV2Core();
7276
+ try {
7277
+ const placeholders = KNOWLEDGE_TABLE_NAMES.map(() => "?").join(",");
7278
+ const tableNames = (await executor.execute({
7279
+ sql: `SELECT name FROM sqlite_master WHERE type = 'table' AND name IN (${placeholders})`,
7280
+ args: [...KNOWLEDGE_TABLE_NAMES]
7281
+ })).rows.map((row) => String(row.name));
7282
+ if (tableNames.length === 0) return inspectKnowledgeSchema({
7283
+ available: true,
7284
+ tableNames
7285
+ });
7286
+ const missingTables = KNOWLEDGE_TABLE_NAMES.filter((table) => !tableNames.includes(table));
7287
+ if (missingTables.length > 0) return inspectKnowledgeSchema({
7288
+ available: true,
7289
+ tableNames,
7290
+ reason: `Missing Knowledge v2 tables: ${missingTables.join(", ")}`
7291
+ });
7292
+ for (const [table, schema] of knowledgeV2TableSchemas) {
7293
+ const columns = await executor.execute(`PRAGMA table_info("${table}")`);
7294
+ const actual = new Set(columns.rows.map((row) => String(row.name)));
7295
+ const missing = Object.keys(schema).filter((column) => !actual.has(column));
7296
+ if (missing.length > 0) return inspectKnowledgeSchema({
7297
+ available: true,
7298
+ tableNames,
7299
+ reason: `Knowledge table ${table} is missing v2 columns: ${missing.join(", ")}`
7300
+ });
7301
+ }
7302
+ const accessState = await executor.execute(`SELECT schemaVersion FROM "${TABLE_KNOWLEDGE_ACCESS_STATE}" WHERE id='global'`);
7303
+ if (Number(accessState.rows[0]?.schemaVersion) !== 2) return inspectKnowledgeSchema({
7304
+ available: true,
7305
+ tableNames,
7306
+ reason: "Knowledge schema version marker is missing or incompatible"
7307
+ });
7308
+ return inspectKnowledgeSchema({
7309
+ available: true,
7310
+ tableNames,
7311
+ schemaVersion: 2
7312
+ });
7313
+ } catch (error) {
7314
+ return inspectKnowledgeSchema({
7315
+ available: false,
7316
+ tableNames: [],
7317
+ reason: error instanceof Error ? error.message : String(error)
7318
+ });
7319
+ }
7320
+ }
6144
7321
  async init() {
6145
- await this.#db.createTable({
6146
- tableName: TABLE_KNOWLEDGE_NODES,
6147
- schema: KNOWLEDGE_NODES_SCHEMA
7322
+ await this.#transaction((tx) => this.#initializeSchema(tx));
7323
+ }
7324
+ async #initializeSchema(tx) {
7325
+ const { assertKnowledgeSchemaCompatible } = await loadKnowledgeV2Core();
7326
+ let inspection = await this.#inspectSchema(tx);
7327
+ if (inspection.status === "incompatible-reset-required" && await this.#isPublishedV1Layout(tx)) {
7328
+ for (const table of [...RETIRED_KNOWLEDGE_TABLE_NAMES, ...[...KNOWLEDGE_TABLE_NAMES].reverse()]) await tx.execute(`DROP TABLE IF EXISTS "${table}"`);
7329
+ inspection = await this.#inspectSchema(tx);
7330
+ }
7331
+ assertKnowledgeSchemaCompatible(inspection);
7332
+ const createTable = (input) => this.#db.createTable({
7333
+ ...input,
7334
+ executor: tx
6148
7335
  });
6149
- await this.#db.alterTable({
7336
+ await createTable({
6150
7337
  tableName: TABLE_KNOWLEDGE_NODES,
6151
- schema: KNOWLEDGE_NODES_SCHEMA,
6152
- ifNotExists: ["description"]
7338
+ schema: KNOWLEDGE_V2_NODES_SCHEMA
6153
7339
  });
6154
- await this.#db.createTable({
7340
+ await createTable({
6155
7341
  tableName: TABLE_KNOWLEDGE_RECORDS,
6156
- schema: KNOWLEDGE_RECORDS_SCHEMA
7342
+ schema: KNOWLEDGE_V2_RECORDS_SCHEMA
6157
7343
  });
6158
- await this.#db.createTable({
7344
+ await createTable({
6159
7345
  tableName: TABLE_KNOWLEDGE_MENTIONS,
6160
- schema: KNOWLEDGE_MENTIONS_SCHEMA,
7346
+ schema: KNOWLEDGE_V2_MENTIONS_SCHEMA,
7347
+ compositePrimaryKey: ["recordId", "sourceId"]
7348
+ });
7349
+ await createTable({
7350
+ tableName: TABLE_KNOWLEDGE_NODE_SCOPES,
7351
+ schema: KNOWLEDGE_NODE_SCOPES_SCHEMA,
7352
+ compositePrimaryKey: ["nodeId", "scopeNodeId"]
7353
+ });
7354
+ await createTable({
7355
+ tableName: TABLE_KNOWLEDGE_RECORD_SCOPES,
7356
+ schema: KNOWLEDGE_RECORD_SCOPES_SCHEMA,
7357
+ compositePrimaryKey: ["recordId", "scopeNodeId"]
7358
+ });
7359
+ await createTable({
7360
+ tableName: TABLE_KNOWLEDGE_SCOPE_GRANTS,
7361
+ schema: KNOWLEDGE_SCOPE_GRANTS_SCHEMA,
7362
+ compositePrimaryKey: ["scopeNodeId", "scopeRefId"]
7363
+ });
7364
+ await createTable({
7365
+ tableName: TABLE_KNOWLEDGE_ACCESS_STATE,
7366
+ schema: KNOWLEDGE_ACCESS_STATE_SCHEMA
7367
+ });
7368
+ await createTable({
7369
+ tableName: TABLE_KNOWLEDGE_SCOPE_ADDRESSES,
7370
+ schema: KNOWLEDGE_SCOPE_ADDRESSES_SCHEMA
7371
+ });
7372
+ await createTable({
7373
+ tableName: TABLE_KNOWLEDGE_NODE_ADDRESSES,
7374
+ schema: KNOWLEDGE_NODE_ADDRESSES_SCHEMA,
7375
+ compositePrimaryKey: ["source", "address"]
7376
+ });
7377
+ await createTable({
7378
+ tableName: TABLE_KNOWLEDGE_IMPORT_STATE,
7379
+ schema: KNOWLEDGE_IMPORT_STATE_SCHEMA,
6161
7380
  compositePrimaryKey: [
6162
- "sourceType",
6163
- "sourceId",
6164
- "recordId"
7381
+ "importerId",
7382
+ "binding",
7383
+ "key"
6165
7384
  ]
6166
7385
  });
6167
- await this.#db.createTable({
6168
- tableName: TABLE_KNOWLEDGE_CURSORS,
6169
- schema: KNOWLEDGE_CURSORS_SCHEMA,
6170
- compositePrimaryKey: ["sourceThreadId", "agent"]
7386
+ await createTable({
7387
+ tableName: TABLE_KNOWLEDGE_IMPORT_RUNS,
7388
+ schema: KNOWLEDGE_IMPORT_RUNS_SCHEMA
6171
7389
  });
6172
- await this.#db.createTable({
6173
- tableName: TABLE_KNOWLEDGE_ACTIVITY,
6174
- schema: KNOWLEDGE_ACTIVITY_SCHEMA
7390
+ await createTable({
7391
+ tableName: TABLE_KNOWLEDGE_PROPOSALS,
7392
+ schema: KNOWLEDGE_PROPOSALS_SCHEMA
6175
7393
  });
6176
- await this.#db.createTable({
6177
- tableName: TABLE_KNOWLEDGE_SEMANTIC_OUTBOX,
6178
- schema: KNOWLEDGE_SEMANTIC_OUTBOX_SCHEMA
7394
+ await createTable({
7395
+ tableName: TABLE_KNOWLEDGE_ACTIVITY,
7396
+ schema: KNOWLEDGE_V2_ACTIVITY_SCHEMA
6179
7397
  });
6180
- await this.#client.batch([
6181
- {
6182
- sql: `CREATE UNIQUE INDEX IF NOT EXISTS idx_knowledge_nodes_identity ON "${TABLE_KNOWLEDGE_NODES}" (type, scopeKey, canonicalName)`,
6183
- args: []
6184
- },
6185
- {
6186
- sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_nodes_scope ON "${TABLE_KNOWLEDGE_NODES}" (scopeKey, type)`,
6187
- args: []
6188
- },
6189
- {
6190
- sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_records_node_latest ON "${TABLE_KNOWLEDGE_RECORDS}" (node, id DESC)`,
6191
- args: []
6192
- },
6193
- {
6194
- sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_records_thread_latest ON "${TABLE_KNOWLEDGE_RECORDS}" (sourceThreadId, id DESC)`,
6195
- args: []
6196
- },
6197
- {
6198
- sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_mentions_record ON "${TABLE_KNOWLEDGE_MENTIONS}" (recordId, sourceType, sourceId)`,
6199
- args: []
6200
- },
6201
- {
6202
- sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_activity_latest ON "${TABLE_KNOWLEDGE_ACTIVITY}" (id DESC)`,
6203
- args: []
6204
- },
6205
- {
6206
- sql: `CREATE UNIQUE INDEX IF NOT EXISTS idx_knowledge_outbox_idempotency ON "${TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}" (idempotencyKey)`,
6207
- args: []
6208
- },
6209
- {
6210
- sql: `CREATE INDEX IF NOT EXISTS idx_knowledge_outbox_claim ON "${TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}" (status, availableAt, createdAt)`,
6211
- args: []
6212
- }
6213
- ], "write");
7398
+ await createTable({
7399
+ tableName: TABLE_KNOWLEDGE_SEMANTIC_OUTBOX,
7400
+ schema: KNOWLEDGE_SEMANTIC_OUTBOX_SCHEMA
7401
+ });
7402
+ for (const statement of [
7403
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_knowledge_nodes_identity ON "${TABLE_KNOWLEDGE_NODES}" (type, scopeKey, canonicalName)`,
7404
+ `CREATE INDEX IF NOT EXISTS idx_knowledge_nodes_scope ON "${TABLE_KNOWLEDGE_NODES}" (scopeKey, type)`,
7405
+ `CREATE INDEX IF NOT EXISTS idx_knowledge_nodes_name ON "${TABLE_KNOWLEDGE_NODES}" (type, canonicalName)`,
7406
+ `CREATE INDEX IF NOT EXISTS idx_knowledge_records_node_latest ON "${TABLE_KNOWLEDGE_RECORDS}" (node, id DESC)`,
7407
+ `CREATE INDEX IF NOT EXISTS idx_knowledge_records_thread_latest ON "${TABLE_KNOWLEDGE_RECORDS}" (sourceThreadId, id DESC)`,
7408
+ `CREATE INDEX IF NOT EXISTS idx_knowledge_mentions_record ON "${TABLE_KNOWLEDGE_MENTIONS}" (recordId, sourceType, sourceId)`,
7409
+ `CREATE INDEX IF NOT EXISTS idx_knowledge_activity_latest ON "${TABLE_KNOWLEDGE_ACTIVITY}" (id DESC)`,
7410
+ `CREATE INDEX IF NOT EXISTS idx_knowledge_records_scope ON "${TABLE_KNOWLEDGE_RECORDS}" (scopeKey, id)`,
7411
+ `CREATE INDEX IF NOT EXISTS idx_knowledge_activity_scope ON "${TABLE_KNOWLEDGE_ACTIVITY}" (scopeKey, id DESC)`,
7412
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_knowledge_outbox_idempotency ON "${TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}" (idempotencyKey)`,
7413
+ `CREATE INDEX IF NOT EXISTS idx_knowledge_outbox_claim ON "${TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}" (status, availableAt, createdAt)`,
7414
+ `CREATE INDEX IF NOT EXISTS idx_knowledge_node_scopes_scope ON "${TABLE_KNOWLEDGE_NODE_SCOPES}" (scopeNodeId, nodeId)`,
7415
+ `CREATE INDEX IF NOT EXISTS idx_knowledge_record_scopes_scope ON "${TABLE_KNOWLEDGE_RECORD_SCOPES}" (scopeNodeId, recordId)`,
7416
+ `CREATE INDEX IF NOT EXISTS idx_knowledge_scope_grants_ref ON "${TABLE_KNOWLEDGE_SCOPE_GRANTS}" (scopeRefId, scopeNodeId)`,
7417
+ `CREATE INDEX IF NOT EXISTS idx_knowledge_node_addresses_node ON "${TABLE_KNOWLEDGE_NODE_ADDRESSES}" (nodeId)`,
7418
+ `CREATE INDEX IF NOT EXISTS idx_knowledge_import_runs_lookup ON "${TABLE_KNOWLEDGE_IMPORT_RUNS}" (importerId, binding, queuedAt DESC)`,
7419
+ `CREATE INDEX IF NOT EXISTS idx_knowledge_activity_import_run ON "${TABLE_KNOWLEDGE_ACTIVITY}" (importRunId, id DESC)`
7420
+ ]) await tx.execute(statement);
7421
+ await tx.execute({
7422
+ sql: `INSERT OR IGNORE INTO "${TABLE_KNOWLEDGE_ACCESS_STATE}" (id, epoch, schemaVersion) VALUES ('global', 0, ?)`,
7423
+ args: [2]
7424
+ });
7425
+ }
7426
+ /**
7427
+ * True when the only Knowledge objects are the tables and indexes published v1 adapters created,
7428
+ * with or without rows. Anything else (v2 tables, unknown tables, views, triggers) needs an
7429
+ * explicit reset.
7430
+ */
7431
+ async #isPublishedV1Layout(executor) {
7432
+ const objects = await executor.execute("SELECT type, name FROM sqlite_master WHERE name LIKE 'mastra\\_knowledge\\_%' ESCAPE '\\' OR tbl_name LIKE 'mastra\\_knowledge\\_%' ESCAPE '\\' OR sql LIKE '%mastra\\_knowledge\\_%' ESCAPE '\\'");
7433
+ for (const row of objects.rows) {
7434
+ const type = String(row.type);
7435
+ const name = String(row.name);
7436
+ if (type === "table" && PUBLISHED_KNOWLEDGE_V1_TABLE_NAMES.has(name)) continue;
7437
+ const knownIndex = PUBLISHED_KNOWLEDGE_V1_INDEX_NAMES.has(name) || name.startsWith("sqlite_autoindex_");
7438
+ if (type !== "index" || !knownIndex) return false;
7439
+ }
7440
+ return true;
6214
7441
  }
6215
7442
  async dangerouslyClearAll() {
6216
7443
  await this.#transaction(async (tx) => {
6217
7444
  for (const table of [
7445
+ TABLE_KNOWLEDGE_RECORD_SCOPES,
7446
+ TABLE_KNOWLEDGE_NODE_SCOPES,
7447
+ TABLE_KNOWLEDGE_SCOPE_GRANTS,
7448
+ TABLE_KNOWLEDGE_SCOPE_ADDRESSES,
7449
+ TABLE_KNOWLEDGE_NODE_ADDRESSES,
6218
7450
  TABLE_KNOWLEDGE_MENTIONS,
6219
- TABLE_KNOWLEDGE_RECORDS,
6220
- TABLE_KNOWLEDGE_NODES,
6221
- TABLE_KNOWLEDGE_CURSORS,
7451
+ TABLE_KNOWLEDGE_PROPOSALS,
6222
7452
  TABLE_KNOWLEDGE_ACTIVITY,
6223
- TABLE_KNOWLEDGE_SEMANTIC_OUTBOX
7453
+ TABLE_KNOWLEDGE_IMPORT_STATE,
7454
+ TABLE_KNOWLEDGE_IMPORT_RUNS,
7455
+ TABLE_KNOWLEDGE_SEMANTIC_OUTBOX,
7456
+ TABLE_KNOWLEDGE_RECORDS,
7457
+ TABLE_KNOWLEDGE_NODES
6224
7458
  ]) await tx.execute(`DELETE FROM "${table}"`);
7459
+ await tx.execute(`UPDATE "${TABLE_KNOWLEDGE_ACCESS_STATE}" SET epoch = 0 WHERE id = 'global'`);
7460
+ });
7461
+ }
7462
+ async dangerouslyReset() {
7463
+ await withClientWriteLock(this.#client, async () => {
7464
+ await this.#client.batch([...RETIRED_KNOWLEDGE_TABLE_NAMES, ...[...KNOWLEDGE_TABLE_NAMES].reverse()].map((table) => ({
7465
+ sql: `DROP TABLE IF EXISTS "${table}"`,
7466
+ args: []
7467
+ })), "write");
7468
+ });
7469
+ await this.init();
7470
+ }
7471
+ async reconcileStructure(plan) {
7472
+ return withReconcileLock(this.getStorageIsolationKey(), () => this.#transaction(async (tx) => {
7473
+ await tx.execute(`UPDATE "${TABLE_KNOWLEDGE_ACCESS_STATE}" SET epoch=epoch WHERE id='global'`);
7474
+ const scopes = {};
7475
+ const createdScopeIds = [];
7476
+ const createdAddresses = /* @__PURE__ */ new Set();
7477
+ const retrofit = plan.retrofit ?? true;
7478
+ let structureChanged = false;
7479
+ const deletedScopeAddresses = /* @__PURE__ */ new Set();
7480
+ const resolveAddress = async (address) => {
7481
+ if (scopes[address]) return scopes[address];
7482
+ const row = (await tx.execute({
7483
+ sql: `SELECT a.scopeNodeId,n.isScope,n.deletedAt FROM "${TABLE_KNOWLEDGE_SCOPE_ADDRESSES}" a JOIN "${TABLE_KNOWLEDGE_NODES}" n ON n.id=a.scopeNodeId WHERE a.address=?`,
7484
+ args: [address]
7485
+ })).rows[0];
7486
+ if (!row) return void 0;
7487
+ if (!row.isScope) throw new Error(`Knowledge address ${address} does not reference a scope`);
7488
+ if (row.deletedAt) deletedScopeAddresses.add(address);
7489
+ scopes[address] = String(row.scopeNodeId);
7490
+ return scopes[address];
7491
+ };
7492
+ for (const scope of plan.scopes) {
7493
+ if (await resolveAddress(scope.address)) continue;
7494
+ const id = randomUUID();
7495
+ const now = (/* @__PURE__ */ new Date()).toISOString();
7496
+ await tx.execute({
7497
+ sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODES}" (id,type,name,canonicalName,kind,description,isScope,metadata,version,createdAt,updatedAt) VALUES (?,'node',?,?,?,?,TRUE,jsonb(?),1,?,?)`,
7498
+ args: [
7499
+ id,
7500
+ scope.name,
7501
+ canonicalName(scope.name),
7502
+ scope.kind ?? null,
7503
+ scope.description ?? null,
7504
+ scope.metadata ? JSON.stringify(scope.metadata) : null,
7505
+ now,
7506
+ now
7507
+ ]
7508
+ });
7509
+ await tx.execute({
7510
+ sql: `INSERT INTO "${TABLE_KNOWLEDGE_SCOPE_ADDRESSES}" (address,scopeNodeId) VALUES (?,?)`,
7511
+ args: [scope.address, id]
7512
+ });
7513
+ scopes[scope.address] = id;
7514
+ createdAddresses.add(scope.address);
7515
+ createdScopeIds.push(id);
7516
+ structureChanged = true;
7517
+ }
7518
+ for (const scope of plan.scopes) {
7519
+ if (!createdAddresses.has(scope.address) && (!retrofit || deletedScopeAddresses.has(scope.address))) continue;
7520
+ const scopeNodeId = scopes[scope.address];
7521
+ for (const parentAddress of scope.parentAddresses ?? []) {
7522
+ const parentId = await resolveAddress(parentAddress);
7523
+ if (!parentId || deletedScopeAddresses.has(parentAddress)) {
7524
+ const deletedParentId = scopes[parentAddress];
7525
+ if ((deletedParentId ? await tx.execute({
7526
+ sql: `SELECT 1 FROM "mastra_knowledge_node_scopes" WHERE nodeId=? AND scopeNodeId=?`,
7527
+ args: [scopeNodeId, deletedParentId]
7528
+ }) : void 0)?.rows.length) continue;
7529
+ throw new Error(`Knowledge parent scope does not exist: ${parentAddress}`);
7530
+ }
7531
+ if ((await tx.execute({
7532
+ sql: `SELECT 1 FROM "mastra_knowledge_node_scopes" WHERE nodeId=? AND scopeNodeId=?`,
7533
+ args: [scopeNodeId, parentId]
7534
+ })).rows.length) continue;
7535
+ if ((await tx.execute({
7536
+ sql: `SELECT n.id FROM "mastra_knowledge_node_scopes" ns JOIN "${TABLE_KNOWLEDGE_NODES}" n ON n.id=ns.nodeId WHERE ns.scopeNodeId=? AND n.isScope AND n.canonicalName=? AND n.deletedAt IS NULL AND n.id<>? LIMIT 1`,
7537
+ args: [
7538
+ parentId,
7539
+ canonicalName(scope.name),
7540
+ scopeNodeId
7541
+ ]
7542
+ })).rows.length) throw new Error(`Knowledge scope name ${scope.name} already exists under ${parentAddress}`);
7543
+ const inserted = await tx.execute({
7544
+ sql: `INSERT OR IGNORE INTO "${TABLE_KNOWLEDGE_NODE_SCOPES}" (nodeId,scopeNodeId,addedAt) VALUES (?,?,?)`,
7545
+ args: [
7546
+ scopeNodeId,
7547
+ parentId,
7548
+ (/* @__PURE__ */ new Date()).toISOString()
7549
+ ]
7550
+ });
7551
+ structureChanged ||= inserted.rowsAffected > 0;
7552
+ }
7553
+ for (const grant of scope.grants ?? []) {
7554
+ const scopeRefId = await resolveAddress(grant.scopeRefAddress);
7555
+ if (!scopeRefId || deletedScopeAddresses.has(grant.scopeRefAddress)) {
7556
+ const deletedScopeRefId = scopes[grant.scopeRefAddress];
7557
+ if ((deletedScopeRefId ? await tx.execute({
7558
+ sql: `SELECT 1 FROM "mastra_knowledge_scope_grants" WHERE scopeNodeId=? AND scopeRefId=?`,
7559
+ args: [scopeNodeId, deletedScopeRefId]
7560
+ }) : void 0)?.rows.length) continue;
7561
+ throw new Error(`Knowledge grant scope does not exist: ${grant.scopeRefAddress}`);
7562
+ }
7563
+ const inserted = await tx.execute({
7564
+ sql: `INSERT OR IGNORE INTO "${TABLE_KNOWLEDGE_SCOPE_GRANTS}" (scopeNodeId,scopeRefId,role,canSuggest) VALUES (?,?,?,?)`,
7565
+ args: [
7566
+ scopeNodeId,
7567
+ scopeRefId,
7568
+ grant.role,
7569
+ grant.canSuggest ?? null
7570
+ ]
7571
+ });
7572
+ structureChanged ||= inserted.rowsAffected > 0;
7573
+ }
7574
+ }
7575
+ if (structureChanged) await tx.execute(`UPDATE "${TABLE_KNOWLEDGE_ACCESS_STATE}" SET epoch=epoch+1 WHERE id='global'`);
7576
+ const state = await tx.execute(`SELECT epoch FROM "${TABLE_KNOWLEDGE_ACCESS_STATE}" WHERE id='global'`);
7577
+ return {
7578
+ scopes,
7579
+ createdScopeIds,
7580
+ deletedScopeAddresses: [...deletedScopeAddresses],
7581
+ changed: structureChanged,
7582
+ accessEpoch: Number(state.rows[0]?.epoch ?? 0)
7583
+ };
7584
+ }));
7585
+ }
7586
+ async listScopeNodes(input = {}) {
7587
+ const { limit, after } = parseListKnowledgeScopeNodesInput(input);
7588
+ if (input.addresses?.length === 0 || input.ids?.length === 0) return {
7589
+ scopes: [],
7590
+ nextCursor: null
7591
+ };
7592
+ const args = [];
7593
+ const where = [`n.isScope`, `n.deletedAt IS NULL`];
7594
+ let within = "";
7595
+ if (input.withinAddress !== void 0) {
7596
+ within = `WITH RECURSIVE within_scope(id) AS (SELECT r.id FROM "${TABLE_KNOWLEDGE_NODES}" r JOIN "${TABLE_KNOWLEDGE_SCOPE_ADDRESSES}" ra ON ra.scopeNodeId=r.id WHERE ra.address=? AND r.isScope AND r.deletedAt IS NULL UNION SELECT ns.nodeId FROM "${TABLE_KNOWLEDGE_NODE_SCOPES}" ns JOIN within_scope w ON ns.scopeNodeId=w.id JOIN "${TABLE_KNOWLEDGE_NODES}" c ON c.id=ns.nodeId WHERE c.isScope AND c.deletedAt IS NULL) `;
7597
+ args.push(input.withinAddress);
7598
+ where.push("n.id IN (SELECT id FROM within_scope)");
7599
+ }
7600
+ if (input.addresses) {
7601
+ where.push(`a.address IN (${input.addresses.map(() => "?").join(",")})`);
7602
+ args.push(...input.addresses);
7603
+ }
7604
+ if (input.ids) {
7605
+ where.push(`n.id IN (${input.ids.map(() => "?").join(",")})`);
7606
+ args.push(...input.ids);
7607
+ }
7608
+ if (after) {
7609
+ where.push("(n.name > ? OR (n.name = ? AND n.id > ?))");
7610
+ args.push(after.name, after.name, after.id);
7611
+ }
7612
+ args.push(limit + 1);
7613
+ const rows = (await this.#client.execute({
7614
+ sql: `${within}SELECT n.id,n.name,n.kind,n.description,a.address FROM "${TABLE_KNOWLEDGE_NODES}" n LEFT JOIN "${TABLE_KNOWLEDGE_SCOPE_ADDRESSES}" a ON a.scopeNodeId=n.id WHERE ${where.join(" AND ")} ORDER BY n.name, n.id LIMIT ?`,
7615
+ args
7616
+ })).rows;
7617
+ if (rows.length === 0) return {
7618
+ scopes: [],
7619
+ nextCursor: null
7620
+ };
7621
+ const parents = await this.#client.execute({
7622
+ sql: `SELECT ns.nodeId,ns.scopeNodeId FROM "${TABLE_KNOWLEDGE_NODE_SCOPES}" ns WHERE ns.nodeId IN (${rows.map(() => "?").join(",")}) ORDER BY ns.scopeNodeId`,
7623
+ args: rows.map((row) => String(row.id))
6225
7624
  });
7625
+ const parentsByScopeId = /* @__PURE__ */ new Map();
7626
+ for (const row of parents.rows) {
7627
+ const scopeId = String(row.nodeId);
7628
+ const parentId = String(row.scopeNodeId);
7629
+ const existing = parentsByScopeId.get(scopeId);
7630
+ if (existing) existing.push(parentId);
7631
+ else parentsByScopeId.set(scopeId, [parentId]);
7632
+ }
7633
+ return pageKnowledgeScopeNodes(rows.map((row) => ({
7634
+ id: String(row.id),
7635
+ address: String(row.address),
7636
+ name: String(row.name),
7637
+ ...row.kind == null ? {} : { kind: String(row.kind) },
7638
+ ...row.description == null ? {} : { description: String(row.description) },
7639
+ parentIds: parentsByScopeId.get(String(row.id)) ?? []
7640
+ })), limit, input);
7641
+ }
7642
+ async listScopeMembers(input) {
7643
+ const { limit, after } = parseListKnowledgeScopeMembersInput(input);
7644
+ const args = [input.scopeNodeId];
7645
+ let keyset = "";
7646
+ if (after) {
7647
+ const updatedAt = after.updatedAt.toISOString();
7648
+ keyset = ` AND (n.updatedAt < ? OR (n.updatedAt = ? AND (n.name > ? OR (n.name = ? AND n.id > ?))))`;
7649
+ args.push(updatedAt, updatedAt, after.name, after.name, after.id);
7650
+ }
7651
+ args.push(limit + 1);
7652
+ return pageKnowledgeScopeMembers((await this.#client.execute({
7653
+ sql: `SELECT *, json(scope) AS scopeJson FROM "${TABLE_KNOWLEDGE_NODES}" n WHERE EXISTS (SELECT 1 FROM "${TABLE_KNOWLEDGE_NODE_SCOPES}" ns WHERE ns.nodeId=n.id AND ns.scopeNodeId=?) AND n.deletedAt IS NULL AND n.mergedInto IS NULL${keyset} ORDER BY n.updatedAt DESC, n.name ASC, n.id ASC LIMIT ?`,
7654
+ args
7655
+ })).rows.map(parseNode), limit, input);
6226
7656
  }
6227
7657
  async createNode(input) {
6228
7658
  await assertKnowledgeDescriptionWithinBoundCompat(input.description);
@@ -6232,6 +7662,7 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
6232
7662
  if (existing) {
6233
7663
  const terminal = await this.#resolveTerminalNode(tx, existing.id);
6234
7664
  if (!isKnowledgeScopeVisible(terminal.scope, scope)) throw new Error(`Merged knowledge node is not visible from scope: ${input.name}`);
7665
+ await this.#placeNodeInScopes(tx, terminal.id, input.scopeAddresses, /* @__PURE__ */ new Date());
6235
7666
  return terminal;
6236
7667
  }
6237
7668
  const now = /* @__PURE__ */ new Date();
@@ -6248,7 +7679,7 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
6248
7679
  updatedAt: now
6249
7680
  };
6250
7681
  await tx.execute({
6251
- sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODES}" (id,type,name,canonicalName,kind,content,description,scope,scopeKey,version,mergedInto,createdAt,updatedAt) VALUES (?,?,?,?,?,?,?,jsonb(?),?,?,NULL,?,?)`,
7682
+ sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODES}" (id,type,name,canonicalName,kind,content,description,isScope,scope,scopeKey,version,mergedInto,createdAt,updatedAt) VALUES (?,?,?,?,?,?,?,FALSE,jsonb(?),?,?,NULL,?,?)`,
6252
7683
  args: [
6253
7684
  node.id,
6254
7685
  "node",
@@ -6264,6 +7695,8 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
6264
7695
  now.toISOString()
6265
7696
  ]
6266
7697
  });
7698
+ await this.#replaceNodeScopes(tx, node.id, scope, now);
7699
+ await this.#placeNodeInScopes(tx, node.id, input.scopeAddresses, now);
6267
7700
  await this.#replaceMentions(tx, "node", node.id, node.content ?? "", input.resolutionScope ?? scope, scope);
6268
7701
  await this.#activity(tx, "node-created", "node", node.id, scope);
6269
7702
  await this.#outbox(tx, "node", node.id, "upsert", node.version, scope);
@@ -6280,13 +7713,13 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
6280
7713
  return this.#resolveNode(this.#client, input.name, canonicalizeKnowledgeScope(input.scope));
6281
7714
  }
6282
7715
  async listNodes(input) {
6283
- const key = knowledgeScopeKey(canonicalizeKnowledgeScope(input.scope));
7716
+ const keys = visibleScopeKeys(canonicalizeKnowledgeScope(input.scope));
6284
7717
  const clauses = [
6285
7718
  `type = 'node'`,
6286
7719
  "mergedInto IS NULL",
6287
- visibleSql
7720
+ visibleSql(keys)
6288
7721
  ];
6289
- const args = [key, key];
7722
+ const args = [...keys];
6290
7723
  if (input.namePrefix) {
6291
7724
  clauses.push("canonicalName LIKE ? ESCAPE '='");
6292
7725
  args.push(`${escapeLikePattern(canonicalName(input.namePrefix))}%`);
@@ -6338,6 +7771,7 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
6338
7771
  input.version
6339
7772
  ]
6340
7773
  })).rowsAffected === 0) throw new KnowledgeConflictError(input.id);
7774
+ if (knowledgeScopeKey(existing.scope) !== knowledgeScopeKey(scope)) await this.#swapIdentityScopes(tx, input.id, existing.scope, scope, now);
6341
7775
  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);
6342
7776
  await this.#activity(tx, "node-updated", "node", input.id, scope);
6343
7777
  if (knowledgeScopeKey(existing.scope) !== knowledgeScopeKey(scope)) {
@@ -6392,8 +7826,12 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
6392
7826
  ]
6393
7827
  })).rowsAffected === 0) throw new KnowledgeConflictError(source.id);
6394
7828
  await tx.execute({
6395
- sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET node=? WHERE node=?`,
6396
- args: [target.id, source.id]
7829
+ sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET node=?,version=version+1,updatedAt=? WHERE node=?`,
7830
+ args: [
7831
+ target.id,
7832
+ (/* @__PURE__ */ new Date()).toISOString(),
7833
+ source.id
7834
+ ]
6397
7835
  });
6398
7836
  await tx.execute({
6399
7837
  sql: `DELETE FROM "${TABLE_KNOWLEDGE_MENTIONS}" WHERE recordId=? AND EXISTS (SELECT 1 FROM "${TABLE_KNOWLEDGE_MENTIONS}" target WHERE target.sourceType="${TABLE_KNOWLEDGE_MENTIONS}".sourceType AND target.sourceId="${TABLE_KNOWLEDGE_MENTIONS}".sourceId AND target.recordId=?)`,
@@ -6433,10 +7871,10 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
6433
7871
  });
6434
7872
  }
6435
7873
  async appendKnowledge(input) {
7874
+ await assertKnowledgeRecordTextWithinBoundCompat(input.text);
6436
7875
  const scope = canonicalizeKnowledgeScope(input.scope);
6437
7876
  const resolutionScope = canonicalizeKnowledgeScope(input.resolutionScope);
6438
7877
  const defaultScope = canonicalizeKnowledgeScope(input.defaultScope);
6439
- assertKnowledgeScopeWithinCeiling(scope, input.maxScope);
6440
7878
  return this.#transaction(async (tx) => {
6441
7879
  const node = nodeReferenceId(input.node);
6442
7880
  const parent = await this.#resolveTerminalNode(tx, node);
@@ -6449,11 +7887,10 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
6449
7887
  sourceThreadId: input.sourceThreadId,
6450
7888
  capturedAt: /* @__PURE__ */ new Date(),
6451
7889
  when: input.when ? new Date(input.when) : void 0,
6452
- maxScope: input.maxScope,
6453
7890
  metadata: input.metadata
6454
7891
  };
6455
7892
  await tx.execute({
6456
- sql: `INSERT INTO "${TABLE_KNOWLEDGE_RECORDS}" (id,node,text,scope,scopeKey,sourceThreadId,capturedAt,"when",maxScope,metadata,deletedAt,deletedBy) VALUES (?,?,?,jsonb(?),?,?,?,?,?,jsonb(?),NULL,NULL)`,
7893
+ sql: `INSERT INTO "${TABLE_KNOWLEDGE_RECORDS}" (id,node,text,scope,scopeKey,sourceThreadId,capturedAt,"when",metadata,version,updatedAt,deletedAt,deletedBy) VALUES (?,?,?,jsonb(?),?,?,?,?,jsonb(?),?,?,NULL,NULL)`,
6457
7894
  args: [
6458
7895
  record.id,
6459
7896
  record.node,
@@ -6463,10 +7900,12 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
6463
7900
  record.sourceThreadId,
6464
7901
  record.capturedAt.toISOString(),
6465
7902
  record.when?.toISOString() ?? null,
6466
- record.maxScope ?? null,
6467
- record.metadata ? JSON.stringify(record.metadata) : null
7903
+ record.metadata ? JSON.stringify(record.metadata) : null,
7904
+ 1,
7905
+ record.capturedAt.toISOString()
6468
7906
  ]
6469
7907
  });
7908
+ await this.#replaceRecordScopes(tx, record.id, scope, record.capturedAt);
6470
7909
  await this.#replaceMentions(tx, "record", record.id, record.text, resolutionScope, defaultScope);
6471
7910
  await this.#activity(tx, "record-created", "record", record.id, scope, record.sourceThreadId);
6472
7911
  await this.#outbox(tx, "record", record.id, "upsert", record.id, scope);
@@ -6490,17 +7929,13 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
6490
7929
  return this.#queryKnowledge(input, "related");
6491
7930
  }
6492
7931
  async knowledgeBySource(input) {
6493
- const key = knowledgeScopeKey(canonicalizeKnowledgeScope(input.scope));
6494
- const args = [
6495
- input.sourceThreadId,
6496
- key,
6497
- key
6498
- ];
7932
+ const keys = visibleScopeKeys(canonicalizeKnowledgeScope(input.scope));
7933
+ const args = [input.sourceThreadId, ...keys];
6499
7934
  if (input.after) args.push(input.after);
6500
7935
  const limit = input.limit ?? 100;
6501
7936
  args.push(limit + 1);
6502
7937
  const records = (await this.#client.execute({
6503
- sql: `SELECT *,json(scope) AS scopeJson,json(metadata) AS metadataJson FROM "${TABLE_KNOWLEDGE_RECORDS}" WHERE sourceThreadId=? AND ${visibleSql}${input.includeDeleted ? "" : " AND deletedAt IS NULL"}${input.after ? " AND id > ?" : ""} ORDER BY id ASC LIMIT ?`,
7938
+ sql: `SELECT *,json(scope) AS scopeJson,json(metadata) AS metadataJson FROM "${TABLE_KNOWLEDGE_RECORDS}" WHERE sourceThreadId=? AND ${visibleSql(keys)}${input.includeDeleted ? "" : " AND deletedAt IS NULL"}${input.after ? " AND id > ?" : ""} ORDER BY id ASC LIMIT ?`,
6504
7939
  args
6505
7940
  })).rows.map(parseKnowledge);
6506
7941
  return {
@@ -6515,10 +7950,11 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
6515
7950
  if (record.deletedAt) return record;
6516
7951
  const deletedAt = /* @__PURE__ */ new Date();
6517
7952
  await tx.execute({
6518
- sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET deletedAt=?,deletedBy=? WHERE id=? AND deletedAt IS NULL`,
7953
+ sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET deletedAt=?,deletedBy=?,version=version+1,updatedAt=? WHERE id=? AND deletedAt IS NULL`,
6519
7954
  args: [
6520
7955
  deletedAt.toISOString(),
6521
7956
  input.deletedBy,
7957
+ deletedAt.toISOString(),
6522
7958
  input.id
6523
7959
  ]
6524
7960
  });
@@ -6537,8 +7973,8 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
6537
7973
  if (!record) throw new KnowledgeNotFoundError("record", input.id);
6538
7974
  if (!record.deletedAt) return record;
6539
7975
  await tx.execute({
6540
- sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET deletedAt=NULL,deletedBy=NULL WHERE id=?`,
6541
- args: [input.id]
7976
+ sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET deletedAt=NULL,deletedBy=NULL,version=version+1,updatedAt=? WHERE id=?`,
7977
+ args: [(/* @__PURE__ */ new Date()).toISOString(), input.id]
6542
7978
  });
6543
7979
  await this.#activity(tx, "record-restored", "record", input.id, record.scope, record.sourceThreadId);
6544
7980
  await this.#outbox(tx, "record", input.id, "upsert", createKnowledgeUlid(), record.scope);
@@ -6554,15 +7990,17 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
6554
7990
  return this.#transaction(async (tx) => {
6555
7991
  const record = await this.#getKnowledge(tx, input.id, true);
6556
7992
  if (!record) throw new KnowledgeNotFoundError("record", input.id);
6557
- assertKnowledgeScopeWithinCeiling(scope, record.maxScope);
7993
+ const updatedAt = /* @__PURE__ */ new Date();
6558
7994
  await tx.execute({
6559
- sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET scope=jsonb(?),scopeKey=? WHERE id=?`,
7995
+ sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET scope=jsonb(?),scopeKey=?,version=version+1,updatedAt=? WHERE id=?`,
6560
7996
  args: [
6561
7997
  JSON.stringify(scope),
6562
7998
  knowledgeScopeKey(scope),
7999
+ updatedAt.toISOString(),
6563
8000
  input.id
6564
8001
  ]
6565
8002
  });
8003
+ await this.#replaceRecordScopes(tx, input.id, scope, updatedAt);
6566
8004
  await this.#activity(tx, "record-rescoped", "record", input.id, scope, record.sourceThreadId);
6567
8005
  if (knowledgeScopeKey(record.scope) !== knowledgeScopeKey(scope)) await this.#outbox(tx, "record", input.id, "delete", createKnowledgeUlid(), record.scope);
6568
8006
  if (!record.deletedAt) await this.#outbox(tx, "record", input.id, "upsert", createKnowledgeUlid(), scope);
@@ -6572,32 +8010,16 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
6572
8010
  };
6573
8011
  });
6574
8012
  }
6575
- async raiseKnowledgeCeiling(input) {
6576
- return this.#transaction(async (tx) => {
6577
- const record = await this.#getKnowledge(tx, input.id, true);
6578
- if (!record) throw new KnowledgeNotFoundError("record", input.id);
6579
- assertKnowledgeScopeWithinCeiling(record.scope, input.maxScope);
6580
- assertKnowledgeCeilingRaised(record.maxScope, input.maxScope);
6581
- await tx.execute({
6582
- sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET maxScope=? WHERE id=?`,
6583
- args: [input.maxScope ?? null, input.id]
6584
- });
6585
- return {
6586
- ...record,
6587
- maxScope: input.maxScope
6588
- };
6589
- });
6590
- }
6591
8013
  async search(input) {
6592
- const key = knowledgeScopeKey(canonicalizeKnowledgeScope(input.scope));
8014
+ const scope = canonicalizeKnowledgeScope(input.scope);
8015
+ const keys = visibleScopeKeys(scope);
6593
8016
  const normalizedQuery = input.query.trim().toLocaleLowerCase();
6594
8017
  if (!normalizedQuery) return [];
6595
8018
  const query = `%${escapeLikePattern(normalizedQuery)}%`;
6596
8019
  const results = (await this.#client.execute({
6597
- sql: `SELECT *,json(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 '=' OR lower(COALESCE(description,'')) LIKE ? ESCAPE '=') ORDER BY updatedAt DESC LIMIT ?`,
8020
+ sql: `SELECT *,json(scope) AS scopeJson FROM "${TABLE_KNOWLEDGE_NODES}" WHERE mergedInto IS NULL AND ${visibleSql(keys)} AND (canonicalName LIKE ? ESCAPE '=' OR lower(COALESCE(kind,'')) LIKE ? ESCAPE '=' OR lower(COALESCE(content,'')) LIKE ? ESCAPE '=' OR lower(COALESCE(description,'')) LIKE ? ESCAPE '=') ORDER BY updatedAt DESC LIMIT ?`,
6598
8021
  args: [
6599
- key,
6600
- key,
8022
+ ...keys,
6601
8023
  query,
6602
8024
  query,
6603
8025
  query,
@@ -6618,60 +8040,47 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
6618
8040
  }));
6619
8041
  if (results.length < (input.limit ?? 20)) {
6620
8042
  const knowledge = await this.#client.execute({
6621
- sql: `SELECT k.*,json(k.scope) AS scopeJson,json(k.metadata) AS metadataJson,n.name FROM "${TABLE_KNOWLEDGE_RECORDS}" k JOIN "${TABLE_KNOWLEDGE_NODES}" n ON n.id=k.node AND n.type='node' AND n.mergedInto IS NULL WHERE k.deletedAt IS NULL AND ${visibleSql.replaceAll("scopeKey", "k.scopeKey")} AND lower(k.text) LIKE ? ESCAPE '=' ORDER BY k.id DESC LIMIT ?`,
8043
+ sql: `SELECT k.*,json(k.scope) AS scopeJson,json(k.metadata) AS metadataJson,n.name,json(n.scope) AS parentScopeJson FROM "${TABLE_KNOWLEDGE_RECORDS}" k CROSS JOIN "${TABLE_KNOWLEDGE_NODES}" n ON n.id=k.node AND n.type='node' AND n.mergedInto IS NULL WHERE k.deletedAt IS NULL AND ${visibleSql(keys, "k.scopeKey")} AND lower(k.text) LIKE ? ESCAPE '=' ORDER BY k.id DESC LIMIT ?`,
6622
8044
  args: [
6623
- key,
6624
- key,
8045
+ ...keys,
6625
8046
  query,
6626
8047
  (input.limit ?? 20) - results.length
6627
8048
  ]
6628
8049
  });
6629
- results.push(...knowledge.rows.map((row) => ({
6630
- type: "record",
6631
- id: String(row.id),
6632
- recordId: String(row.node),
6633
- name: String(row.name),
6634
- text: String(row.text),
6635
- scope: parseJson$3(row.scopeJson)
6636
- })));
8050
+ results.push(...knowledge.rows.map((row) => {
8051
+ const parentVisible = isKnowledgeScopeVisible(parseJson$3(row.parentScopeJson), scope);
8052
+ return {
8053
+ type: "record",
8054
+ id: String(row.id),
8055
+ recordId: parentVisible ? String(row.node) : String(row.id),
8056
+ name: parentVisible ? String(row.name) : "(private node)",
8057
+ text: String(row.text),
8058
+ scope: parseJson$3(row.scopeJson)
8059
+ };
8060
+ }));
6637
8061
  }
6638
8062
  return results;
6639
8063
  }
6640
- async getCurationCursor(input) {
6641
- const row = (await this.#client.execute({
6642
- sql: `SELECT * FROM "${TABLE_KNOWLEDGE_CURSORS}" WHERE sourceThreadId=? AND agent=?`,
6643
- args: [input.sourceThreadId, input.agent]
6644
- })).rows[0];
6645
- return row ? {
6646
- sourceThreadId: String(row.sourceThreadId),
6647
- agent: String(row.agent),
6648
- lastKnowledgeId: String(row.lastKnowledgeId),
6649
- updatedAt: toDate(row.updatedAt)
6650
- } : null;
6651
- }
6652
- async advanceCurationCursor(input) {
6653
- const updatedAt = /* @__PURE__ */ new Date();
6654
- if ((await this.#db.executeWriteOperationWithRetry(() => withClientWriteLock(this.#client, () => this.#client.execute({
6655
- sql: `INSERT INTO "${TABLE_KNOWLEDGE_CURSORS}" (sourceThreadId,agent,lastKnowledgeId,updatedAt) VALUES (?,?,?,?) ON CONFLICT(sourceThreadId,agent) DO UPDATE SET lastKnowledgeId=excluded.lastKnowledgeId,updatedAt=excluded.updatedAt WHERE excluded.lastKnowledgeId >= "${TABLE_KNOWLEDGE_CURSORS}".lastKnowledgeId`,
6656
- args: [
6657
- input.sourceThreadId,
6658
- input.agent,
6659
- input.lastKnowledgeId,
6660
- updatedAt.toISOString()
6661
- ]
6662
- })), "advance knowledge curation cursor")).rowsAffected === 0) throw new Error("Knowledge curation cursor cannot move backwards");
6663
- return {
6664
- ...input,
6665
- updatedAt
6666
- };
8064
+ /**
8065
+ * @deprecated Curation cursors were removed. Observation-time curate is the only Knowledge writer and needs no
8066
+ * cursor. Always throws.
8067
+ */
8068
+ async getCurationCursor(_input) {
8069
+ throw new Error(KNOWLEDGE_CURATION_CURSOR_REMOVED_MESSAGE);
8070
+ }
8071
+ /**
8072
+ * @deprecated Curation cursors were removed. Observation-time curate is the only Knowledge writer and needs no
8073
+ * cursor. Always throws.
8074
+ */
8075
+ async advanceCurationCursor(_input) {
8076
+ throw new Error(KNOWLEDGE_CURATION_CURSOR_REMOVED_MESSAGE);
6667
8077
  }
6668
8078
  async listActivity(input) {
6669
- const key = knowledgeScopeKey(canonicalizeKnowledgeScope(input.scope));
8079
+ const keys = visibleScopeKeys(canonicalizeKnowledgeScope(input.scope));
6670
8080
  return (await this.#client.execute({
6671
- sql: `SELECT *,json(scope) AS scopeJson FROM "${TABLE_KNOWLEDGE_ACTIVITY}" WHERE ${visibleSql}${input.after ? " AND id < ?" : ""} ORDER BY id DESC LIMIT ?`,
8081
+ sql: `SELECT *,json(scope) AS scopeJson FROM "${TABLE_KNOWLEDGE_ACTIVITY}" WHERE ${visibleSql(keys)}${input.after ? " AND id < ?" : ""} ORDER BY id DESC LIMIT ?`,
6672
8082
  args: [
6673
- key,
6674
- key,
8083
+ ...keys,
6675
8084
  ...input.after ? [input.after] : [],
6676
8085
  input.limit ?? 100
6677
8086
  ]
@@ -6693,9 +8102,9 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
6693
8102
  args.push(input.status);
6694
8103
  }
6695
8104
  if (input.scope) {
6696
- const key = knowledgeScopeKey(canonicalizeKnowledgeScope(input.scope));
6697
- clauses.push(visibleSql);
6698
- args.push(key, key);
8105
+ const keys = visibleScopeKeys(input.scope);
8106
+ clauses.push(visibleSql(keys));
8107
+ args.push(...keys);
6699
8108
  }
6700
8109
  args.push(input.limit ?? 100);
6701
8110
  return (await this.#client.execute({
@@ -6714,9 +8123,9 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
6714
8123
  ];
6715
8124
  const args = [now.toISOString(), stale.toISOString()];
6716
8125
  if (input.scope) {
6717
- const key = knowledgeScopeKey(canonicalizeKnowledgeScope(input.scope));
6718
- clauses.push(visibleSql);
6719
- args.push(key, key);
8126
+ const keys = visibleScopeKeys(input.scope);
8127
+ clauses.push(visibleSql(keys));
8128
+ args.push(...keys);
6720
8129
  }
6721
8130
  args.push(input.limit ?? 100);
6722
8131
  const ids = (await tx.execute({
@@ -6793,12 +8202,14 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
6793
8202
  return result.rows[0] ? parseNode(result.rows[0]) : null;
6794
8203
  }
6795
8204
  async #resolveNode(executor, name, scope) {
6796
- for (let length = scope.length; length > 0; length--) {
6797
- const node = await this.#getNodeByName(executor, name, scope.slice(0, length));
6798
- if (node) {
6799
- const terminal = await this.#resolveTerminalNode(executor, node.id);
6800
- if (terminal && isKnowledgeScopeVisible(terminal.scope, scope)) return terminal;
6801
- }
8205
+ const keys = visibleScopeKeys(scope);
8206
+ const candidates = (await executor.execute({
8207
+ sql: `SELECT *,json(scope) AS scopeJson FROM "${TABLE_KNOWLEDGE_NODES}" WHERE type='node' AND canonicalName=? AND (scopeKey IN (${keys.map(() => "?").join(",")}) OR mergedInto IS NOT NULL)`,
8208
+ args: [canonicalName(name), ...keys]
8209
+ })).rows.map(parseNode).sort((left, right) => right.scope.length - left.scope.length);
8210
+ for (const candidate of candidates) {
8211
+ const terminal = await this.#resolveTerminalNode(executor, candidate.id);
8212
+ if (terminal && isKnowledgeScopeVisible(terminal.scope, scope)) return terminal;
6802
8213
  }
6803
8214
  return null;
6804
8215
  }
@@ -6823,19 +8234,18 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
6823
8234
  const scope = canonicalizeKnowledgeScope(input.scope);
6824
8235
  const node = await this.#resolveTerminalNode(this.#client, nodeReferenceId(input.node));
6825
8236
  if (!node) return { records: [] };
6826
- const key = knowledgeScopeKey(scope);
8237
+ const keys = visibleScopeKeys(scope);
6827
8238
  const includesMentions = relationship !== "about";
6828
8239
  const relationSql = relationship === "about" ? "k.node=?" : relationship === "mentioning" ? "m.recordId=?" : "(k.node=? OR m.recordId=?)";
6829
8240
  const args = [
6830
8241
  node.id,
6831
8242
  ...relationship === "related" ? [node.id] : [],
6832
- key,
6833
- key
8243
+ ...keys
6834
8244
  ];
6835
8245
  if (input.after) args.push(input.after);
6836
8246
  args.push((input.limit ?? 100) + 1);
6837
8247
  const result = await this.#client.execute({
6838
- sql: `SELECT DISTINCT k.*,json(k.scope) AS scopeJson,json(k.metadata) AS metadataJson FROM "${TABLE_KNOWLEDGE_RECORDS}" k${includesMentions ? ` LEFT JOIN "${TABLE_KNOWLEDGE_MENTIONS}" m ON m.sourceType='record' AND m.sourceId=k.id` : ""} WHERE ${relationSql} AND ${visibleSql.replaceAll("scopeKey", "k.scopeKey")}${input.includeDeleted ? "" : " AND k.deletedAt IS NULL"}${input.after ? " AND k.id < ?" : ""} ORDER BY k.id DESC LIMIT ?`,
8248
+ sql: `SELECT DISTINCT k.*,json(k.scope) AS scopeJson,json(k.metadata) AS metadataJson FROM "${TABLE_KNOWLEDGE_RECORDS}" k${includesMentions ? ` LEFT JOIN "${TABLE_KNOWLEDGE_MENTIONS}" m ON m.sourceType='record' AND m.sourceId=k.id` : ""} WHERE ${relationSql} AND ${visibleSql(keys, "k.scopeKey")}${input.includeDeleted ? "" : " AND k.deletedAt IS NULL"}${input.after ? " AND k.id < ?" : ""} ORDER BY k.id DESC LIMIT ?`,
6839
8249
  args
6840
8250
  });
6841
8251
  const limit = input.limit ?? 100;
@@ -6845,6 +8255,90 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
6845
8255
  nextCursor: result.rows.length > limit ? String(rows.at(-1)?.id) : void 0
6846
8256
  };
6847
8257
  }
8258
+ async #resolveScopeNodeIds(executor, addresses) {
8259
+ const scopeNodeIds = /* @__PURE__ */ new Set();
8260
+ for (const address of addresses) {
8261
+ const result = await executor.execute({
8262
+ sql: `SELECT scopeNodeId FROM "${TABLE_KNOWLEDGE_SCOPE_ADDRESSES}" WHERE address=?`,
8263
+ args: [address]
8264
+ });
8265
+ if (result.rows[0]?.scopeNodeId != null) scopeNodeIds.add(String(result.rows[0].scopeNodeId));
8266
+ }
8267
+ return [...scopeNodeIds];
8268
+ }
8269
+ async #replaceNodeScopes(executor, nodeId, addresses, addedAt) {
8270
+ await executor.execute({
8271
+ sql: `DELETE FROM "${TABLE_KNOWLEDGE_NODE_SCOPES}" WHERE nodeId=?`,
8272
+ args: [nodeId]
8273
+ });
8274
+ for (const scopeNodeId of await this.#resolveScopeNodeIds(executor, addresses)) await executor.execute({
8275
+ sql: `INSERT OR IGNORE INTO "${TABLE_KNOWLEDGE_NODE_SCOPES}" (nodeId,scopeNodeId,addedAt) VALUES (?,?,?)`,
8276
+ args: [
8277
+ nodeId,
8278
+ scopeNodeId,
8279
+ addedAt.toISOString()
8280
+ ]
8281
+ });
8282
+ }
8283
+ /**
8284
+ * Move a node's identity memberships from `from` to `to` without touching structural
8285
+ * placements added by `#placeNodeInScopes`, which a scope change must not erase.
8286
+ */
8287
+ async #swapIdentityScopes(executor, nodeId, from, to, addedAt) {
8288
+ const next = await this.#resolveScopeNodeIds(executor, to);
8289
+ for (const scopeNodeId of await this.#resolveScopeNodeIds(executor, from)) {
8290
+ if (next.includes(scopeNodeId)) continue;
8291
+ await executor.execute({
8292
+ sql: `DELETE FROM "${TABLE_KNOWLEDGE_NODE_SCOPES}" WHERE nodeId=? AND scopeNodeId=?`,
8293
+ args: [nodeId, scopeNodeId]
8294
+ });
8295
+ }
8296
+ for (const scopeNodeId of next) await executor.execute({
8297
+ sql: `INSERT OR IGNORE INTO "${TABLE_KNOWLEDGE_NODE_SCOPES}" (nodeId,scopeNodeId,addedAt) VALUES (?,?,?)`,
8298
+ args: [
8299
+ nodeId,
8300
+ scopeNodeId,
8301
+ addedAt.toISOString()
8302
+ ]
8303
+ });
8304
+ }
8305
+ /**
8306
+ * Additive structural placement: every address must resolve to a live scope node
8307
+ * (unlike identity membership, an unknown address is a caller error, not a lazy
8308
+ * materialization miss). Runs inside the caller's transaction, so a throw aborts
8309
+ * the whole mutation.
8310
+ */
8311
+ async #placeNodeInScopes(executor, nodeId, addresses, addedAt) {
8312
+ for (const address of addresses ?? []) {
8313
+ const scopeNodeId = (await executor.execute({
8314
+ sql: `SELECT sa.scopeNodeId AS scopeNodeId FROM "${TABLE_KNOWLEDGE_SCOPE_ADDRESSES}" sa JOIN "${TABLE_KNOWLEDGE_NODES}" n ON n.id=sa.scopeNodeId WHERE sa.address=? AND n.isScope AND n.deletedAt IS NULL`,
8315
+ args: [address]
8316
+ })).rows[0]?.scopeNodeId;
8317
+ if (scopeNodeId == null) throw new KnowledgeNotFoundError("scope", address);
8318
+ await executor.execute({
8319
+ sql: `INSERT OR IGNORE INTO "${TABLE_KNOWLEDGE_NODE_SCOPES}" (nodeId,scopeNodeId,addedAt) VALUES (?,?,?)`,
8320
+ args: [
8321
+ nodeId,
8322
+ String(scopeNodeId),
8323
+ addedAt.toISOString()
8324
+ ]
8325
+ });
8326
+ }
8327
+ }
8328
+ async #replaceRecordScopes(executor, recordId, addresses, addedAt) {
8329
+ await executor.execute({
8330
+ sql: `DELETE FROM "${TABLE_KNOWLEDGE_RECORD_SCOPES}" WHERE recordId=?`,
8331
+ args: [recordId]
8332
+ });
8333
+ for (const scopeNodeId of await this.#resolveScopeNodeIds(executor, addresses)) await executor.execute({
8334
+ sql: `INSERT OR IGNORE INTO "${TABLE_KNOWLEDGE_RECORD_SCOPES}" (recordId,scopeNodeId,addedAt) VALUES (?,?,?)`,
8335
+ args: [
8336
+ recordId,
8337
+ scopeNodeId,
8338
+ addedAt.toISOString()
8339
+ ]
8340
+ });
8341
+ }
6848
8342
  async #replaceMentions(tx, sourceType, sourceId, text, resolutionScope, defaultScope) {
6849
8343
  await tx.execute({
6850
8344
  sql: `DELETE FROM "${TABLE_KNOWLEDGE_MENTIONS}" WHERE sourceType=? AND sourceId=?`,
@@ -6868,7 +8362,7 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
6868
8362
  updatedAt: now
6869
8363
  };
6870
8364
  await tx.execute({
6871
- sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODES}" (id,type,name,canonicalName,kind,content,scope,scopeKey,version,mergedInto,createdAt,updatedAt) VALUES (?,?,?,?,?,NULL,jsonb(?),?,?,NULL,?,?)`,
8365
+ sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODES}" (id,type,name,canonicalName,kind,content,isScope,scope,scopeKey,version,mergedInto,createdAt,updatedAt) VALUES (?,?,?,?,?,NULL,FALSE,jsonb(?),?,?,NULL,?,?)`,
6872
8366
  args: [
6873
8367
  node.id,
6874
8368
  "node",
@@ -6882,6 +8376,7 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
6882
8376
  now.toISOString()
6883
8377
  ]
6884
8378
  });
8379
+ await this.#replaceNodeScopes(tx, node.id, defaultScope, now);
6885
8380
  await this.#activity(tx, "node-created", "node", node.id, defaultScope);
6886
8381
  await this.#outbox(tx, "node", node.id, "upsert", 1, defaultScope);
6887
8382
  }
@@ -6898,13 +8393,17 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
6898
8393
  }
6899
8394
  async #activity(executor, action, recordType, recordId, scope, sourceThreadId) {
6900
8395
  const now = /* @__PURE__ */ new Date();
8396
+ const [contextScopeId] = await this.#resolveScopeNodeIds(executor, scope);
6901
8397
  await executor.execute({
6902
- sql: `INSERT INTO "${TABLE_KNOWLEDGE_ACTIVITY}" (id,action,recordType,recordId,scope,scopeKey,sourceThreadId,createdAt) VALUES (?,?,?,?,jsonb(?),?,?,?)`,
8398
+ sql: `INSERT INTO "${TABLE_KNOWLEDGE_ACTIVITY}" (id,action,targetType,targetId,contextScopeId,recordType,recordId,scope,scopeKey,sourceThreadId,createdAt) VALUES (?,?,?,?,?,?,?,jsonb(?),?,?,?)`,
6903
8399
  args: [
6904
8400
  createKnowledgeUlid(),
6905
8401
  action,
6906
8402
  recordType,
6907
8403
  recordId,
8404
+ contextScopeId ?? null,
8405
+ recordType,
8406
+ recordId,
6908
8407
  JSON.stringify(scope),
6909
8408
  knowledgeScopeKey(scope),
6910
8409
  sourceThreadId ?? null,
@@ -7831,6 +9330,10 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
7831
9330
  * Retention-eligible tables. `threads`, `messages`, and `resources` all anchor
7832
9331
  * on `createdAt` and are indexed for fast batched deletes. Cascade order is
7833
9332
  * enforced in `prune()` (children before threads), not here.
9333
+ *
9334
+ * Run fences anchor on `retiredAt` (epoch ms), which stays NULL until the
9335
+ * execution holding the fence settles, so a running execution's fence is
9336
+ * never pruned.
7834
9337
  */
7835
9338
  static retentionTables = {
7836
9339
  messages: {
@@ -7847,6 +9350,12 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
7847
9350
  table: TABLE_THREADS,
7848
9351
  column: "createdAt",
7849
9352
  indexed: true
9353
+ },
9354
+ runFences: {
9355
+ table: TABLE_MEMORY_RUN_FENCES,
9356
+ column: "retiredAt",
9357
+ indexed: true,
9358
+ anchorType: "epoch-ms"
7850
9359
  }
7851
9360
  };
7852
9361
  #client;
@@ -7869,6 +9378,13 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
7869
9378
  #write(operation, fn) {
7870
9379
  return this.#db.executeWriteOperationWithRetry(() => withClientWriteLock(this.#client, fn), operation);
7871
9380
  }
9381
+ /**
9382
+ * `#write` for a write that may carry a run fence: with a check, it runs in a write
9383
+ * transaction behind the check, and a retry reruns the whole transaction.
9384
+ */
9385
+ #fencedWrite(operation, check, write) {
9386
+ return this.#db.executeWriteOperationWithRetry(() => withRunFence(this.#client, check, write), operation);
9387
+ }
7872
9388
  async init() {
7873
9389
  await this.#db.createTable({
7874
9390
  tableName: TABLE_THREADS,
@@ -7882,6 +9398,10 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
7882
9398
  tableName: TABLE_RESOURCES,
7883
9399
  schema: TABLE_SCHEMAS[TABLE_RESOURCES]
7884
9400
  });
9401
+ await this.#db.createTable({
9402
+ tableName: TABLE_MEMORY_RUN_FENCES,
9403
+ schema: RUN_FENCING_TABLE_SCHEMAS[TABLE_MEMORY_RUN_FENCES]
9404
+ });
7885
9405
  const omSchema = OBSERVATIONAL_MEMORY_TABLE_SCHEMA?.[OM_TABLE];
7886
9406
  if (omSchema) {
7887
9407
  await this.#db.createTable({
@@ -7933,6 +9453,70 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
7933
9453
  await this.#db.deleteData({ tableName: TABLE_THREADS });
7934
9454
  await this.#db.deleteData({ tableName: TABLE_RESOURCES });
7935
9455
  await this.#db.deleteData({ tableName: OM_TABLE });
9456
+ await this.#db.deleteData({ tableName: TABLE_MEMORY_RUN_FENCES });
9457
+ }
9458
+ supportsRunFencing() {
9459
+ return true;
9460
+ }
9461
+ async raiseRunFence(fence) {
9462
+ try {
9463
+ return await this.#db.executeWriteOperationWithRetry(() => inWriteTransaction(this.#client, async (tx) => {
9464
+ const row = (await tx.execute({
9465
+ sql: `SELECT generation, ownerId FROM ${TABLE_MEMORY_RUN_FENCES} WHERE runId = ?`,
9466
+ args: [fence.runId]
9467
+ })).rows[0];
9468
+ const generation = row ? Number(row.generation) : void 0;
9469
+ const raises = generation === void 0 || generation < fence.generation;
9470
+ const reaffirms = generation === fence.generation && String(row.ownerId) === fence.ownerId;
9471
+ if (!raises && !reaffirms) return false;
9472
+ await tx.execute({
9473
+ sql: `INSERT INTO ${TABLE_MEMORY_RUN_FENCES} (runId, generation, ownerId) VALUES (?, ?, ?)
9474
+ ON CONFLICT(runId) DO UPDATE SET generation = excluded.generation, ownerId = excluded.ownerId, retiredAt = NULL`,
9475
+ args: [
9476
+ fence.runId,
9477
+ fence.generation,
9478
+ fence.ownerId
9479
+ ]
9480
+ });
9481
+ return true;
9482
+ }), "raiseRunFence");
9483
+ } catch (error) {
9484
+ throw new MastraError({
9485
+ id: createStorageErrorId("LIBSQL", "RAISE_RUN_FENCE", "FAILED"),
9486
+ domain: ErrorDomain.STORAGE,
9487
+ category: ErrorCategory.THIRD_PARTY,
9488
+ details: { runId: fence.runId }
9489
+ }, error);
9490
+ }
9491
+ }
9492
+ async retireRunFence(fence) {
9493
+ try {
9494
+ return (await this.#db.executeWriteOperationWithRetry(() => withClientWriteLock(this.#client, () => this.#client.execute({
9495
+ sql: `UPDATE ${TABLE_MEMORY_RUN_FENCES} SET retiredAt = ${DB_NOW_MS}
9496
+ WHERE runId = ? AND generation = ? AND ownerId = ?`,
9497
+ args: [
9498
+ fence.runId,
9499
+ fence.generation,
9500
+ fence.ownerId
9501
+ ]
9502
+ })), "retireRunFence")).rowsAffected > 0;
9503
+ } catch (error) {
9504
+ throw new MastraError({
9505
+ id: createStorageErrorId("LIBSQL", "RETIRE_RUN_FENCE", "FAILED"),
9506
+ domain: ErrorDomain.STORAGE,
9507
+ category: ErrorCategory.THIRD_PARTY,
9508
+ details: { runId: fence.runId }
9509
+ }, error);
9510
+ }
9511
+ }
9512
+ /** The check a write must pass: its own fence, otherwise the one in scope. */
9513
+ #runFenceCheck(fence, operation) {
9514
+ const resolved = resolveRunFence(this, fence);
9515
+ return resolved && {
9516
+ claimsTable: TABLE_MEMORY_RUN_FENCES,
9517
+ fence: resolved,
9518
+ operation
9519
+ };
7936
9520
  }
7937
9521
  /**
7938
9522
  * Delete rows older than each policy's `maxAge`, batched and cancellable.
@@ -7948,7 +9532,8 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
7948
9532
  order: [
7949
9533
  "messages",
7950
9534
  "resources",
7951
- "threads"
9535
+ "threads",
9536
+ "runFences"
7952
9537
  ]
7953
9538
  });
7954
9539
  return runPrune({
@@ -8330,7 +9915,7 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
8330
9915
  throw mastraError;
8331
9916
  }
8332
9917
  }
8333
- async saveMessages({ messages }) {
9918
+ async saveMessages({ messages, fence }) {
8334
9919
  if (messages.length === 0) return { messages };
8335
9920
  try {
8336
9921
  const threadId = messages[0]?.threadId;
@@ -8368,13 +9953,16 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
8368
9953
  const BATCH_SIZE = 50;
8369
9954
  const messageStatements = batchStatements.slice(0, -1);
8370
9955
  const threadUpdateStatement = batchStatements[batchStatements.length - 1];
8371
- for (let i = 0; i < messageStatements.length; i += BATCH_SIZE) {
8372
- const batch = messageStatements.slice(i, i + BATCH_SIZE);
8373
- if (batch.length > 0) await this.#write("saveMessages", () => this.#client.batch(batch, "write"));
8374
- }
8375
- if (threadUpdateStatement) await this.#write("saveMessages", () => this.#client.execute(threadUpdateStatement));
9956
+ await this.#fencedWrite("saveMessages", this.#runFenceCheck(fence, "saveMessages"), async (writer) => {
9957
+ for (let i = 0; i < messageStatements.length; i += BATCH_SIZE) {
9958
+ const batch = messageStatements.slice(i, i + BATCH_SIZE);
9959
+ if (batch.length > 0) await writer.batch(batch);
9960
+ }
9961
+ if (threadUpdateStatement) await writer.execute(threadUpdateStatement);
9962
+ });
8376
9963
  return { messages: new MessageList().add(messages, "memory").get.all.db() };
8377
9964
  } catch (error) {
9965
+ if (isRunFenceConflictError(error)) throw error;
8378
9966
  throw new MastraError({
8379
9967
  id: createStorageErrorId("LIBSQL", "SAVE_MESSAGES", "FAILED"),
8380
9968
  domain: ErrorDomain.STORAGE,
@@ -8382,11 +9970,14 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
8382
9970
  }, error);
8383
9971
  }
8384
9972
  }
8385
- async updateMessages({ messages }) {
9973
+ async updateMessages({ messages, fence }) {
8386
9974
  if (messages.length === 0) return [];
9975
+ return this.#fencedWrite("updateMessages", this.#runFenceCheck(fence, "updateMessages"), (writer) => this.#updateMessages(writer, messages));
9976
+ }
9977
+ async #updateMessages(writer, messages) {
8387
9978
  const messageIds = messages.map((m) => m.id);
8388
9979
  const selectSql = `SELECT * FROM ${TABLE_MESSAGES} WHERE id IN (${messageIds.map(() => "?").join(",")})`;
8389
- const existingMessages = (await this.#client.execute({
9980
+ const existingMessages = (await writer.execute({
8390
9981
  sql: selectSql,
8391
9982
  args: messageIds
8392
9983
  })).rows.map((row) => this.parseRow(row));
@@ -8438,48 +10029,44 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
8438
10029
  sql: `UPDATE ${TABLE_THREADS} SET updatedAt = ? WHERE id = ?`,
8439
10030
  args: [now, threadId]
8440
10031
  });
8441
- await this.#write("updateMessages", () => this.#client.batch(batchStatements, "write"));
8442
- return (await this.#client.execute({
10032
+ await writer.batch(batchStatements);
10033
+ return (await writer.execute({
8443
10034
  sql: selectSql,
8444
10035
  args: messageIds
8445
10036
  })).rows.map((row) => this.parseRow(row));
8446
10037
  }
8447
- async deleteMessages(messageIds) {
10038
+ async deleteMessages(messageIds, options) {
8448
10039
  if (!messageIds || messageIds.length === 0) return;
8449
10040
  try {
8450
10041
  const BATCH_SIZE = 100;
8451
- await this.#write("deleteMessages", async () => {
10042
+ const check = this.#runFenceCheck(options?.fence, "deleteMessages");
10043
+ await this.#db.executeWriteOperationWithRetry(() => inWriteTransaction(this.#client, async (tx) => {
10044
+ if (check) await assertRunFence(tx, check);
8452
10045
  const threadIds = /* @__PURE__ */ new Set();
8453
- const tx = await this.#client.transaction("write");
8454
- try {
8455
- for (let i = 0; i < messageIds.length; i += BATCH_SIZE) {
8456
- const batch = messageIds.slice(i, i + BATCH_SIZE);
8457
- const placeholders = batch.map(() => "?").join(",");
8458
- (await tx.execute({
8459
- sql: `SELECT DISTINCT thread_id FROM "${TABLE_MESSAGES}" WHERE id IN (${placeholders})`,
8460
- args: batch
8461
- })).rows?.forEach((row) => {
8462
- if (row.thread_id) threadIds.add(row.thread_id);
8463
- });
8464
- await tx.execute({
8465
- sql: `DELETE FROM "${TABLE_MESSAGES}" WHERE id IN (${placeholders})`,
8466
- args: batch
8467
- });
8468
- }
8469
- if (threadIds.size > 0) {
8470
- const now = (/* @__PURE__ */ new Date()).toISOString();
8471
- for (const threadId of threadIds) await tx.execute({
8472
- sql: `UPDATE "${TABLE_THREADS}" SET "updatedAt" = ? WHERE id = ?`,
8473
- args: [now, threadId]
8474
- });
8475
- }
8476
- await tx.commit();
8477
- } catch (error) {
8478
- await tx.rollback();
8479
- throw error;
10046
+ for (let i = 0; i < messageIds.length; i += BATCH_SIZE) {
10047
+ const batch = messageIds.slice(i, i + BATCH_SIZE);
10048
+ const placeholders = batch.map(() => "?").join(",");
10049
+ (await tx.execute({
10050
+ sql: `SELECT DISTINCT thread_id FROM "${TABLE_MESSAGES}" WHERE id IN (${placeholders})`,
10051
+ args: batch
10052
+ })).rows?.forEach((row) => {
10053
+ if (row.thread_id) threadIds.add(row.thread_id);
10054
+ });
10055
+ await tx.execute({
10056
+ sql: `DELETE FROM "${TABLE_MESSAGES}" WHERE id IN (${placeholders})`,
10057
+ args: batch
10058
+ });
8480
10059
  }
8481
- });
10060
+ if (threadIds.size > 0) {
10061
+ const now = (/* @__PURE__ */ new Date()).toISOString();
10062
+ for (const threadId of threadIds) await tx.execute({
10063
+ sql: `UPDATE "${TABLE_THREADS}" SET "updatedAt" = ? WHERE id = ?`,
10064
+ args: [now, threadId]
10065
+ });
10066
+ }
10067
+ }), "deleteMessages");
8482
10068
  } catch (error) {
10069
+ if (isRunFenceConflictError(error)) throw error;
8483
10070
  throw new MastraError({
8484
10071
  id: createStorageErrorId("LIBSQL", "DELETE_MESSAGES", "FAILED"),
8485
10072
  domain: ErrorDomain.STORAGE,
@@ -8503,13 +10090,18 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
8503
10090
  };
8504
10091
  }
8505
10092
  async saveResource({ resource }) {
10093
+ return this.#insertResource(resource, void 0);
10094
+ }
10095
+ async #insertResource(resource, check) {
8506
10096
  await this.#db.insert({
8507
10097
  tableName: TABLE_RESOURCES,
8508
- record: { ...resource }
10098
+ record: { ...resource },
10099
+ check
8509
10100
  });
8510
10101
  return resource;
8511
10102
  }
8512
- async updateResource({ resourceId, workingMemory, metadata }) {
10103
+ async updateResource({ resourceId, workingMemory, metadata, fence }) {
10104
+ const check = this.#runFenceCheck(fence, "updateResource");
8513
10105
  const existingResource = await this.getResourceById({ resourceId });
8514
10106
  if (!existingResource) {
8515
10107
  const newResource = {
@@ -8519,7 +10111,7 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
8519
10111
  createdAt: /* @__PURE__ */ new Date(),
8520
10112
  updatedAt: /* @__PURE__ */ new Date()
8521
10113
  };
8522
- return this.saveResource({ resource: newResource });
10114
+ return this.#insertResource(newResource, check);
8523
10115
  }
8524
10116
  const updatedResource = {
8525
10117
  ...existingResource,
@@ -8543,7 +10135,7 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
8543
10135
  updates.push("updatedAt = ?");
8544
10136
  values.push(updatedResource.updatedAt.toISOString());
8545
10137
  values.push(resourceId);
8546
- await this.#write("updateResource", () => this.#client.execute({
10138
+ await this.#fencedWrite("updateResource", check, (writer) => writer.execute({
8547
10139
  sql: `UPDATE ${TABLE_RESOURCES} SET ${updates.join(", ")} WHERE id = ?`,
8548
10140
  args: values
8549
10141
  }));
@@ -8747,14 +10339,16 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
8747
10339
  throw mastraError;
8748
10340
  }
8749
10341
  }
8750
- async saveThread({ thread }) {
10342
+ async saveThread({ thread, fence }) {
8751
10343
  try {
8752
10344
  await this.#db.insert({
8753
10345
  tableName: TABLE_THREADS,
8754
- record: { ...thread }
10346
+ record: { ...thread },
10347
+ check: this.#runFenceCheck(fence, "saveThread")
8755
10348
  });
8756
10349
  return thread;
8757
10350
  } catch (error) {
10351
+ if (isRunFenceConflictError(error)) throw error;
8758
10352
  const mastraError = new MastraError({
8759
10353
  id: createStorageErrorId("LIBSQL", "SAVE_THREAD", "FAILED"),
8760
10354
  domain: ErrorDomain.STORAGE,
@@ -8766,7 +10360,8 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
8766
10360
  throw mastraError;
8767
10361
  }
8768
10362
  }
8769
- async updateThread({ id, title, metadata }) {
10363
+ async updateThread({ id, title, metadata, fence }) {
10364
+ const check = this.#runFenceCheck(fence, "updateThread");
8770
10365
  const thread = await this.getThreadById({ threadId: id });
8771
10366
  if (!thread) throw new MastraError({
8772
10367
  id: createStorageErrorId("LIBSQL", "UPDATE_THREAD", "NOT_FOUND"),
@@ -8789,7 +10384,7 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
8789
10384
  updatedAt: now
8790
10385
  };
8791
10386
  try {
8792
- await this.#write("updateThread", () => this.#client.execute({
10387
+ await this.#fencedWrite("updateThread", check, (writer) => writer.execute({
8793
10388
  sql: `UPDATE ${TABLE_THREADS} SET title = COALESCE(?, title), metadata = jsonb(?), updatedAt = ? WHERE id = ?`,
8794
10389
  args: [
8795
10390
  title ?? null,
@@ -8800,6 +10395,7 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
8800
10395
  }));
8801
10396
  return updatedThread;
8802
10397
  } catch (error) {
10398
+ if (isRunFenceConflictError(error)) throw error;
8803
10399
  throw new MastraError({
8804
10400
  id: createStorageErrorId("LIBSQL", "UPDATE_THREAD", "FAILED"),
8805
10401
  domain: ErrorDomain.STORAGE,
@@ -9210,7 +10806,7 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
9210
10806
  try {
9211
10807
  const now = /* @__PURE__ */ new Date();
9212
10808
  const observedMessageIdsJson = input.observedMessageIds ? JSON.stringify(input.observedMessageIds) : null;
9213
- if ((await this.#write("updateActiveObservations", () => this.#client.execute({
10809
+ if ((await this.#fencedWrite("updateActiveObservations", this.#runFenceCheck(void 0, "updateActiveObservations"), (writer) => writer.execute({
9214
10810
  sql: `UPDATE "${OM_TABLE}" SET
9215
10811
  "activeObservations" = ?,
9216
10812
  "lastObservedAt" = ?,
@@ -9248,9 +10844,11 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
9248
10844
  }
9249
10845
  async createReflectionGeneration(input) {
9250
10846
  try {
10847
+ const check = this.#runFenceCheck(void 0, "createReflectionGeneration");
9251
10848
  return await this.#write("createReflectionGeneration", async () => {
9252
10849
  const tx = await this.#client.transaction("write");
9253
10850
  try {
10851
+ if (check) await assertRunFence(tx, check);
9254
10852
  const record = await this.#insertReflectionGeneration(tx, input);
9255
10853
  await tx.commit();
9256
10854
  return record;
@@ -9587,9 +11185,11 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
9587
11185
  async updateBufferedObservations(input) {
9588
11186
  try {
9589
11187
  const nowStr = (/* @__PURE__ */ new Date()).toISOString();
11188
+ const check = this.#runFenceCheck(void 0, "updateBufferedObservations");
9590
11189
  await this.#write("updateBufferedObservations", async () => {
9591
11190
  const tx = await this.#client.transaction("write");
9592
11191
  try {
11192
+ if (check) await assertRunFence(tx, check);
9593
11193
  const current = await tx.execute({
9594
11194
  sql: `SELECT "bufferedObservationChunks" FROM "${OM_TABLE}" WHERE id = ?`,
9595
11195
  args: [input.id]
@@ -9668,9 +11268,11 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
9668
11268
  async swapBufferedToActive(input) {
9669
11269
  try {
9670
11270
  const nowStr = (/* @__PURE__ */ new Date()).toISOString();
11271
+ const check = this.#runFenceCheck(void 0, "swapBufferedToActive");
9671
11272
  return await this.#write("swapBufferedToActive", async () => {
9672
11273
  const tx = await this.#client.transaction("write");
9673
11274
  try {
11275
+ if (check) await assertRunFence(tx, check);
9674
11276
  const current = await tx.execute({
9675
11277
  sql: `SELECT * FROM "${OM_TABLE}" WHERE id = ?`,
9676
11278
  args: [input.id]
@@ -9817,7 +11419,7 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
9817
11419
  async updateBufferedReflection(input) {
9818
11420
  try {
9819
11421
  const nowStr = (/* @__PURE__ */ new Date()).toISOString();
9820
- if ((await this.#write("updateBufferedReflection", () => this.#client.execute({
11422
+ if ((await this.#fencedWrite("updateBufferedReflection", this.#runFenceCheck(void 0, "updateBufferedReflection"), (writer) => writer.execute({
9821
11423
  sql: `UPDATE "${OM_TABLE}" SET
9822
11424
  "bufferedReflection" = CASE
9823
11425
  WHEN "bufferedReflection" IS NOT NULL AND "bufferedReflection" != ''
@@ -9857,9 +11459,11 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
9857
11459
  }
9858
11460
  async swapBufferedReflectionToActive(input) {
9859
11461
  try {
11462
+ const check = this.#runFenceCheck(void 0, "swapBufferedReflectionToActive");
9860
11463
  return await this.#write("swapBufferedReflectionToActive", async () => {
9861
11464
  const tx = await this.#client.transaction("write");
9862
11465
  try {
11466
+ if (check) await assertRunFence(tx, check);
9863
11467
  const current = await tx.execute({
9864
11468
  sql: `SELECT * FROM "${OM_TABLE}" WHERE id = ?`,
9865
11469
  args: [input.currentRecord.id]
@@ -13359,16 +14963,39 @@ var WorkflowDefinitionsLibSQL = class extends WorkflowDefinitionsStorage {
13359
14963
  };
13360
14964
  //#endregion
13361
14965
  //#region src/storage/domains/workflows/index.ts
14966
+ const RUN_OWNER_COLUMNS = `generation, ownerId, leaseExpiresAt, ${DB_NOW_MS} AS nowMs`;
14967
+ function toRunOwnershipRecord(runId, row) {
14968
+ const leaseExpiresAt = row.leaseExpiresAt === null ? null : Number(row.leaseExpiresAt);
14969
+ return {
14970
+ runId,
14971
+ generation: Number(row.generation),
14972
+ ownerId: String(row.ownerId),
14973
+ leaseExpiresAt: leaseExpiresAt === null ? null : new Date(leaseExpiresAt),
14974
+ live: leaseExpiresAt !== null && leaseExpiresAt > Number(row.nowMs)
14975
+ };
14976
+ }
13362
14977
  var WorkflowsLibSQL = class WorkflowsLibSQL extends WorkflowsStorage {
13363
14978
  /**
13364
14979
  * Workflow run snapshots accumulate as runs execute. Anchored on `updatedAt`
13365
14980
  * (last activity) so suspended/long-running runs are not pruned by start age.
14981
+ *
14982
+ * Run ownership rows are anchored on `updatedAt` (epoch ms), which every
14983
+ * claim, renewal and release refreshes, so a live execution's row is never
14984
+ * older than its renew interval.
13366
14985
  */
13367
- static retentionTables = { workflowSnapshot: {
13368
- table: TABLE_WORKFLOW_SNAPSHOT,
13369
- column: "updatedAt",
13370
- indexed: true
13371
- } };
14986
+ static retentionTables = {
14987
+ workflowSnapshot: {
14988
+ table: TABLE_WORKFLOW_SNAPSHOT,
14989
+ column: "updatedAt",
14990
+ indexed: true
14991
+ },
14992
+ runOwnership: {
14993
+ table: TABLE_WORKFLOW_RUN_OWNERS,
14994
+ column: "updatedAt",
14995
+ indexed: true,
14996
+ anchorType: "epoch-ms"
14997
+ }
14998
+ };
13372
14999
  #db;
13373
15000
  #client;
13374
15001
  executeWithRetry;
@@ -13420,16 +15047,132 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends WorkflowsStorage {
13420
15047
  schema,
13421
15048
  ifNotExists: ["resourceId"]
13422
15049
  });
15050
+ await this.#db.createTable({
15051
+ tableName: TABLE_WORKFLOW_RUN_OWNERS,
15052
+ schema: RUN_FENCING_TABLE_SCHEMAS[TABLE_WORKFLOW_RUN_OWNERS]
15053
+ });
13423
15054
  }
13424
15055
  async dangerouslyClearAll() {
13425
15056
  await this.#db.deleteData({ tableName: TABLE_WORKFLOW_SNAPSHOT });
15057
+ await this.#db.deleteData({ tableName: TABLE_WORKFLOW_RUN_OWNERS });
15058
+ }
15059
+ supportsRunFencing() {
15060
+ return true;
15061
+ }
15062
+ #runFenceCheck(fence, runId, operation) {
15063
+ const resolved = resolveRunFence(this, fence, runId);
15064
+ return resolved && {
15065
+ claimsTable: TABLE_WORKFLOW_RUN_OWNERS,
15066
+ fence: resolved,
15067
+ operation
15068
+ };
15069
+ }
15070
+ async #readRunOwner(q, runId) {
15071
+ const row = (await q.execute({
15072
+ sql: `SELECT ${RUN_OWNER_COLUMNS} FROM ${TABLE_WORKFLOW_RUN_OWNERS} WHERE runId = ?`,
15073
+ args: [runId]
15074
+ })).rows[0];
15075
+ return row ? toRunOwnershipRecord(runId, row) : null;
15076
+ }
15077
+ #ownershipError(operation, runId, error) {
15078
+ return new MastraError({
15079
+ id: createStorageErrorId("LIBSQL", operation, "FAILED"),
15080
+ domain: ErrorDomain.STORAGE,
15081
+ category: ErrorCategory.THIRD_PARTY,
15082
+ details: { runId }
15083
+ }, error);
15084
+ }
15085
+ async claimRunOwnership({ runId, ownerId, leaseMs, force, expectedGeneration }) {
15086
+ try {
15087
+ return await this.executeWithRetry(() => inWriteTransaction(this.#client, async (tx) => {
15088
+ const current = await this.#readRunOwner(tx, runId);
15089
+ const generation = current?.generation ?? 0;
15090
+ if (expectedGeneration !== void 0 && expectedGeneration !== generation) return {
15091
+ acquired: false,
15092
+ record: current
15093
+ };
15094
+ if (current?.live && !force) return {
15095
+ acquired: false,
15096
+ record: current
15097
+ };
15098
+ await tx.execute({
15099
+ sql: `INSERT INTO ${TABLE_WORKFLOW_RUN_OWNERS} (runId, generation, ownerId, leaseExpiresAt, updatedAt)
15100
+ VALUES (?, COALESCE(?, MAX(1, ${DB_NOW_MS})), ?, ${DB_NOW_MS} + ?, ${DB_NOW_MS})
15101
+ ON CONFLICT(runId) DO UPDATE SET
15102
+ generation = excluded.generation,
15103
+ ownerId = excluded.ownerId,
15104
+ leaseExpiresAt = excluded.leaseExpiresAt,
15105
+ updatedAt = excluded.updatedAt`,
15106
+ args: [
15107
+ runId,
15108
+ current ? generation + 1 : null,
15109
+ ownerId,
15110
+ leaseMs
15111
+ ]
15112
+ });
15113
+ return {
15114
+ acquired: true,
15115
+ record: await this.#readRunOwner(tx, runId)
15116
+ };
15117
+ }), "claimRunOwnership");
15118
+ } catch (error) {
15119
+ throw this.#ownershipError("CLAIM_RUN_OWNERSHIP", runId, error);
15120
+ }
15121
+ }
15122
+ async renewRunOwnership({ leaseMs, ...fence }) {
15123
+ try {
15124
+ return await this.executeWithRetry(() => inWriteTransaction(this.#client, async (tx) => {
15125
+ const renewed = await tx.execute({
15126
+ sql: `UPDATE ${TABLE_WORKFLOW_RUN_OWNERS} SET leaseExpiresAt = ${DB_NOW_MS} + ?, updatedAt = ${DB_NOW_MS}
15127
+ WHERE runId = ? AND generation = ? AND ownerId = ? AND leaseExpiresAt IS NOT NULL`,
15128
+ args: [
15129
+ leaseMs,
15130
+ fence.runId,
15131
+ fence.generation,
15132
+ fence.ownerId
15133
+ ]
15134
+ });
15135
+ const record = await this.#readRunOwner(tx, fence.runId);
15136
+ return renewed.rowsAffected > 0 ? {
15137
+ renewed: true,
15138
+ record
15139
+ } : {
15140
+ renewed: false,
15141
+ record
15142
+ };
15143
+ }), "renewRunOwnership");
15144
+ } catch (error) {
15145
+ throw this.#ownershipError("RENEW_RUN_OWNERSHIP", fence.runId, error);
15146
+ }
15147
+ }
15148
+ async releaseRunOwnership(fence) {
15149
+ try {
15150
+ return (await this.executeWithRetry(() => withClientWriteLock(this.#client, () => this.#client.execute({
15151
+ sql: `UPDATE ${TABLE_WORKFLOW_RUN_OWNERS} SET leaseExpiresAt = NULL, updatedAt = ${DB_NOW_MS}
15152
+ WHERE runId = ? AND generation = ? AND ownerId = ?`,
15153
+ args: [
15154
+ fence.runId,
15155
+ fence.generation,
15156
+ fence.ownerId
15157
+ ]
15158
+ })), "releaseRunOwnership")).rowsAffected > 0;
15159
+ } catch (error) {
15160
+ throw this.#ownershipError("RELEASE_RUN_OWNERSHIP", fence.runId, error);
15161
+ }
13426
15162
  }
13427
- /** Delete workflow snapshots older than the `workflowSnapshot` policy's `maxAge`, batched. */
15163
+ async getRunOwnership({ runId }) {
15164
+ try {
15165
+ return await this.#readRunOwner(this.#client, runId);
15166
+ } catch (error) {
15167
+ throw this.#ownershipError("GET_RUN_OWNERSHIP", runId, error);
15168
+ }
15169
+ }
15170
+ /** Delete workflow run snapshots and run ownership rows older than their policy's `maxAge`, batched. */
13428
15171
  async prune(policies, options) {
13429
15172
  const targets = resolveTargets({
13430
15173
  policies,
13431
15174
  descriptor: WorkflowsLibSQL.retentionTables,
13432
- order: ["workflowSnapshot"]
15175
+ order: ["workflowSnapshot", "runOwnership"]
13433
15176
  });
13434
15177
  return runPrune({
13435
15178
  db: this.#db,
@@ -13459,10 +15202,12 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends WorkflowsStorage {
13459
15202
  this.logger.warn("LibSQL Workflows: Failed to set PRAGMA settings.", err);
13460
15203
  }
13461
15204
  }
13462
- async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext, state }) {
15205
+ async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext, state, fence: explicitFence }) {
15206
+ const check = this.#runFenceCheck(explicitFence, runId, "updateWorkflowResults");
13463
15207
  return this.executeWithRetry(() => withClientWriteLock(this.#client, async () => {
13464
15208
  const tx = await this.#client.transaction("write");
13465
15209
  try {
15210
+ if (check) await assertRunFence(tx, check);
13466
15211
  const existingSnapshotResult = await tx.execute({
13467
15212
  sql: `SELECT json(snapshot) as snapshot FROM ${TABLE_WORKFLOW_SNAPSHOT} WHERE workflow_name = ? AND run_id = ?`,
13468
15213
  args: [workflowName, runId]
@@ -13515,10 +15260,12 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends WorkflowsStorage {
13515
15260
  }
13516
15261
  }), "updateWorkflowResults");
13517
15262
  }
13518
- async updateWorkflowState({ workflowName, runId, opts }) {
15263
+ async updateWorkflowState({ workflowName, runId, opts, fence: explicitFence }) {
15264
+ const check = this.#runFenceCheck(explicitFence, runId, "updateWorkflowState");
13519
15265
  return this.executeWithRetry(() => withClientWriteLock(this.#client, async () => {
13520
15266
  const tx = await this.#client.transaction("write");
13521
15267
  try {
15268
+ if (check) await assertRunFence(tx, check);
13522
15269
  const existingSnapshotResult = await tx.execute({
13523
15270
  sql: `SELECT json(snapshot) as snapshot FROM ${TABLE_WORKFLOW_SNAPSHOT} WHERE workflow_name = ? AND run_id = ?`,
13524
15271
  args: [workflowName, runId]
@@ -13559,7 +15306,7 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends WorkflowsStorage {
13559
15306
  }
13560
15307
  }), "updateWorkflowState");
13561
15308
  }
13562
- async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt }) {
15309
+ async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt, fence }) {
13563
15310
  const now = /* @__PURE__ */ new Date();
13564
15311
  const createdAtValue = (createdAt ?? now).toISOString();
13565
15312
  const updatedAtValue = (updatedAt ?? now).toISOString();
@@ -13567,7 +15314,8 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends WorkflowsStorage {
13567
15314
  workflowName,
13568
15315
  runId
13569
15316
  });
13570
- await this.executeWithRetry(() => withClientWriteLock(this.#client, () => this.#client.execute({
15317
+ const check = this.#runFenceCheck(fence, runId, "persistWorkflowSnapshot");
15318
+ await this.executeWithRetry(() => withRunFence(this.#client, check, (writer) => writer.execute({
13571
15319
  sql: `INSERT INTO ${TABLE_WORKFLOW_SNAPSHOT} (workflow_name, run_id, resourceId, snapshot, createdAt, updatedAt)
13572
15320
  VALUES (?, ?, ?, jsonb(?), ?, ?)
13573
15321
  ON CONFLICT(workflow_name, run_id)
@@ -13623,14 +15371,16 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends WorkflowsStorage {
13623
15371
  }, error);
13624
15372
  }
13625
15373
  }
13626
- async deleteWorkflowRunById({ runId, workflowName }) {
15374
+ async deleteWorkflowRunById({ runId, workflowName, fence }) {
15375
+ const check = this.#runFenceCheck(fence, runId, "deleteWorkflowRunById");
13627
15376
  return this.executeWithRetry(async () => {
13628
15377
  try {
13629
- await this.#client.execute({
15378
+ await withRunFence(this.#client, check, (writer) => writer.execute({
13630
15379
  sql: `DELETE FROM ${TABLE_WORKFLOW_SNAPSHOT} WHERE workflow_name = ? AND run_id = ?`,
13631
15380
  args: [workflowName, runId]
13632
- });
15381
+ }));
13633
15382
  } catch (error) {
15383
+ if (isRunFenceConflictError(error)) throw error;
13634
15384
  throw new MastraError({
13635
15385
  id: createStorageErrorId("LIBSQL", "DELETE_WORKFLOW_RUN_BY_ID", "FAILED"),
13636
15386
  domain: ErrorDomain.STORAGE,
@@ -14500,7 +16250,7 @@ var LibSQLFactoryStorage = class extends FactoryStorage {
14500
16250
  ...config.authToken ? { authToken: config.authToken } : {},
14501
16251
  ...isLocalDb ? { timeout: DEFAULT_CONNECTION_TIMEOUT_MS } : {}
14502
16252
  });
14503
- this.#client = isSingleConnectionDatabase(config) ? gateSingleConnectionClient(client) : client;
16253
+ this.#client = isSingleConnectionDatabase(config) ? gateSingleConnectionClient(client) : isLocalDb ? gateIfLocalFile(resetConnectionsAfterBusy(client, { maxWaitMs: DEFAULT_CONNECTION_TIMEOUT_MS }), config.url, { maxWaitMs: DEFAULT_CONNECTION_TIMEOUT_MS }) : client;
14504
16254
  this.ops = new LibSQLFactoryStorageOps(this.#client, this.#schemas, (fn) => withClientWriteLock(this.#client, fn));
14505
16255
  }
14506
16256
  getMastraStorage() {
@@ -14666,7 +16416,10 @@ var LibSQLStore = class extends MastraCompositeStore {
14666
16416
  ...config.syncInterval !== void 0 ? { syncInterval: config.syncInterval } : {},
14667
16417
  ...this.isLocalDb ? { timeout: this.connectionTimeoutMs } : {}
14668
16418
  });
14669
- this.client = isSingleConnectionDatabase(config) ? gateSingleConnectionClient(client) : client;
16419
+ this.client = isSingleConnectionDatabase(config) ? gateSingleConnectionClient(client) : this.isLocalDb ? gateIfLocalFile(resetConnectionsAfterBusy(client, {
16420
+ afterReset: () => this.applyLocalPragmas(client),
16421
+ maxWaitMs: this.connectionTimeoutMs
16422
+ }), config.url, { maxWaitMs: this.connectionTimeoutMs }) : client;
14670
16423
  this.pragmasReady = this.isLocalDb ? this.applyLocalPragmas() : Promise.resolve();
14671
16424
  } else {
14672
16425
  this.client = config.client;
@@ -14682,7 +16435,10 @@ var LibSQLStore = class extends MastraCompositeStore {
14682
16435
  const workflows = new WorkflowsLibSQL(domainConfig);
14683
16436
  const workflowDefinitions = new WorkflowDefinitionsLibSQL(domainConfig);
14684
16437
  const memory = new MemoryLibSQL(domainConfig);
14685
- const knowledge = new KnowledgeLibSQL(domainConfig);
16438
+ const knowledge = new KnowledgeLibSQL({
16439
+ ...domainConfig,
16440
+ storageIsolationKey: config.storageIsolationKey ?? getLibSQLKnowledgeIsolationKey(config, this.client)
16441
+ });
14686
16442
  const observability = new ObservabilityLibSQL(domainConfig);
14687
16443
  const agents = new AgentsLibSQL(domainConfig);
14688
16444
  const channels = new ChannelsLibSQL(domainConfig);
@@ -14729,7 +16485,7 @@ var LibSQLStore = class extends MastraCompositeStore {
14729
16485
  threadState
14730
16486
  };
14731
16487
  }
14732
- async applyLocalPragmas() {
16488
+ async applyLocalPragmas(client = this.client) {
14733
16489
  const pragmas = [
14734
16490
  ["journal_mode=WAL", "PRAGMA journal_mode=WAL;"],
14735
16491
  [`busy_timeout=${this.connectionTimeoutMs}`, `PRAGMA busy_timeout=${this.connectionTimeoutMs};`],
@@ -14739,14 +16495,14 @@ var LibSQLStore = class extends MastraCompositeStore {
14739
16495
  [`mmap_size=${this.localPragmas.mmapSize}`, `PRAGMA mmap_size=${this.localPragmas.mmapSize};`]
14740
16496
  ];
14741
16497
  for (const [label, sql] of pragmas) try {
14742
- await this.client.execute(sql);
16498
+ await client.execute(sql);
14743
16499
  this.logger.debug(`LibSQLStore: PRAGMA ${label} set.`);
14744
16500
  } catch (err) {
14745
16501
  this.logger.warn(`LibSQLStore: Failed to set PRAGMA ${label}.`, err);
14746
16502
  }
14747
16503
  }
14748
16504
  getStoresToInit() {
14749
- return Object.values(this.stores).filter(Boolean);
16505
+ return Object.entries(this.stores).filter(([name]) => name !== "knowledge").map(([, store]) => store).filter(Boolean);
14750
16506
  }
14751
16507
  async initDomainsSequentially() {
14752
16508
  for (const store of this.getStoresToInit()) await store.init();