@mastra/oracledb 0.5.1-alpha.0 → 0.5.2-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_types/@internal_core/dist/memory/index.d.ts +102 -0
- package/dist/_types/@internal_core/package.json +1 -0
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/docs-memory-observational-memory.md +50 -19
- package/dist/docs/references/reference-storage-retention.md +21 -18
- package/dist/index.cjs +346 -113
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +346 -113
- package/dist/index.js.map +1 -1
- package/dist/storage/domains/memory/index.d.ts +10 -0
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/domains/memory/observational-buffering.d.ts +2 -1
- package/dist/storage/domains/memory/observational-buffering.d.ts.map +1 -1
- package/dist/storage/domains/memory/observational.d.ts +23 -1
- package/dist/storage/domains/memory/observational.d.ts.map +1 -1
- package/dist/storage/domains/memory/schema.d.ts +11 -0
- package/dist/storage/domains/memory/schema.d.ts.map +1 -1
- package/dist/storage/domains/memory/utils.d.ts +8 -1
- package/dist/storage/domains/memory/utils.d.ts.map +1 -1
- package/dist/storage/index.d.ts.map +1 -1
- package/package.json +5 -4
package/dist/index.cjs
CHANGED
|
@@ -3369,6 +3369,71 @@ function jsonComparableValue$1(value) {
|
|
|
3369
3369
|
return value;
|
|
3370
3370
|
}
|
|
3371
3371
|
//#endregion
|
|
3372
|
+
//#region ../../packages/_internal-core/dist/memory/index.js
|
|
3373
|
+
/**
|
|
3374
|
+
* A buffered chunk stores `lastObservedAt = max message time + 1ms`. It is wholly covered
|
|
3375
|
+
* (every message it observed is already at or before the cursor) iff
|
|
3376
|
+
* `cursor >= chunk.lastObservedAt - 1ms`.
|
|
3377
|
+
*/
|
|
3378
|
+
function isBufferedChunkCoveredByCursor(chunkLastObservedAt, cursor) {
|
|
3379
|
+
if (!cursor) return false;
|
|
3380
|
+
return new Date(cursor).getTime() >= new Date(chunkLastObservedAt).getTime() - 1;
|
|
3381
|
+
}
|
|
3382
|
+
/** The later of two cursors; `undefined` only when both are absent. */
|
|
3383
|
+
function maxObservationCursor(a, b) {
|
|
3384
|
+
if (!a) return b ? new Date(b) : void 0;
|
|
3385
|
+
if (!b) return new Date(a);
|
|
3386
|
+
return new Date(Math.max(new Date(a).getTime(), new Date(b).getTime()));
|
|
3387
|
+
}
|
|
3388
|
+
/**
|
|
3389
|
+
* Decide the new generation's text when a reflection built from `snapshot` commits against
|
|
3390
|
+
* the stored record.
|
|
3391
|
+
*
|
|
3392
|
+
* - Stored text equals the snapshot: the reflection replaces it.
|
|
3393
|
+
* - Stored text extends the snapshot (activation only appends): the appended tail is kept
|
|
3394
|
+
* after the reflection, and its tokens are added.
|
|
3395
|
+
* - Anything else (a non-append rewrite): `null` — the reflection must not be applied.
|
|
3396
|
+
*/
|
|
3397
|
+
function planReflectionGenerationText(input) {
|
|
3398
|
+
const stored = input.storedObservations ?? "";
|
|
3399
|
+
const snapshot = input.snapshotObservations ?? "";
|
|
3400
|
+
if (stored === snapshot) return {
|
|
3401
|
+
mode: "equal",
|
|
3402
|
+
observations: input.reflection,
|
|
3403
|
+
tokenCount: input.tokenCount
|
|
3404
|
+
};
|
|
3405
|
+
if (!stored.startsWith(snapshot)) return null;
|
|
3406
|
+
const tail = stored.slice(snapshot.length).trimStart();
|
|
3407
|
+
if (!tail.trim()) return {
|
|
3408
|
+
mode: "equal",
|
|
3409
|
+
observations: input.reflection,
|
|
3410
|
+
tokenCount: input.tokenCount
|
|
3411
|
+
};
|
|
3412
|
+
return {
|
|
3413
|
+
mode: "append",
|
|
3414
|
+
observations: input.reflection ? `${input.reflection}\n\n${tail}` : tail,
|
|
3415
|
+
tokenCount: input.tokenCount + Math.max(0, (input.storedObservationTokenCount ?? 0) - (input.snapshotObservationTokenCount ?? 0))
|
|
3416
|
+
};
|
|
3417
|
+
}
|
|
3418
|
+
/**
|
|
3419
|
+
* Backs `updateActiveObservations` in adapters that implement `commitActiveObservations`.
|
|
3420
|
+
* Callers of `updateActiveObservations` (which resolves `void`) can't see a commit result, so a
|
|
3421
|
+
* write that was not applied throws instead of resolving as if it had committed.
|
|
3422
|
+
*/
|
|
3423
|
+
function assertActiveObservationsApplied(result, id) {
|
|
3424
|
+
if (result.applied) return;
|
|
3425
|
+
throw new Error(result.reason === "retired" ? `Observational memory record ${id} was superseded by a newer generation; observations were not written` : `Observational memory record ${id} changed since the observations were composed; observations were not written`);
|
|
3426
|
+
}
|
|
3427
|
+
/**
|
|
3428
|
+
* Whether the stored text can still accept a reflection built from `snapshot`
|
|
3429
|
+
* (equal, or only appended to since).
|
|
3430
|
+
*/
|
|
3431
|
+
function isAppendOnlySince(storedObservations, snapshotObservations) {
|
|
3432
|
+
const stored = storedObservations ?? "";
|
|
3433
|
+
const snapshot = snapshotObservations ?? "";
|
|
3434
|
+
return stored === snapshot || stored.startsWith(snapshot);
|
|
3435
|
+
}
|
|
3436
|
+
//#endregion
|
|
3372
3437
|
//#region src/storage/domains/memory/utils.ts
|
|
3373
3438
|
const STORE_NAME$5 = "ORACLEDB";
|
|
3374
3439
|
const EMPTY_STRING_SENTINEL = "__MASTRA_ORACLE_EMPTY_STRING__";
|
|
@@ -3406,6 +3471,17 @@ function chunkValues(values, size = 900) {
|
|
|
3406
3471
|
for (let index = 0; index < values.length; index += size) chunks.push(values.slice(index, index + size));
|
|
3407
3472
|
return chunks;
|
|
3408
3473
|
}
|
|
3474
|
+
/**
|
|
3475
|
+
* Bind an instant as TIMESTAMP WITH TIME ZONE. A bare `Date` binds as TIMESTAMP WITH LOCAL
|
|
3476
|
+
* TIME ZONE, which the server converts with the session's fixed offset (captured at connect
|
|
3477
|
+
* time); an instant on the other side of a DST change is then stored shifted by an hour.
|
|
3478
|
+
*/
|
|
3479
|
+
function timestampBind(value) {
|
|
3480
|
+
return value === void 0 || value === null ? null : {
|
|
3481
|
+
type: oracledb.default.DB_TYPE_TIMESTAMP_TZ,
|
|
3482
|
+
val: toDate(value)
|
|
3483
|
+
};
|
|
3484
|
+
}
|
|
3409
3485
|
function boolToNumber(value) {
|
|
3410
3486
|
return value ? 1 : 0;
|
|
3411
3487
|
}
|
|
@@ -3554,12 +3630,48 @@ const OM_LAST_BUFFERED_AT_TOKENS = "\"lastBufferedAtTokens\"";
|
|
|
3554
3630
|
const OM_LAST_BUFFERED_AT_TIME = "\"lastBufferedAtTime\"";
|
|
3555
3631
|
const OM_CREATED_AT = "\"createdAt\"";
|
|
3556
3632
|
const OM_UPDATED_AT = "\"updatedAt\"";
|
|
3633
|
+
const OM_SUPERSEDED_BY = "\"supersededBy\"";
|
|
3557
3634
|
async function initMemorySchema(ctx) {
|
|
3558
3635
|
await ctx.db.withConnection(async (connection) => {
|
|
3559
3636
|
await createTables(ctx, connection);
|
|
3560
3637
|
await createIndexes(ctx, connection);
|
|
3638
|
+
await backfillObservationalMemorySupersededBy(ctx, connection);
|
|
3561
3639
|
});
|
|
3562
3640
|
}
|
|
3641
|
+
/**
|
|
3642
|
+
* Retire every live observational memory row that sorts after its key's canonical head
|
|
3643
|
+
* (`generationCount DESC, createdAt ASC, id ASC`), marking it superseded by the head. Rows
|
|
3644
|
+
* retired by older adapter versions (which never set `supersededBy`) become frozen. Idempotent.
|
|
3645
|
+
* One statement: the head's ordering key is part of the condition, so a row created by a
|
|
3646
|
+
* concurrent rollover (higher generation) never matches, and a row locked by one is
|
|
3647
|
+
* re-evaluated after it commits.
|
|
3648
|
+
*/
|
|
3649
|
+
async function backfillObservationalMemorySupersededBy(ctx, connection) {
|
|
3650
|
+
const om = table(ctx, _mastra_core_storage.TABLE_OBSERVATIONAL_MEMORY);
|
|
3651
|
+
const heads = `
|
|
3652
|
+
SELECT ${OM_LOOKUP_KEY} AS head_key, id AS head_id, ${OM_GENERATION_COUNT} AS head_gen, ${OM_CREATED_AT} AS head_created
|
|
3653
|
+
FROM (
|
|
3654
|
+
SELECT ${OM_LOOKUP_KEY}, id, ${OM_GENERATION_COUNT}, ${OM_CREATED_AT},
|
|
3655
|
+
ROW_NUMBER() OVER (PARTITION BY ${OM_LOOKUP_KEY} ORDER BY ${OM_GENERATION_COUNT} DESC, ${OM_CREATED_AT} ASC, id ASC) AS rn
|
|
3656
|
+
FROM ${om}
|
|
3657
|
+
WHERE ${OM_LOOKUP_KEY} IN (
|
|
3658
|
+
SELECT ${OM_LOOKUP_KEY} FROM ${om} WHERE ${OM_SUPERSEDED_BY} IS NULL GROUP BY ${OM_LOOKUP_KEY} HAVING COUNT(*) > 1
|
|
3659
|
+
)
|
|
3660
|
+
)
|
|
3661
|
+
WHERE rn = 1`;
|
|
3662
|
+
await connection.execute(`UPDATE ${om} r
|
|
3663
|
+
SET r.${OM_SUPERSEDED_BY} = (SELECT h.head_id FROM (${heads}) h WHERE h.head_key = r.${OM_LOOKUP_KEY})
|
|
3664
|
+
WHERE r.${OM_SUPERSEDED_BY} IS NULL
|
|
3665
|
+
AND EXISTS (
|
|
3666
|
+
SELECT 1 FROM (${heads}) h
|
|
3667
|
+
WHERE h.head_key = r.${OM_LOOKUP_KEY}
|
|
3668
|
+
AND (
|
|
3669
|
+
r.${OM_GENERATION_COUNT} < h.head_gen
|
|
3670
|
+
OR (r.${OM_GENERATION_COUNT} = h.head_gen AND r.${OM_CREATED_AT} > h.head_created)
|
|
3671
|
+
OR (r.${OM_GENERATION_COUNT} = h.head_gen AND r.${OM_CREATED_AT} = h.head_created AND r.id > h.head_id)
|
|
3672
|
+
)
|
|
3673
|
+
)`, {}, { autoCommit: true });
|
|
3674
|
+
}
|
|
3563
3675
|
async function clearAllMemoryTables(ctx) {
|
|
3564
3676
|
await ctx.db.tx(async (client) => {
|
|
3565
3677
|
await client.none(`DELETE FROM ${table(ctx, _mastra_core_storage.TABLE_OBSERVATIONAL_MEMORY)}`);
|
|
@@ -3633,7 +3745,8 @@ async function createTables(ctx, connection) {
|
|
|
3633
3745
|
${OM_LAST_BUFFERED_AT_TIME} TIMESTAMP WITH TIME ZONE,
|
|
3634
3746
|
metadata JSON,
|
|
3635
3747
|
${OM_CREATED_AT} TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
3636
|
-
${OM_UPDATED_AT} TIMESTAMP WITH TIME ZONE NOT NULL
|
|
3748
|
+
${OM_UPDATED_AT} TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
3749
|
+
${OM_SUPERSEDED_BY} VARCHAR2(512)
|
|
3637
3750
|
)`, [-955]);
|
|
3638
3751
|
await ensureObservationalMemoryColumns(ctx, connection);
|
|
3639
3752
|
await relaxObservationalMemoryNullability(ctx, connection);
|
|
@@ -3771,6 +3884,10 @@ async function ensureObservationalMemoryColumns(ctx, connection) {
|
|
|
3771
3884
|
{
|
|
3772
3885
|
name: OM_UPDATED_AT,
|
|
3773
3886
|
type: "TIMESTAMP WITH TIME ZONE"
|
|
3887
|
+
},
|
|
3888
|
+
{
|
|
3889
|
+
name: OM_SUPERSEDED_BY,
|
|
3890
|
+
type: "VARCHAR2(512)"
|
|
3774
3891
|
}
|
|
3775
3892
|
];
|
|
3776
3893
|
for (const column of columns) await executeDdl(connection, `ALTER TABLE ${table(ctx, _mastra_core_storage.TABLE_OBSERVATIONAL_MEMORY)} ADD (${column.name} ${column.type})`, [-1430]);
|
|
@@ -4286,14 +4403,15 @@ function mergeMessageContent(existingContent, updateContent) {
|
|
|
4286
4403
|
}
|
|
4287
4404
|
//#endregion
|
|
4288
4405
|
//#region src/storage/domains/memory/observational.ts
|
|
4406
|
+
/** Hops a write aimed at a retired record may follow to reach the head before giving up. */
|
|
4407
|
+
const OM_MAX_HEAD_HOPS = 3;
|
|
4408
|
+
/** Canonical head order for a lookup key. */
|
|
4409
|
+
const OM_HEAD_ORDER = `${OM_GENERATION_COUNT} DESC, ${OM_CREATED_AT} ASC, id ASC`;
|
|
4289
4410
|
async function getObservationalMemory(ctx, threadId, resourceId) {
|
|
4290
4411
|
try {
|
|
4291
4412
|
const lookupKey = getOMKey(threadId, resourceId);
|
|
4292
4413
|
return await ctx.db.withConnection(async (connection) => {
|
|
4293
|
-
const row =
|
|
4294
|
-
WHERE ${OM_LOOKUP_KEY} = :lookupKey
|
|
4295
|
-
ORDER BY ${OM_GENERATION_COUNT} DESC
|
|
4296
|
-
FETCH FIRST 1 ROWS ONLY`, asBindParameters({ lookupKey }), executeOptions()))[0];
|
|
4414
|
+
const row = await getHeadOMRow(ctx, connection, lookupKey);
|
|
4297
4415
|
return row ? parseOMRow(row) : null;
|
|
4298
4416
|
});
|
|
4299
4417
|
} catch (error) {
|
|
@@ -4325,11 +4443,11 @@ async function getObservationalMemoryHistory(ctx, threadId, resourceId, limit =
|
|
|
4325
4443
|
}
|
|
4326
4444
|
if (options?.from) {
|
|
4327
4445
|
conditions.push(`${OM_CREATED_AT} >= :fromDate`);
|
|
4328
|
-
binds.fromDate = options.from;
|
|
4446
|
+
binds.fromDate = timestampBind(options.from);
|
|
4329
4447
|
}
|
|
4330
4448
|
if (options?.to) {
|
|
4331
4449
|
conditions.push(`${OM_CREATED_AT} <= :toDate`);
|
|
4332
|
-
binds.toDate = options.to;
|
|
4450
|
+
binds.toDate = timestampBind(options.to);
|
|
4333
4451
|
}
|
|
4334
4452
|
if (options?.groupId !== void 0) {
|
|
4335
4453
|
conditions.push(`(DBMS_LOB.INSTR(${OM_ACTIVE_OBSERVATIONS}, :groupPrefix) > 0 OR EXISTS (
|
|
@@ -4368,6 +4486,7 @@ async function getObservationalMemoryHistory(ctx, threadId, resourceId, limit =
|
|
|
4368
4486
|
}
|
|
4369
4487
|
async function initializeObservationalMemory(ctx, input) {
|
|
4370
4488
|
const now = /* @__PURE__ */ new Date();
|
|
4489
|
+
const lookupKey = getOMKey(input.threadId, input.resourceId);
|
|
4371
4490
|
const record = {
|
|
4372
4491
|
id: globalThis.crypto.randomUUID(),
|
|
4373
4492
|
scope: input.scope,
|
|
@@ -4389,13 +4508,22 @@ async function initializeObservationalMemory(ctx, input) {
|
|
|
4389
4508
|
lastBufferedAtTokens: 0,
|
|
4390
4509
|
lastBufferedAtTime: null,
|
|
4391
4510
|
config: input.config,
|
|
4392
|
-
observedTimezone: input.observedTimezone
|
|
4511
|
+
observedTimezone: input.observedTimezone,
|
|
4512
|
+
supersededBy: null
|
|
4393
4513
|
};
|
|
4394
4514
|
try {
|
|
4395
|
-
await ctx.
|
|
4515
|
+
const existing = await getObservationalMemory(ctx, input.threadId, input.resourceId);
|
|
4516
|
+
if (existing) return existing;
|
|
4517
|
+
if (await ctx.db.tx(async (_client, connection) => {
|
|
4518
|
+
const omTable = table(ctx, _mastra_core_storage.TABLE_OBSERVATIONAL_MEMORY);
|
|
4519
|
+
await connection.execute(`LOCK TABLE ${omTable} IN SHARE ROW EXCLUSIVE MODE`);
|
|
4520
|
+
if ((await connection.execute(`SELECT 1 FROM ${omTable} WHERE "lookupKey" = :lookupKey AND "supersededBy" IS NULL FETCH FIRST 1 ROWS ONLY`, { lookupKey })).rows?.length) return false;
|
|
4396
4521
|
await insertOMRecord(ctx, connection, record);
|
|
4397
|
-
|
|
4398
|
-
return record;
|
|
4522
|
+
return true;
|
|
4523
|
+
})) return record;
|
|
4524
|
+
const head = await getObservationalMemory(ctx, input.threadId, input.resourceId);
|
|
4525
|
+
if (!head) throw new Error(`Observational memory record not found after initialization: ${lookupKey}`);
|
|
4526
|
+
return head;
|
|
4399
4527
|
} catch (error) {
|
|
4400
4528
|
throw storageError$1("INITIALIZE_OBSERVATIONAL_MEMORY", "FAILED", {
|
|
4401
4529
|
threadId: input.threadId ?? "",
|
|
@@ -4417,7 +4545,17 @@ async function insertObservationalMemoryRecord(ctx, record) {
|
|
|
4417
4545
|
}
|
|
4418
4546
|
async function updateActiveObservations(ctx, input) {
|
|
4419
4547
|
try {
|
|
4420
|
-
await ctx.db.tx(async (_client, connection) => {
|
|
4548
|
+
return await ctx.db.tx(async (_client, connection) => {
|
|
4549
|
+
const row = await lockOMRow(ctx, connection, input.id, "UPDATE_ACTIVE_OBSERVATIONS");
|
|
4550
|
+
if (row.supersededBy) return {
|
|
4551
|
+
applied: false,
|
|
4552
|
+
reason: "retired"
|
|
4553
|
+
};
|
|
4554
|
+
if (input.expectedActiveObservations !== void 0 && input.expectedActiveObservations !== stringOrEmpty(row.activeObservations)) return {
|
|
4555
|
+
applied: false,
|
|
4556
|
+
reason: "conflict"
|
|
4557
|
+
};
|
|
4558
|
+
const lastObservedAt = maxObservationCursor(row.lastObservedAt, input.lastObservedAt);
|
|
4421
4559
|
assertRowsAffected((await connection.execute(`
|
|
4422
4560
|
UPDATE ${table(ctx, _mastra_core_storage.TABLE_OBSERVATIONAL_MEMORY)}
|
|
4423
4561
|
SET ${OM_ACTIVE_OBSERVATIONS} = :activeObservations,
|
|
@@ -4431,12 +4569,13 @@ async function updateActiveObservations(ctx, input) {
|
|
|
4431
4569
|
WHERE id = :id`, {
|
|
4432
4570
|
id: input.id,
|
|
4433
4571
|
activeObservations: nullableClobBind(input.observations),
|
|
4434
|
-
lastObservedAt:
|
|
4572
|
+
lastObservedAt: timestampBind(lastObservedAt),
|
|
4435
4573
|
tokenCount: Math.round(input.tokenCount),
|
|
4436
4574
|
observedMessageIds: nullableJsonBind(input.observedMessageIds),
|
|
4437
4575
|
observedTimezone: input.observedTimezone ?? null,
|
|
4438
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
4576
|
+
updatedAt: timestampBind(/* @__PURE__ */ new Date())
|
|
4439
4577
|
})).rowsAffected, "UPDATE_ACTIVE_OBSERVATIONS", input.id);
|
|
4578
|
+
return { applied: true };
|
|
4440
4579
|
});
|
|
4441
4580
|
} catch (error) {
|
|
4442
4581
|
if (error instanceof _mastra_core_error.MastraError) throw error;
|
|
@@ -4444,39 +4583,73 @@ async function updateActiveObservations(ctx, input) {
|
|
|
4444
4583
|
}
|
|
4445
4584
|
}
|
|
4446
4585
|
async function createReflectionGeneration(ctx, input) {
|
|
4586
|
+
const { currentRecord } = input;
|
|
4587
|
+
try {
|
|
4588
|
+
return await ctx.db.tx(async (_client, connection) => {
|
|
4589
|
+
const row = await findOMRowForUpdate(ctx, connection, currentRecord.id);
|
|
4590
|
+
if (!row) return currentRecord;
|
|
4591
|
+
if (row.supersededBy) return parseOMRow(await resolveLiveOMRow(ctx, connection, row));
|
|
4592
|
+
const stored = parseOMRow(row);
|
|
4593
|
+
const plan = planReflectionGenerationText({
|
|
4594
|
+
storedObservations: stored.activeObservations,
|
|
4595
|
+
storedObservationTokenCount: stored.observationTokenCount,
|
|
4596
|
+
snapshotObservations: currentRecord.activeObservations,
|
|
4597
|
+
snapshotObservationTokenCount: currentRecord.observationTokenCount,
|
|
4598
|
+
reflection: input.reflection,
|
|
4599
|
+
tokenCount: input.tokenCount
|
|
4600
|
+
});
|
|
4601
|
+
if (!plan) return stored;
|
|
4602
|
+
return rollOverOMRow(ctx, connection, stored, plan.observations, plan.tokenCount, input.newRecordId);
|
|
4603
|
+
});
|
|
4604
|
+
} catch (error) {
|
|
4605
|
+
if (error instanceof _mastra_core_error.MastraError) throw error;
|
|
4606
|
+
throw storageError$1("CREATE_REFLECTION_GENERATION", "FAILED", { id: currentRecord.id }, error);
|
|
4607
|
+
}
|
|
4608
|
+
}
|
|
4609
|
+
/**
|
|
4610
|
+
* Create the next generation from the stored (live, row-locked) record and retire it in the
|
|
4611
|
+
* same transaction. Buffered chunks move to the new generation; the cursor, buffering markers,
|
|
4612
|
+
* flags, and counters carry over; buffered reflection state does not.
|
|
4613
|
+
*/
|
|
4614
|
+
async function rollOverOMRow(ctx, connection, stored, observations, tokenCount, newRecordId) {
|
|
4447
4615
|
const now = /* @__PURE__ */ new Date();
|
|
4448
4616
|
const record = {
|
|
4449
|
-
id: globalThis.crypto.randomUUID(),
|
|
4450
|
-
scope:
|
|
4451
|
-
threadId:
|
|
4452
|
-
resourceId:
|
|
4617
|
+
id: newRecordId ?? globalThis.crypto.randomUUID(),
|
|
4618
|
+
scope: stored.scope,
|
|
4619
|
+
threadId: stored.threadId,
|
|
4620
|
+
resourceId: stored.resourceId,
|
|
4453
4621
|
createdAt: now,
|
|
4454
4622
|
updatedAt: now,
|
|
4455
|
-
lastObservedAt:
|
|
4623
|
+
lastObservedAt: stored.lastObservedAt,
|
|
4456
4624
|
originType: "reflection",
|
|
4457
|
-
generationCount:
|
|
4458
|
-
activeObservations:
|
|
4459
|
-
totalTokensObserved:
|
|
4460
|
-
observationTokenCount: Math.round(
|
|
4461
|
-
pendingMessageTokens:
|
|
4625
|
+
generationCount: stored.generationCount + 1,
|
|
4626
|
+
activeObservations: observations,
|
|
4627
|
+
totalTokensObserved: stored.totalTokensObserved,
|
|
4628
|
+
observationTokenCount: Math.round(tokenCount),
|
|
4629
|
+
pendingMessageTokens: stored.pendingMessageTokens,
|
|
4462
4630
|
isReflecting: false,
|
|
4463
4631
|
isObserving: false,
|
|
4464
|
-
isBufferingObservation:
|
|
4632
|
+
isBufferingObservation: stored.isBufferingObservation,
|
|
4465
4633
|
isBufferingReflection: false,
|
|
4466
|
-
lastBufferedAtTokens:
|
|
4467
|
-
lastBufferedAtTime: null,
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4634
|
+
lastBufferedAtTokens: stored.lastBufferedAtTokens,
|
|
4635
|
+
lastBufferedAtTime: stored.lastBufferedAtTime ?? null,
|
|
4636
|
+
bufferedObservationChunks: stored.bufferedObservationChunks?.length ? stored.bufferedObservationChunks : void 0,
|
|
4637
|
+
config: stored.config,
|
|
4638
|
+
metadata: stored.metadata,
|
|
4639
|
+
observedTimezone: stored.observedTimezone,
|
|
4640
|
+
supersededBy: null
|
|
4471
4641
|
};
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4642
|
+
await insertOMRecord(ctx, connection, record, now);
|
|
4643
|
+
if ((await connection.execute(`UPDATE ${table(ctx, _mastra_core_storage.TABLE_OBSERVATIONAL_MEMORY)}
|
|
4644
|
+
SET "supersededBy" = :newId,
|
|
4645
|
+
"bufferedObservationChunks" = NULL,
|
|
4646
|
+
"updatedAt" = :updatedAt
|
|
4647
|
+
WHERE id = :id AND "supersededBy" IS NULL`, {
|
|
4648
|
+
newId: record.id,
|
|
4649
|
+
updatedAt: timestampBind(now),
|
|
4650
|
+
id: stored.id
|
|
4651
|
+
})).rowsAffected !== 1) throw storageError$1("CREATE_REFLECTION_GENERATION", "RETIRE_FAILED", { id: stored.id }, /* @__PURE__ */ new Error(`Failed to retire observational memory record ${stored.id}`));
|
|
4652
|
+
return record;
|
|
4480
4653
|
}
|
|
4481
4654
|
async function setReflectingFlag(ctx, id, isReflecting) {
|
|
4482
4655
|
await updateOMFlag(ctx, id, OM_IS_REFLECTING, isReflecting, "SET_REFLECTING_FLAG");
|
|
@@ -4491,9 +4664,10 @@ async function setBufferingObservationFlag(ctx, id, isBuffering, lastBufferedAtT
|
|
|
4491
4664
|
const binds = {
|
|
4492
4665
|
id,
|
|
4493
4666
|
isBuffering: boolToNumber(isBuffering),
|
|
4494
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
4667
|
+
updatedAt: timestampBind(/* @__PURE__ */ new Date())
|
|
4495
4668
|
};
|
|
4496
4669
|
if (lastBufferedAtTokens !== void 0) binds.lastBufferedAtTokens = Math.round(lastBufferedAtTokens);
|
|
4670
|
+
binds.id = (await resolveLiveOMRow(ctx, connection, await lockOMRow(ctx, connection, id, "SET_BUFFERING_OBSERVATION_FLAG"))).id;
|
|
4497
4671
|
assertRowsAffected((await connection.execute(`UPDATE ${table(ctx, _mastra_core_storage.TABLE_OBSERVATIONAL_MEMORY)}
|
|
4498
4672
|
SET ${OM_IS_BUFFERING_OBSERVATION} = :isBuffering,
|
|
4499
4673
|
${OM_UPDATED_AT} = :updatedAt
|
|
@@ -4524,7 +4698,17 @@ async function clearObservationalMemory(ctx, threadId, resourceId) {
|
|
|
4524
4698
|
}
|
|
4525
4699
|
async function setPendingMessageTokens(ctx, id, tokenCount) {
|
|
4526
4700
|
try {
|
|
4527
|
-
await
|
|
4701
|
+
await ctx.db.tx(async (_client, connection) => {
|
|
4702
|
+
const target = await resolveLiveOMRow(ctx, connection, await lockOMRow(ctx, connection, id, "SET_PENDING_MESSAGE_TOKENS"));
|
|
4703
|
+
assertRowsAffected((await connection.execute(`UPDATE ${table(ctx, _mastra_core_storage.TABLE_OBSERVATIONAL_MEMORY)}
|
|
4704
|
+
SET ${OM_PENDING_MESSAGE_TOKENS} = :tokenCount,
|
|
4705
|
+
${OM_UPDATED_AT} = :updatedAt
|
|
4706
|
+
WHERE id = :id`, {
|
|
4707
|
+
tokenCount: Math.round(tokenCount),
|
|
4708
|
+
updatedAt: timestampBind(/* @__PURE__ */ new Date()),
|
|
4709
|
+
id: target.id
|
|
4710
|
+
})).rowsAffected, "SET_PENDING_MESSAGE_TOKENS", id);
|
|
4711
|
+
});
|
|
4528
4712
|
} catch (error) {
|
|
4529
4713
|
if (error instanceof _mastra_core_error.MastraError) throw error;
|
|
4530
4714
|
throw storageError$1("SET_PENDING_MESSAGE_TOKENS", "FAILED", {
|
|
@@ -4546,7 +4730,7 @@ async function updateObservationalMemoryConfig(ctx, input) {
|
|
|
4546
4730
|
WHERE id = :id`, {
|
|
4547
4731
|
id: input.id,
|
|
4548
4732
|
config: jsonBind(merged),
|
|
4549
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
4733
|
+
updatedAt: timestampBind(/* @__PURE__ */ new Date())
|
|
4550
4734
|
})).rowsAffected, "UPDATE_OM_CONFIG", input.id);
|
|
4551
4735
|
});
|
|
4552
4736
|
} catch (error) {
|
|
@@ -4592,6 +4776,7 @@ async function insertOMRecord(ctx, connection, record, timestamp = record.create
|
|
|
4592
4776
|
${OM_LAST_BUFFERED_AT_TOKENS},
|
|
4593
4777
|
${OM_LAST_BUFFERED_AT_TIME},
|
|
4594
4778
|
metadata,
|
|
4779
|
+
${OM_SUPERSEDED_BY},
|
|
4595
4780
|
${OM_CREATED_AT},
|
|
4596
4781
|
${OM_UPDATED_AT}
|
|
4597
4782
|
) VALUES (
|
|
@@ -4627,6 +4812,7 @@ async function insertOMRecord(ctx, connection, record, timestamp = record.create
|
|
|
4627
4812
|
:lastBufferedAtTokens,
|
|
4628
4813
|
:lastBufferedAtTime,
|
|
4629
4814
|
:metadata,
|
|
4815
|
+
:supersededBy,
|
|
4630
4816
|
:createdAt,
|
|
4631
4817
|
:updatedAt
|
|
4632
4818
|
)`, {
|
|
@@ -4640,8 +4826,8 @@ async function insertOMRecord(ctx, connection, record, timestamp = record.create
|
|
|
4640
4826
|
originType: record.originType ?? "initial",
|
|
4641
4827
|
config: jsonBind(record.config ?? {}),
|
|
4642
4828
|
generationCount: record.generationCount ?? 0,
|
|
4643
|
-
lastObservedAt: record.lastObservedAt
|
|
4644
|
-
lastReflectionAt: record.originType === "reflection" ? timestamp : null,
|
|
4829
|
+
lastObservedAt: timestampBind(record.lastObservedAt),
|
|
4830
|
+
lastReflectionAt: record.originType === "reflection" ? timestampBind(timestamp) : null,
|
|
4645
4831
|
pendingMessageTokens: Math.round(record.pendingMessageTokens ?? 0),
|
|
4646
4832
|
totalTokensObserved: Math.round(record.totalTokensObserved ?? 0),
|
|
4647
4833
|
observationTokenCount: Math.round(record.observationTokenCount ?? 0),
|
|
@@ -4660,10 +4846,11 @@ async function insertOMRecord(ctx, connection, record, timestamp = record.create
|
|
|
4660
4846
|
isBufferingObservation: boolToNumber(record.isBufferingObservation),
|
|
4661
4847
|
isBufferingReflection: boolToNumber(record.isBufferingReflection),
|
|
4662
4848
|
lastBufferedAtTokens: Math.round(record.lastBufferedAtTokens ?? 0),
|
|
4663
|
-
lastBufferedAtTime: record.lastBufferedAtTime
|
|
4849
|
+
lastBufferedAtTime: timestampBind(record.lastBufferedAtTime),
|
|
4664
4850
|
metadata: nullableJsonBind(record.metadata),
|
|
4665
|
-
|
|
4666
|
-
|
|
4851
|
+
supersededBy: record.supersededBy ?? null,
|
|
4852
|
+
createdAt: timestampBind(record.createdAt),
|
|
4853
|
+
updatedAt: timestampBind(record.updatedAt)
|
|
4667
4854
|
});
|
|
4668
4855
|
}
|
|
4669
4856
|
async function updateOMFlag(ctx, id, column, value, operation) {
|
|
@@ -4687,7 +4874,7 @@ async function updateOMColumns(ctx, id, operation, columns) {
|
|
|
4687
4874
|
WHERE id = :id`, {
|
|
4688
4875
|
...binds,
|
|
4689
4876
|
id,
|
|
4690
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
4877
|
+
updatedAt: timestampBind(/* @__PURE__ */ new Date())
|
|
4691
4878
|
})).rowsAffected, operation, id);
|
|
4692
4879
|
});
|
|
4693
4880
|
}
|
|
@@ -4725,6 +4912,7 @@ function omSelect() {
|
|
|
4725
4912
|
${OM_LAST_BUFFERED_AT_TOKENS} AS "lastBufferedAtTokens",
|
|
4726
4913
|
${OM_LAST_BUFFERED_AT_TIME} AS "lastBufferedAtTime",
|
|
4727
4914
|
metadata AS "metadata",
|
|
4915
|
+
${OM_SUPERSEDED_BY} AS "supersededBy",
|
|
4728
4916
|
${OM_CREATED_AT} AS "createdAt",
|
|
4729
4917
|
${OM_UPDATED_AT} AS "updatedAt"`;
|
|
4730
4918
|
}
|
|
@@ -4760,15 +4948,60 @@ function parseOMRow(row) {
|
|
|
4760
4948
|
config: parseJson(row.config),
|
|
4761
4949
|
metadata: parseOptionalJsonObject(row.metadata, { emptyObjectAsUndefined: true }),
|
|
4762
4950
|
observedMessageIds: parseOptionalStringArray(row.observedMessageIds),
|
|
4763
|
-
observedTimezone: row.observedTimezone ? String(row.observedTimezone) : void 0
|
|
4951
|
+
observedTimezone: row.observedTimezone ? String(row.observedTimezone) : void 0,
|
|
4952
|
+
supersededBy: row.supersededBy ? String(row.supersededBy) : null
|
|
4764
4953
|
};
|
|
4765
4954
|
}
|
|
4955
|
+
/** The canonical head row of a lookup key (non-locking read). */
|
|
4956
|
+
async function getHeadOMRow(ctx, connection, lookupKey) {
|
|
4957
|
+
return rows(await connection.execute(`${omSelect()} FROM ${table(ctx, _mastra_core_storage.TABLE_OBSERVATIONAL_MEMORY)}
|
|
4958
|
+
WHERE ${OM_LOOKUP_KEY} = :lookupKey
|
|
4959
|
+
ORDER BY ${OM_HEAD_ORDER}
|
|
4960
|
+
FETCH FIRST 1 ROWS ONLY`, asBindParameters({ lookupKey }), executeOptions()))[0];
|
|
4961
|
+
}
|
|
4962
|
+
/** Row-locks one record by primary key for the rest of the transaction; `undefined` when missing. */
|
|
4963
|
+
async function findOMRowForUpdate(ctx, connection, id) {
|
|
4964
|
+
return rows(await connection.execute(`${omSelect()} FROM ${table(ctx, _mastra_core_storage.TABLE_OBSERVATIONAL_MEMORY)} WHERE id = :id FOR UPDATE`, { id }, executeOptions()))[0];
|
|
4965
|
+
}
|
|
4966
|
+
/**
|
|
4967
|
+
* Row-locks one record by primary key. Observational memory updates are incremental and
|
|
4968
|
+
* order-sensitive, so mutating paths derive their next state from a locked row.
|
|
4969
|
+
* Throws "record not found" when it is missing.
|
|
4970
|
+
*/
|
|
4971
|
+
async function lockOMRow(ctx, connection, id, operation) {
|
|
4972
|
+
const row = await findOMRowForUpdate(ctx, connection, id);
|
|
4973
|
+
if (!row) {
|
|
4974
|
+
assertRowsAffected(0, operation, id);
|
|
4975
|
+
throw new Error(`Observational memory record not found: ${id}`);
|
|
4976
|
+
}
|
|
4977
|
+
return row;
|
|
4978
|
+
}
|
|
4979
|
+
/**
|
|
4980
|
+
* The live, row-locked record a lifecycle write aimed at `row` lands on: `row` itself while
|
|
4981
|
+
* live, otherwise the head of its lookup key (locked, then re-checked — a rollover may retire
|
|
4982
|
+
* it between the head read and the lock).
|
|
4983
|
+
*/
|
|
4984
|
+
async function resolveLiveOMRow(ctx, connection, row) {
|
|
4985
|
+
let current = row;
|
|
4986
|
+
for (let hop = 0; current.supersededBy; hop++) {
|
|
4987
|
+
const head = hop < OM_MAX_HEAD_HOPS ? await getHeadOMRow(ctx, connection, current.lookupKey) : void 0;
|
|
4988
|
+
const locked = head && head.id !== current.id ? await findOMRowForUpdate(ctx, connection, head.id) : void 0;
|
|
4989
|
+
if (!locked) throw storageError$1("RESOLVE_OBSERVATIONAL_MEMORY_HEAD", "FAILED", { id: row.id }, /* @__PURE__ */ new Error(`Observational memory record ${row.id} is superseded but no live head was found`));
|
|
4990
|
+
current = locked;
|
|
4991
|
+
}
|
|
4992
|
+
return current;
|
|
4993
|
+
}
|
|
4766
4994
|
//#endregion
|
|
4767
4995
|
//#region src/storage/domains/memory/observational-buffering.ts
|
|
4768
4996
|
async function updateBufferedObservations(ctx, input) {
|
|
4769
4997
|
try {
|
|
4770
|
-
await ctx.db.tx(async (_client, connection) => {
|
|
4771
|
-
const
|
|
4998
|
+
return await ctx.db.tx(async (_client, connection) => {
|
|
4999
|
+
const row = await resolveLiveOMRow(ctx, connection, await lockOMRow(ctx, connection, input.id, "UPDATE_BUFFERED_OBSERVATIONS"));
|
|
5000
|
+
const existingChunks = parseBufferedChunks(row.bufferedObservationChunks);
|
|
5001
|
+
if (existingChunks.some((existing) => existing.cycleId === input.chunk.cycleId) || isBufferedChunkCoveredByCursor(input.chunk.lastObservedAt, row.lastObservedAt)) return {
|
|
5002
|
+
persisted: false,
|
|
5003
|
+
recordId: row.id
|
|
5004
|
+
};
|
|
4772
5005
|
const newChunk = {
|
|
4773
5006
|
id: `ombuf-${globalThis.crypto.randomUUID()}`,
|
|
4774
5007
|
cycleId: input.chunk.cycleId,
|
|
@@ -4784,18 +5017,21 @@ async function updateBufferedObservations(ctx, input) {
|
|
|
4784
5017
|
extractedValues: input.chunk.extractedValues,
|
|
4785
5018
|
extractionFailures: input.chunk.extractionFailures
|
|
4786
5019
|
};
|
|
4787
|
-
const
|
|
4788
|
-
const lastBufferedAtTimeSql = input.lastBufferedAtTime === void 0 || input.lastBufferedAtTime === null ? "" : `,\n ${OM_LAST_BUFFERED_AT_TIME} = :lastBufferedAtTime`;
|
|
4789
|
-
const binds = {
|
|
4790
|
-
id: input.id,
|
|
4791
|
-
bufferedObservationChunks: nullableJsonBind(updatedChunks),
|
|
4792
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
4793
|
-
};
|
|
4794
|
-
if (input.lastBufferedAtTime !== void 0 && input.lastBufferedAtTime !== null) binds.lastBufferedAtTime = toDate(input.lastBufferedAtTime);
|
|
5020
|
+
const lastBufferedAtTime = maxObservationCursor(row.lastBufferedAtTime, input.lastBufferedAtTime);
|
|
4795
5021
|
assertRowsAffected((await connection.execute(`UPDATE ${table(ctx, _mastra_core_storage.TABLE_OBSERVATIONAL_MEMORY)}
|
|
4796
5022
|
SET ${OM_BUFFERED_OBSERVATION_CHUNKS} = :bufferedObservationChunks,
|
|
4797
|
-
${
|
|
4798
|
-
|
|
5023
|
+
${OM_LAST_BUFFERED_AT_TIME} = :lastBufferedAtTime,
|
|
5024
|
+
${OM_UPDATED_AT} = :updatedAt
|
|
5025
|
+
WHERE id = :id`, asBindParameters({
|
|
5026
|
+
id: row.id,
|
|
5027
|
+
bufferedObservationChunks: nullableJsonBind([...existingChunks, newChunk]),
|
|
5028
|
+
lastBufferedAtTime: timestampBind(lastBufferedAtTime),
|
|
5029
|
+
updatedAt: timestampBind(/* @__PURE__ */ new Date())
|
|
5030
|
+
}))).rowsAffected, "UPDATE_BUFFERED_OBSERVATIONS", row.id);
|
|
5031
|
+
return {
|
|
5032
|
+
persisted: true,
|
|
5033
|
+
recordId: row.id
|
|
5034
|
+
};
|
|
4799
5035
|
});
|
|
4800
5036
|
} catch (error) {
|
|
4801
5037
|
if (error instanceof _mastra_core_error.MastraError) throw error;
|
|
@@ -4806,10 +5042,22 @@ async function swapBufferedToActive(ctx, input) {
|
|
|
4806
5042
|
try {
|
|
4807
5043
|
return await ctx.db.tx(async (_client, connection) => {
|
|
4808
5044
|
const row = await lockOMRow(ctx, connection, input.id, "SWAP_BUFFERED_TO_ACTIVE");
|
|
4809
|
-
|
|
5045
|
+
if (row.supersededBy) return {
|
|
5046
|
+
...emptySwapResult(),
|
|
5047
|
+
retired: true
|
|
5048
|
+
};
|
|
5049
|
+
const refreshedWeights = new Map((input.bufferedChunks ?? []).map((chunk) => [chunk.id, chunk.messageTokens]));
|
|
5050
|
+
const chunks = parseBufferedChunks(row.bufferedObservationChunks).map((chunk) => {
|
|
5051
|
+
const weight = refreshedWeights.get(chunk.id);
|
|
5052
|
+
return weight === void 0 ? chunk : {
|
|
5053
|
+
...chunk,
|
|
5054
|
+
messageTokens: weight
|
|
5055
|
+
};
|
|
5056
|
+
});
|
|
4810
5057
|
if (chunks.length === 0) return emptySwapResult();
|
|
4811
5058
|
const activation = calculateBufferedActivation(chunks, input);
|
|
4812
5059
|
const lastObservedAt = input.lastObservedAt ?? (activation.activatedChunks.at(-1)?.lastObservedAt ? toDate(activation.activatedChunks.at(-1).lastObservedAt) : /* @__PURE__ */ new Date());
|
|
5060
|
+
const storedCursor = maxObservationCursor(row.lastObservedAt, lastObservedAt);
|
|
4813
5061
|
const boundary = `\n\n--- message boundary (${lastObservedAt.toISOString()}) ---\n\n`;
|
|
4814
5062
|
const existingActive = stringOrEmpty(row.activeObservations);
|
|
4815
5063
|
const newActive = existingActive ? `${existingActive}${boundary}${activation.activatedContent}` : activation.activatedContent;
|
|
@@ -4827,8 +5075,8 @@ async function swapBufferedToActive(ctx, input) {
|
|
|
4827
5075
|
observationTokens: activation.activatedTokens,
|
|
4828
5076
|
pendingMessageTokens: pendingTokens,
|
|
4829
5077
|
bufferedObservationChunks: nullableJsonBind(activation.remainingChunks.length > 0 ? activation.remainingChunks : null),
|
|
4830
|
-
lastObservedAt,
|
|
4831
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
5078
|
+
lastObservedAt: timestampBind(storedCursor),
|
|
5079
|
+
updatedAt: timestampBind(/* @__PURE__ */ new Date())
|
|
4832
5080
|
})).rowsAffected, "SWAP_BUFFERED_TO_ACTIVE", input.id);
|
|
4833
5081
|
return activation.result;
|
|
4834
5082
|
});
|
|
@@ -4856,7 +5104,7 @@ async function updateBufferedReflection(ctx, input) {
|
|
|
4856
5104
|
tokenCount: Math.round(input.tokenCount),
|
|
4857
5105
|
inputTokenCount: Math.round(input.inputTokenCount),
|
|
4858
5106
|
reflectedObservationLineCount: Math.round(input.reflectedObservationLineCount),
|
|
4859
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
5107
|
+
updatedAt: timestampBind(/* @__PURE__ */ new Date())
|
|
4860
5108
|
})).rowsAffected, "UPDATE_BUFFERED_REFLECTION", input.id);
|
|
4861
5109
|
});
|
|
4862
5110
|
} catch (error) {
|
|
@@ -4865,66 +5113,32 @@ async function updateBufferedReflection(ctx, input) {
|
|
|
4865
5113
|
}
|
|
4866
5114
|
}
|
|
4867
5115
|
async function swapBufferedReflectionToActive(ctx, input) {
|
|
5116
|
+
const { currentRecord } = input;
|
|
4868
5117
|
try {
|
|
4869
5118
|
return await ctx.db.tx(async (_client, connection) => {
|
|
4870
|
-
const row = await
|
|
4871
|
-
|
|
4872
|
-
if (
|
|
4873
|
-
const
|
|
4874
|
-
const
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
const
|
|
4878
|
-
const
|
|
4879
|
-
|
|
4880
|
-
scope: lockedRecord.scope,
|
|
4881
|
-
threadId: lockedRecord.threadId,
|
|
4882
|
-
resourceId: lockedRecord.resourceId,
|
|
4883
|
-
createdAt: now,
|
|
4884
|
-
updatedAt: now,
|
|
4885
|
-
lastObservedAt: lockedRecord.lastObservedAt,
|
|
4886
|
-
originType: "reflection",
|
|
4887
|
-
generationCount: lockedRecord.generationCount + 1,
|
|
4888
|
-
activeObservations: newObservations,
|
|
4889
|
-
totalTokensObserved: lockedRecord.totalTokensObserved,
|
|
4890
|
-
observationTokenCount: Math.round(input.tokenCount),
|
|
4891
|
-
pendingMessageTokens: 0,
|
|
4892
|
-
isReflecting: false,
|
|
4893
|
-
isObserving: false,
|
|
4894
|
-
isBufferingObservation: false,
|
|
4895
|
-
isBufferingReflection: false,
|
|
4896
|
-
lastBufferedAtTokens: 0,
|
|
4897
|
-
lastBufferedAtTime: null,
|
|
4898
|
-
config: lockedRecord.config,
|
|
4899
|
-
metadata: lockedRecord.metadata,
|
|
4900
|
-
observedTimezone: lockedRecord.observedTimezone
|
|
4901
|
-
};
|
|
4902
|
-
await insertOMRecord(ctx, connection, newRecord, now);
|
|
5119
|
+
const row = await findOMRowForUpdate(ctx, connection, currentRecord.id);
|
|
5120
|
+
if (!row) return currentRecord;
|
|
5121
|
+
if (row.supersededBy) return parseOMRow(await resolveLiveOMRow(ctx, connection, row));
|
|
5122
|
+
const stored = parseOMRow(row);
|
|
5123
|
+
const bufferedReflection = stored.bufferedReflection ?? "";
|
|
5124
|
+
if (!bufferedReflection) throw storageError$1("SWAP_BUFFERED_REFLECTION_TO_ACTIVE", "NO_CONTENT", { id: currentRecord.id }, /* @__PURE__ */ new Error("No buffered reflection to swap"), _mastra_core_error.ErrorCategory.USER);
|
|
5125
|
+
if (!isAppendOnlySince(stored.activeObservations, currentRecord.activeObservations)) return stored;
|
|
5126
|
+
const reflectedLineCount = stored.reflectedObservationLineCount ?? 0;
|
|
5127
|
+
const unreflectedContent = stored.activeObservations.split("\n").slice(reflectedLineCount).join("\n").trim();
|
|
5128
|
+
const newRecord = await rollOverOMRow(ctx, connection, stored, unreflectedContent ? `${bufferedReflection}\n\n${unreflectedContent}` : bufferedReflection, input.tokenCount + Math.max(0, stored.observationTokenCount - (currentRecord.observationTokenCount ?? 0)), input.newRecordId);
|
|
4903
5129
|
assertRowsAffected((await connection.execute(`UPDATE ${table(ctx, _mastra_core_storage.TABLE_OBSERVATIONAL_MEMORY)}
|
|
4904
5130
|
SET ${OM_BUFFERED_REFLECTION} = NULL,
|
|
4905
5131
|
${OM_BUFFERED_REFLECTION_TOKENS} = NULL,
|
|
4906
5132
|
${OM_BUFFERED_REFLECTION_INPUT_TOKENS} = NULL,
|
|
4907
|
-
${OM_REFLECTED_OBSERVATION_LINE_COUNT} = NULL
|
|
4908
|
-
|
|
4909
|
-
WHERE id = :id`, {
|
|
4910
|
-
id: input.currentRecord.id,
|
|
4911
|
-
updatedAt: now
|
|
4912
|
-
})).rowsAffected, "SWAP_BUFFERED_REFLECTION_TO_ACTIVE", input.currentRecord.id);
|
|
5133
|
+
${OM_REFLECTED_OBSERVATION_LINE_COUNT} = NULL
|
|
5134
|
+
WHERE id = :id`, { id: stored.id })).rowsAffected, "SWAP_BUFFERED_REFLECTION_TO_ACTIVE", stored.id);
|
|
4913
5135
|
return newRecord;
|
|
4914
5136
|
});
|
|
4915
5137
|
} catch (error) {
|
|
4916
5138
|
if (error instanceof _mastra_core_error.MastraError) throw error;
|
|
4917
|
-
throw storageError$1("SWAP_BUFFERED_REFLECTION_TO_ACTIVE", "FAILED", { id:
|
|
5139
|
+
throw storageError$1("SWAP_BUFFERED_REFLECTION_TO_ACTIVE", "FAILED", { id: currentRecord.id }, error);
|
|
4918
5140
|
}
|
|
4919
5141
|
}
|
|
4920
|
-
async function lockOMRow(ctx, connection, id, operation) {
|
|
4921
|
-
const row = rows(await connection.execute(`${omSelect()} FROM ${table(ctx, _mastra_core_storage.TABLE_OBSERVATIONAL_MEMORY)} WHERE id = :id FOR UPDATE`, { id }, executeOptions()))[0];
|
|
4922
|
-
if (!row) {
|
|
4923
|
-
assertRowsAffected(0, operation, id);
|
|
4924
|
-
throw new Error(`Observational memory record not found: ${id}`);
|
|
4925
|
-
}
|
|
4926
|
-
return row;
|
|
4927
|
-
}
|
|
4928
5142
|
function emptySwapResult() {
|
|
4929
5143
|
return {
|
|
4930
5144
|
chunksActivated: 0,
|
|
@@ -5459,6 +5673,15 @@ var MemoryOracle = class MemoryOracle extends _mastra_core_storage.MemoryStorage
|
|
|
5459
5673
|
async init() {
|
|
5460
5674
|
await initMemorySchema(this.ctx);
|
|
5461
5675
|
}
|
|
5676
|
+
/**
|
|
5677
|
+
* Mark observational memory rows that are not their key's canonical head as superseded.
|
|
5678
|
+
* Runs as part of `init()`; `OracleStore` also runs it on every start when the memory schema
|
|
5679
|
+
* migration is unchanged and skipped.
|
|
5680
|
+
* @internal
|
|
5681
|
+
*/
|
|
5682
|
+
async backfillObservationalMemorySupersededBy() {
|
|
5683
|
+
await this.ctx.db.withConnection((connection) => backfillObservationalMemorySupersededBy(this.ctx, connection));
|
|
5684
|
+
}
|
|
5462
5685
|
async dangerouslyClearAll() {
|
|
5463
5686
|
await clearAllMemoryTables(this.ctx);
|
|
5464
5687
|
}
|
|
@@ -5595,6 +5818,9 @@ var MemoryOracle = class MemoryOracle extends _mastra_core_storage.MemoryStorage
|
|
|
5595
5818
|
return insertObservationalMemoryRecord(this.ctx, record);
|
|
5596
5819
|
}
|
|
5597
5820
|
async updateActiveObservations(input) {
|
|
5821
|
+
assertActiveObservationsApplied(await this.commitActiveObservations(input), input.id);
|
|
5822
|
+
}
|
|
5823
|
+
async commitActiveObservations(input) {
|
|
5598
5824
|
return updateActiveObservations(this.ctx, input);
|
|
5599
5825
|
}
|
|
5600
5826
|
async createReflectionGeneration(input) {
|
|
@@ -5622,6 +5848,9 @@ var MemoryOracle = class MemoryOracle extends _mastra_core_storage.MemoryStorage
|
|
|
5622
5848
|
return updateObservationalMemoryConfig(this.ctx, input);
|
|
5623
5849
|
}
|
|
5624
5850
|
async updateBufferedObservations(input) {
|
|
5851
|
+
await this.appendBufferedObservations(input);
|
|
5852
|
+
}
|
|
5853
|
+
async appendBufferedObservations(input) {
|
|
5625
5854
|
return updateBufferedObservations(this.ctx, input);
|
|
5626
5855
|
}
|
|
5627
5856
|
async swapBufferedToActive(input) {
|
|
@@ -8501,7 +8730,7 @@ function checksumMigration(input) {
|
|
|
8501
8730
|
//#region src/storage/index.ts
|
|
8502
8731
|
const STORE_NAME = "ORACLEDB";
|
|
8503
8732
|
const DOMAIN_SCHEMA_VERSIONS = {
|
|
8504
|
-
R001_MEMORY_SCHEMA:
|
|
8733
|
+
R001_MEMORY_SCHEMA: 2,
|
|
8505
8734
|
R002_WORKFLOWS_SCHEMA: 1,
|
|
8506
8735
|
R003_OBSERVABILITY_SCHEMA: 1,
|
|
8507
8736
|
R004_SCORES_SCHEMA: 1,
|
|
@@ -8589,6 +8818,10 @@ var OracleStore = class extends _mastra_core_storage.MastraCompositeStore {
|
|
|
8589
8818
|
async runMigrations(forceRepeatable) {
|
|
8590
8819
|
try {
|
|
8591
8820
|
const results = await this.migrationRegistry.run(this.storageMigrations(), { forceRepeatable });
|
|
8821
|
+
if (results.find((result) => result.id === "R001_MEMORY_SCHEMA")?.status === "skipped") {
|
|
8822
|
+
const memory = this.stores.memory;
|
|
8823
|
+
if (memory instanceof MemoryOracle) await memory.backfillObservationalMemorySupersededBy();
|
|
8824
|
+
}
|
|
8592
8825
|
this.isInitialized = true;
|
|
8593
8826
|
return results;
|
|
8594
8827
|
} catch (error) {
|