@mastra/oracledb 0.5.1 → 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 +17 -15
- 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 +3 -2
package/dist/index.js
CHANGED
|
@@ -3345,6 +3345,71 @@ function jsonComparableValue$1(value) {
|
|
|
3345
3345
|
return value;
|
|
3346
3346
|
}
|
|
3347
3347
|
//#endregion
|
|
3348
|
+
//#region ../../packages/_internal-core/dist/memory/index.js
|
|
3349
|
+
/**
|
|
3350
|
+
* A buffered chunk stores `lastObservedAt = max message time + 1ms`. It is wholly covered
|
|
3351
|
+
* (every message it observed is already at or before the cursor) iff
|
|
3352
|
+
* `cursor >= chunk.lastObservedAt - 1ms`.
|
|
3353
|
+
*/
|
|
3354
|
+
function isBufferedChunkCoveredByCursor(chunkLastObservedAt, cursor) {
|
|
3355
|
+
if (!cursor) return false;
|
|
3356
|
+
return new Date(cursor).getTime() >= new Date(chunkLastObservedAt).getTime() - 1;
|
|
3357
|
+
}
|
|
3358
|
+
/** The later of two cursors; `undefined` only when both are absent. */
|
|
3359
|
+
function maxObservationCursor(a, b) {
|
|
3360
|
+
if (!a) return b ? new Date(b) : void 0;
|
|
3361
|
+
if (!b) return new Date(a);
|
|
3362
|
+
return new Date(Math.max(new Date(a).getTime(), new Date(b).getTime()));
|
|
3363
|
+
}
|
|
3364
|
+
/**
|
|
3365
|
+
* Decide the new generation's text when a reflection built from `snapshot` commits against
|
|
3366
|
+
* the stored record.
|
|
3367
|
+
*
|
|
3368
|
+
* - Stored text equals the snapshot: the reflection replaces it.
|
|
3369
|
+
* - Stored text extends the snapshot (activation only appends): the appended tail is kept
|
|
3370
|
+
* after the reflection, and its tokens are added.
|
|
3371
|
+
* - Anything else (a non-append rewrite): `null` — the reflection must not be applied.
|
|
3372
|
+
*/
|
|
3373
|
+
function planReflectionGenerationText(input) {
|
|
3374
|
+
const stored = input.storedObservations ?? "";
|
|
3375
|
+
const snapshot = input.snapshotObservations ?? "";
|
|
3376
|
+
if (stored === snapshot) return {
|
|
3377
|
+
mode: "equal",
|
|
3378
|
+
observations: input.reflection,
|
|
3379
|
+
tokenCount: input.tokenCount
|
|
3380
|
+
};
|
|
3381
|
+
if (!stored.startsWith(snapshot)) return null;
|
|
3382
|
+
const tail = stored.slice(snapshot.length).trimStart();
|
|
3383
|
+
if (!tail.trim()) return {
|
|
3384
|
+
mode: "equal",
|
|
3385
|
+
observations: input.reflection,
|
|
3386
|
+
tokenCount: input.tokenCount
|
|
3387
|
+
};
|
|
3388
|
+
return {
|
|
3389
|
+
mode: "append",
|
|
3390
|
+
observations: input.reflection ? `${input.reflection}\n\n${tail}` : tail,
|
|
3391
|
+
tokenCount: input.tokenCount + Math.max(0, (input.storedObservationTokenCount ?? 0) - (input.snapshotObservationTokenCount ?? 0))
|
|
3392
|
+
};
|
|
3393
|
+
}
|
|
3394
|
+
/**
|
|
3395
|
+
* Backs `updateActiveObservations` in adapters that implement `commitActiveObservations`.
|
|
3396
|
+
* Callers of `updateActiveObservations` (which resolves `void`) can't see a commit result, so a
|
|
3397
|
+
* write that was not applied throws instead of resolving as if it had committed.
|
|
3398
|
+
*/
|
|
3399
|
+
function assertActiveObservationsApplied(result, id) {
|
|
3400
|
+
if (result.applied) return;
|
|
3401
|
+
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`);
|
|
3402
|
+
}
|
|
3403
|
+
/**
|
|
3404
|
+
* Whether the stored text can still accept a reflection built from `snapshot`
|
|
3405
|
+
* (equal, or only appended to since).
|
|
3406
|
+
*/
|
|
3407
|
+
function isAppendOnlySince(storedObservations, snapshotObservations) {
|
|
3408
|
+
const stored = storedObservations ?? "";
|
|
3409
|
+
const snapshot = snapshotObservations ?? "";
|
|
3410
|
+
return stored === snapshot || stored.startsWith(snapshot);
|
|
3411
|
+
}
|
|
3412
|
+
//#endregion
|
|
3348
3413
|
//#region src/storage/domains/memory/utils.ts
|
|
3349
3414
|
const STORE_NAME$5 = "ORACLEDB";
|
|
3350
3415
|
const EMPTY_STRING_SENTINEL = "__MASTRA_ORACLE_EMPTY_STRING__";
|
|
@@ -3382,6 +3447,17 @@ function chunkValues(values, size = 900) {
|
|
|
3382
3447
|
for (let index = 0; index < values.length; index += size) chunks.push(values.slice(index, index + size));
|
|
3383
3448
|
return chunks;
|
|
3384
3449
|
}
|
|
3450
|
+
/**
|
|
3451
|
+
* Bind an instant as TIMESTAMP WITH TIME ZONE. A bare `Date` binds as TIMESTAMP WITH LOCAL
|
|
3452
|
+
* TIME ZONE, which the server converts with the session's fixed offset (captured at connect
|
|
3453
|
+
* time); an instant on the other side of a DST change is then stored shifted by an hour.
|
|
3454
|
+
*/
|
|
3455
|
+
function timestampBind(value) {
|
|
3456
|
+
return value === void 0 || value === null ? null : {
|
|
3457
|
+
type: oracledb.DB_TYPE_TIMESTAMP_TZ,
|
|
3458
|
+
val: toDate(value)
|
|
3459
|
+
};
|
|
3460
|
+
}
|
|
3385
3461
|
function boolToNumber(value) {
|
|
3386
3462
|
return value ? 1 : 0;
|
|
3387
3463
|
}
|
|
@@ -3530,12 +3606,48 @@ const OM_LAST_BUFFERED_AT_TOKENS = "\"lastBufferedAtTokens\"";
|
|
|
3530
3606
|
const OM_LAST_BUFFERED_AT_TIME = "\"lastBufferedAtTime\"";
|
|
3531
3607
|
const OM_CREATED_AT = "\"createdAt\"";
|
|
3532
3608
|
const OM_UPDATED_AT = "\"updatedAt\"";
|
|
3609
|
+
const OM_SUPERSEDED_BY = "\"supersededBy\"";
|
|
3533
3610
|
async function initMemorySchema(ctx) {
|
|
3534
3611
|
await ctx.db.withConnection(async (connection) => {
|
|
3535
3612
|
await createTables(ctx, connection);
|
|
3536
3613
|
await createIndexes(ctx, connection);
|
|
3614
|
+
await backfillObservationalMemorySupersededBy(ctx, connection);
|
|
3537
3615
|
});
|
|
3538
3616
|
}
|
|
3617
|
+
/**
|
|
3618
|
+
* Retire every live observational memory row that sorts after its key's canonical head
|
|
3619
|
+
* (`generationCount DESC, createdAt ASC, id ASC`), marking it superseded by the head. Rows
|
|
3620
|
+
* retired by older adapter versions (which never set `supersededBy`) become frozen. Idempotent.
|
|
3621
|
+
* One statement: the head's ordering key is part of the condition, so a row created by a
|
|
3622
|
+
* concurrent rollover (higher generation) never matches, and a row locked by one is
|
|
3623
|
+
* re-evaluated after it commits.
|
|
3624
|
+
*/
|
|
3625
|
+
async function backfillObservationalMemorySupersededBy(ctx, connection) {
|
|
3626
|
+
const om = table(ctx, TABLE_OBSERVATIONAL_MEMORY);
|
|
3627
|
+
const heads = `
|
|
3628
|
+
SELECT ${OM_LOOKUP_KEY} AS head_key, id AS head_id, ${OM_GENERATION_COUNT} AS head_gen, ${OM_CREATED_AT} AS head_created
|
|
3629
|
+
FROM (
|
|
3630
|
+
SELECT ${OM_LOOKUP_KEY}, id, ${OM_GENERATION_COUNT}, ${OM_CREATED_AT},
|
|
3631
|
+
ROW_NUMBER() OVER (PARTITION BY ${OM_LOOKUP_KEY} ORDER BY ${OM_GENERATION_COUNT} DESC, ${OM_CREATED_AT} ASC, id ASC) AS rn
|
|
3632
|
+
FROM ${om}
|
|
3633
|
+
WHERE ${OM_LOOKUP_KEY} IN (
|
|
3634
|
+
SELECT ${OM_LOOKUP_KEY} FROM ${om} WHERE ${OM_SUPERSEDED_BY} IS NULL GROUP BY ${OM_LOOKUP_KEY} HAVING COUNT(*) > 1
|
|
3635
|
+
)
|
|
3636
|
+
)
|
|
3637
|
+
WHERE rn = 1`;
|
|
3638
|
+
await connection.execute(`UPDATE ${om} r
|
|
3639
|
+
SET r.${OM_SUPERSEDED_BY} = (SELECT h.head_id FROM (${heads}) h WHERE h.head_key = r.${OM_LOOKUP_KEY})
|
|
3640
|
+
WHERE r.${OM_SUPERSEDED_BY} IS NULL
|
|
3641
|
+
AND EXISTS (
|
|
3642
|
+
SELECT 1 FROM (${heads}) h
|
|
3643
|
+
WHERE h.head_key = r.${OM_LOOKUP_KEY}
|
|
3644
|
+
AND (
|
|
3645
|
+
r.${OM_GENERATION_COUNT} < h.head_gen
|
|
3646
|
+
OR (r.${OM_GENERATION_COUNT} = h.head_gen AND r.${OM_CREATED_AT} > h.head_created)
|
|
3647
|
+
OR (r.${OM_GENERATION_COUNT} = h.head_gen AND r.${OM_CREATED_AT} = h.head_created AND r.id > h.head_id)
|
|
3648
|
+
)
|
|
3649
|
+
)`, {}, { autoCommit: true });
|
|
3650
|
+
}
|
|
3539
3651
|
async function clearAllMemoryTables(ctx) {
|
|
3540
3652
|
await ctx.db.tx(async (client) => {
|
|
3541
3653
|
await client.none(`DELETE FROM ${table(ctx, TABLE_OBSERVATIONAL_MEMORY)}`);
|
|
@@ -3609,7 +3721,8 @@ async function createTables(ctx, connection) {
|
|
|
3609
3721
|
${OM_LAST_BUFFERED_AT_TIME} TIMESTAMP WITH TIME ZONE,
|
|
3610
3722
|
metadata JSON,
|
|
3611
3723
|
${OM_CREATED_AT} TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
3612
|
-
${OM_UPDATED_AT} TIMESTAMP WITH TIME ZONE NOT NULL
|
|
3724
|
+
${OM_UPDATED_AT} TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
3725
|
+
${OM_SUPERSEDED_BY} VARCHAR2(512)
|
|
3613
3726
|
)`, [-955]);
|
|
3614
3727
|
await ensureObservationalMemoryColumns(ctx, connection);
|
|
3615
3728
|
await relaxObservationalMemoryNullability(ctx, connection);
|
|
@@ -3747,6 +3860,10 @@ async function ensureObservationalMemoryColumns(ctx, connection) {
|
|
|
3747
3860
|
{
|
|
3748
3861
|
name: OM_UPDATED_AT,
|
|
3749
3862
|
type: "TIMESTAMP WITH TIME ZONE"
|
|
3863
|
+
},
|
|
3864
|
+
{
|
|
3865
|
+
name: OM_SUPERSEDED_BY,
|
|
3866
|
+
type: "VARCHAR2(512)"
|
|
3750
3867
|
}
|
|
3751
3868
|
];
|
|
3752
3869
|
for (const column of columns) await executeDdl(connection, `ALTER TABLE ${table(ctx, TABLE_OBSERVATIONAL_MEMORY)} ADD (${column.name} ${column.type})`, [-1430]);
|
|
@@ -4262,14 +4379,15 @@ function mergeMessageContent(existingContent, updateContent) {
|
|
|
4262
4379
|
}
|
|
4263
4380
|
//#endregion
|
|
4264
4381
|
//#region src/storage/domains/memory/observational.ts
|
|
4382
|
+
/** Hops a write aimed at a retired record may follow to reach the head before giving up. */
|
|
4383
|
+
const OM_MAX_HEAD_HOPS = 3;
|
|
4384
|
+
/** Canonical head order for a lookup key. */
|
|
4385
|
+
const OM_HEAD_ORDER = `${OM_GENERATION_COUNT} DESC, ${OM_CREATED_AT} ASC, id ASC`;
|
|
4265
4386
|
async function getObservationalMemory(ctx, threadId, resourceId) {
|
|
4266
4387
|
try {
|
|
4267
4388
|
const lookupKey = getOMKey(threadId, resourceId);
|
|
4268
4389
|
return await ctx.db.withConnection(async (connection) => {
|
|
4269
|
-
const row =
|
|
4270
|
-
WHERE ${OM_LOOKUP_KEY} = :lookupKey
|
|
4271
|
-
ORDER BY ${OM_GENERATION_COUNT} DESC
|
|
4272
|
-
FETCH FIRST 1 ROWS ONLY`, asBindParameters({ lookupKey }), executeOptions()))[0];
|
|
4390
|
+
const row = await getHeadOMRow(ctx, connection, lookupKey);
|
|
4273
4391
|
return row ? parseOMRow(row) : null;
|
|
4274
4392
|
});
|
|
4275
4393
|
} catch (error) {
|
|
@@ -4301,11 +4419,11 @@ async function getObservationalMemoryHistory(ctx, threadId, resourceId, limit =
|
|
|
4301
4419
|
}
|
|
4302
4420
|
if (options?.from) {
|
|
4303
4421
|
conditions.push(`${OM_CREATED_AT} >= :fromDate`);
|
|
4304
|
-
binds.fromDate = options.from;
|
|
4422
|
+
binds.fromDate = timestampBind(options.from);
|
|
4305
4423
|
}
|
|
4306
4424
|
if (options?.to) {
|
|
4307
4425
|
conditions.push(`${OM_CREATED_AT} <= :toDate`);
|
|
4308
|
-
binds.toDate = options.to;
|
|
4426
|
+
binds.toDate = timestampBind(options.to);
|
|
4309
4427
|
}
|
|
4310
4428
|
if (options?.groupId !== void 0) {
|
|
4311
4429
|
conditions.push(`(DBMS_LOB.INSTR(${OM_ACTIVE_OBSERVATIONS}, :groupPrefix) > 0 OR EXISTS (
|
|
@@ -4344,6 +4462,7 @@ async function getObservationalMemoryHistory(ctx, threadId, resourceId, limit =
|
|
|
4344
4462
|
}
|
|
4345
4463
|
async function initializeObservationalMemory(ctx, input) {
|
|
4346
4464
|
const now = /* @__PURE__ */ new Date();
|
|
4465
|
+
const lookupKey = getOMKey(input.threadId, input.resourceId);
|
|
4347
4466
|
const record = {
|
|
4348
4467
|
id: globalThis.crypto.randomUUID(),
|
|
4349
4468
|
scope: input.scope,
|
|
@@ -4365,13 +4484,22 @@ async function initializeObservationalMemory(ctx, input) {
|
|
|
4365
4484
|
lastBufferedAtTokens: 0,
|
|
4366
4485
|
lastBufferedAtTime: null,
|
|
4367
4486
|
config: input.config,
|
|
4368
|
-
observedTimezone: input.observedTimezone
|
|
4487
|
+
observedTimezone: input.observedTimezone,
|
|
4488
|
+
supersededBy: null
|
|
4369
4489
|
};
|
|
4370
4490
|
try {
|
|
4371
|
-
await ctx.
|
|
4491
|
+
const existing = await getObservationalMemory(ctx, input.threadId, input.resourceId);
|
|
4492
|
+
if (existing) return existing;
|
|
4493
|
+
if (await ctx.db.tx(async (_client, connection) => {
|
|
4494
|
+
const omTable = table(ctx, TABLE_OBSERVATIONAL_MEMORY);
|
|
4495
|
+
await connection.execute(`LOCK TABLE ${omTable} IN SHARE ROW EXCLUSIVE MODE`);
|
|
4496
|
+
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;
|
|
4372
4497
|
await insertOMRecord(ctx, connection, record);
|
|
4373
|
-
|
|
4374
|
-
return record;
|
|
4498
|
+
return true;
|
|
4499
|
+
})) return record;
|
|
4500
|
+
const head = await getObservationalMemory(ctx, input.threadId, input.resourceId);
|
|
4501
|
+
if (!head) throw new Error(`Observational memory record not found after initialization: ${lookupKey}`);
|
|
4502
|
+
return head;
|
|
4375
4503
|
} catch (error) {
|
|
4376
4504
|
throw storageError$1("INITIALIZE_OBSERVATIONAL_MEMORY", "FAILED", {
|
|
4377
4505
|
threadId: input.threadId ?? "",
|
|
@@ -4393,7 +4521,17 @@ async function insertObservationalMemoryRecord(ctx, record) {
|
|
|
4393
4521
|
}
|
|
4394
4522
|
async function updateActiveObservations(ctx, input) {
|
|
4395
4523
|
try {
|
|
4396
|
-
await ctx.db.tx(async (_client, connection) => {
|
|
4524
|
+
return await ctx.db.tx(async (_client, connection) => {
|
|
4525
|
+
const row = await lockOMRow(ctx, connection, input.id, "UPDATE_ACTIVE_OBSERVATIONS");
|
|
4526
|
+
if (row.supersededBy) return {
|
|
4527
|
+
applied: false,
|
|
4528
|
+
reason: "retired"
|
|
4529
|
+
};
|
|
4530
|
+
if (input.expectedActiveObservations !== void 0 && input.expectedActiveObservations !== stringOrEmpty(row.activeObservations)) return {
|
|
4531
|
+
applied: false,
|
|
4532
|
+
reason: "conflict"
|
|
4533
|
+
};
|
|
4534
|
+
const lastObservedAt = maxObservationCursor(row.lastObservedAt, input.lastObservedAt);
|
|
4397
4535
|
assertRowsAffected((await connection.execute(`
|
|
4398
4536
|
UPDATE ${table(ctx, TABLE_OBSERVATIONAL_MEMORY)}
|
|
4399
4537
|
SET ${OM_ACTIVE_OBSERVATIONS} = :activeObservations,
|
|
@@ -4407,12 +4545,13 @@ async function updateActiveObservations(ctx, input) {
|
|
|
4407
4545
|
WHERE id = :id`, {
|
|
4408
4546
|
id: input.id,
|
|
4409
4547
|
activeObservations: nullableClobBind(input.observations),
|
|
4410
|
-
lastObservedAt:
|
|
4548
|
+
lastObservedAt: timestampBind(lastObservedAt),
|
|
4411
4549
|
tokenCount: Math.round(input.tokenCount),
|
|
4412
4550
|
observedMessageIds: nullableJsonBind(input.observedMessageIds),
|
|
4413
4551
|
observedTimezone: input.observedTimezone ?? null,
|
|
4414
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
4552
|
+
updatedAt: timestampBind(/* @__PURE__ */ new Date())
|
|
4415
4553
|
})).rowsAffected, "UPDATE_ACTIVE_OBSERVATIONS", input.id);
|
|
4554
|
+
return { applied: true };
|
|
4416
4555
|
});
|
|
4417
4556
|
} catch (error) {
|
|
4418
4557
|
if (error instanceof MastraError) throw error;
|
|
@@ -4420,39 +4559,73 @@ async function updateActiveObservations(ctx, input) {
|
|
|
4420
4559
|
}
|
|
4421
4560
|
}
|
|
4422
4561
|
async function createReflectionGeneration(ctx, input) {
|
|
4562
|
+
const { currentRecord } = input;
|
|
4563
|
+
try {
|
|
4564
|
+
return await ctx.db.tx(async (_client, connection) => {
|
|
4565
|
+
const row = await findOMRowForUpdate(ctx, connection, currentRecord.id);
|
|
4566
|
+
if (!row) return currentRecord;
|
|
4567
|
+
if (row.supersededBy) return parseOMRow(await resolveLiveOMRow(ctx, connection, row));
|
|
4568
|
+
const stored = parseOMRow(row);
|
|
4569
|
+
const plan = planReflectionGenerationText({
|
|
4570
|
+
storedObservations: stored.activeObservations,
|
|
4571
|
+
storedObservationTokenCount: stored.observationTokenCount,
|
|
4572
|
+
snapshotObservations: currentRecord.activeObservations,
|
|
4573
|
+
snapshotObservationTokenCount: currentRecord.observationTokenCount,
|
|
4574
|
+
reflection: input.reflection,
|
|
4575
|
+
tokenCount: input.tokenCount
|
|
4576
|
+
});
|
|
4577
|
+
if (!plan) return stored;
|
|
4578
|
+
return rollOverOMRow(ctx, connection, stored, plan.observations, plan.tokenCount, input.newRecordId);
|
|
4579
|
+
});
|
|
4580
|
+
} catch (error) {
|
|
4581
|
+
if (error instanceof MastraError) throw error;
|
|
4582
|
+
throw storageError$1("CREATE_REFLECTION_GENERATION", "FAILED", { id: currentRecord.id }, error);
|
|
4583
|
+
}
|
|
4584
|
+
}
|
|
4585
|
+
/**
|
|
4586
|
+
* Create the next generation from the stored (live, row-locked) record and retire it in the
|
|
4587
|
+
* same transaction. Buffered chunks move to the new generation; the cursor, buffering markers,
|
|
4588
|
+
* flags, and counters carry over; buffered reflection state does not.
|
|
4589
|
+
*/
|
|
4590
|
+
async function rollOverOMRow(ctx, connection, stored, observations, tokenCount, newRecordId) {
|
|
4423
4591
|
const now = /* @__PURE__ */ new Date();
|
|
4424
4592
|
const record = {
|
|
4425
|
-
id: globalThis.crypto.randomUUID(),
|
|
4426
|
-
scope:
|
|
4427
|
-
threadId:
|
|
4428
|
-
resourceId:
|
|
4593
|
+
id: newRecordId ?? globalThis.crypto.randomUUID(),
|
|
4594
|
+
scope: stored.scope,
|
|
4595
|
+
threadId: stored.threadId,
|
|
4596
|
+
resourceId: stored.resourceId,
|
|
4429
4597
|
createdAt: now,
|
|
4430
4598
|
updatedAt: now,
|
|
4431
|
-
lastObservedAt:
|
|
4599
|
+
lastObservedAt: stored.lastObservedAt,
|
|
4432
4600
|
originType: "reflection",
|
|
4433
|
-
generationCount:
|
|
4434
|
-
activeObservations:
|
|
4435
|
-
totalTokensObserved:
|
|
4436
|
-
observationTokenCount: Math.round(
|
|
4437
|
-
pendingMessageTokens:
|
|
4601
|
+
generationCount: stored.generationCount + 1,
|
|
4602
|
+
activeObservations: observations,
|
|
4603
|
+
totalTokensObserved: stored.totalTokensObserved,
|
|
4604
|
+
observationTokenCount: Math.round(tokenCount),
|
|
4605
|
+
pendingMessageTokens: stored.pendingMessageTokens,
|
|
4438
4606
|
isReflecting: false,
|
|
4439
4607
|
isObserving: false,
|
|
4440
|
-
isBufferingObservation:
|
|
4608
|
+
isBufferingObservation: stored.isBufferingObservation,
|
|
4441
4609
|
isBufferingReflection: false,
|
|
4442
|
-
lastBufferedAtTokens:
|
|
4443
|
-
lastBufferedAtTime: null,
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4610
|
+
lastBufferedAtTokens: stored.lastBufferedAtTokens,
|
|
4611
|
+
lastBufferedAtTime: stored.lastBufferedAtTime ?? null,
|
|
4612
|
+
bufferedObservationChunks: stored.bufferedObservationChunks?.length ? stored.bufferedObservationChunks : void 0,
|
|
4613
|
+
config: stored.config,
|
|
4614
|
+
metadata: stored.metadata,
|
|
4615
|
+
observedTimezone: stored.observedTimezone,
|
|
4616
|
+
supersededBy: null
|
|
4447
4617
|
};
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4618
|
+
await insertOMRecord(ctx, connection, record, now);
|
|
4619
|
+
if ((await connection.execute(`UPDATE ${table(ctx, TABLE_OBSERVATIONAL_MEMORY)}
|
|
4620
|
+
SET "supersededBy" = :newId,
|
|
4621
|
+
"bufferedObservationChunks" = NULL,
|
|
4622
|
+
"updatedAt" = :updatedAt
|
|
4623
|
+
WHERE id = :id AND "supersededBy" IS NULL`, {
|
|
4624
|
+
newId: record.id,
|
|
4625
|
+
updatedAt: timestampBind(now),
|
|
4626
|
+
id: stored.id
|
|
4627
|
+
})).rowsAffected !== 1) throw storageError$1("CREATE_REFLECTION_GENERATION", "RETIRE_FAILED", { id: stored.id }, /* @__PURE__ */ new Error(`Failed to retire observational memory record ${stored.id}`));
|
|
4628
|
+
return record;
|
|
4456
4629
|
}
|
|
4457
4630
|
async function setReflectingFlag(ctx, id, isReflecting) {
|
|
4458
4631
|
await updateOMFlag(ctx, id, OM_IS_REFLECTING, isReflecting, "SET_REFLECTING_FLAG");
|
|
@@ -4467,9 +4640,10 @@ async function setBufferingObservationFlag(ctx, id, isBuffering, lastBufferedAtT
|
|
|
4467
4640
|
const binds = {
|
|
4468
4641
|
id,
|
|
4469
4642
|
isBuffering: boolToNumber(isBuffering),
|
|
4470
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
4643
|
+
updatedAt: timestampBind(/* @__PURE__ */ new Date())
|
|
4471
4644
|
};
|
|
4472
4645
|
if (lastBufferedAtTokens !== void 0) binds.lastBufferedAtTokens = Math.round(lastBufferedAtTokens);
|
|
4646
|
+
binds.id = (await resolveLiveOMRow(ctx, connection, await lockOMRow(ctx, connection, id, "SET_BUFFERING_OBSERVATION_FLAG"))).id;
|
|
4473
4647
|
assertRowsAffected((await connection.execute(`UPDATE ${table(ctx, TABLE_OBSERVATIONAL_MEMORY)}
|
|
4474
4648
|
SET ${OM_IS_BUFFERING_OBSERVATION} = :isBuffering,
|
|
4475
4649
|
${OM_UPDATED_AT} = :updatedAt
|
|
@@ -4500,7 +4674,17 @@ async function clearObservationalMemory(ctx, threadId, resourceId) {
|
|
|
4500
4674
|
}
|
|
4501
4675
|
async function setPendingMessageTokens(ctx, id, tokenCount) {
|
|
4502
4676
|
try {
|
|
4503
|
-
await
|
|
4677
|
+
await ctx.db.tx(async (_client, connection) => {
|
|
4678
|
+
const target = await resolveLiveOMRow(ctx, connection, await lockOMRow(ctx, connection, id, "SET_PENDING_MESSAGE_TOKENS"));
|
|
4679
|
+
assertRowsAffected((await connection.execute(`UPDATE ${table(ctx, TABLE_OBSERVATIONAL_MEMORY)}
|
|
4680
|
+
SET ${OM_PENDING_MESSAGE_TOKENS} = :tokenCount,
|
|
4681
|
+
${OM_UPDATED_AT} = :updatedAt
|
|
4682
|
+
WHERE id = :id`, {
|
|
4683
|
+
tokenCount: Math.round(tokenCount),
|
|
4684
|
+
updatedAt: timestampBind(/* @__PURE__ */ new Date()),
|
|
4685
|
+
id: target.id
|
|
4686
|
+
})).rowsAffected, "SET_PENDING_MESSAGE_TOKENS", id);
|
|
4687
|
+
});
|
|
4504
4688
|
} catch (error) {
|
|
4505
4689
|
if (error instanceof MastraError) throw error;
|
|
4506
4690
|
throw storageError$1("SET_PENDING_MESSAGE_TOKENS", "FAILED", {
|
|
@@ -4522,7 +4706,7 @@ async function updateObservationalMemoryConfig(ctx, input) {
|
|
|
4522
4706
|
WHERE id = :id`, {
|
|
4523
4707
|
id: input.id,
|
|
4524
4708
|
config: jsonBind(merged),
|
|
4525
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
4709
|
+
updatedAt: timestampBind(/* @__PURE__ */ new Date())
|
|
4526
4710
|
})).rowsAffected, "UPDATE_OM_CONFIG", input.id);
|
|
4527
4711
|
});
|
|
4528
4712
|
} catch (error) {
|
|
@@ -4568,6 +4752,7 @@ async function insertOMRecord(ctx, connection, record, timestamp = record.create
|
|
|
4568
4752
|
${OM_LAST_BUFFERED_AT_TOKENS},
|
|
4569
4753
|
${OM_LAST_BUFFERED_AT_TIME},
|
|
4570
4754
|
metadata,
|
|
4755
|
+
${OM_SUPERSEDED_BY},
|
|
4571
4756
|
${OM_CREATED_AT},
|
|
4572
4757
|
${OM_UPDATED_AT}
|
|
4573
4758
|
) VALUES (
|
|
@@ -4603,6 +4788,7 @@ async function insertOMRecord(ctx, connection, record, timestamp = record.create
|
|
|
4603
4788
|
:lastBufferedAtTokens,
|
|
4604
4789
|
:lastBufferedAtTime,
|
|
4605
4790
|
:metadata,
|
|
4791
|
+
:supersededBy,
|
|
4606
4792
|
:createdAt,
|
|
4607
4793
|
:updatedAt
|
|
4608
4794
|
)`, {
|
|
@@ -4616,8 +4802,8 @@ async function insertOMRecord(ctx, connection, record, timestamp = record.create
|
|
|
4616
4802
|
originType: record.originType ?? "initial",
|
|
4617
4803
|
config: jsonBind(record.config ?? {}),
|
|
4618
4804
|
generationCount: record.generationCount ?? 0,
|
|
4619
|
-
lastObservedAt: record.lastObservedAt
|
|
4620
|
-
lastReflectionAt: record.originType === "reflection" ? timestamp : null,
|
|
4805
|
+
lastObservedAt: timestampBind(record.lastObservedAt),
|
|
4806
|
+
lastReflectionAt: record.originType === "reflection" ? timestampBind(timestamp) : null,
|
|
4621
4807
|
pendingMessageTokens: Math.round(record.pendingMessageTokens ?? 0),
|
|
4622
4808
|
totalTokensObserved: Math.round(record.totalTokensObserved ?? 0),
|
|
4623
4809
|
observationTokenCount: Math.round(record.observationTokenCount ?? 0),
|
|
@@ -4636,10 +4822,11 @@ async function insertOMRecord(ctx, connection, record, timestamp = record.create
|
|
|
4636
4822
|
isBufferingObservation: boolToNumber(record.isBufferingObservation),
|
|
4637
4823
|
isBufferingReflection: boolToNumber(record.isBufferingReflection),
|
|
4638
4824
|
lastBufferedAtTokens: Math.round(record.lastBufferedAtTokens ?? 0),
|
|
4639
|
-
lastBufferedAtTime: record.lastBufferedAtTime
|
|
4825
|
+
lastBufferedAtTime: timestampBind(record.lastBufferedAtTime),
|
|
4640
4826
|
metadata: nullableJsonBind(record.metadata),
|
|
4641
|
-
|
|
4642
|
-
|
|
4827
|
+
supersededBy: record.supersededBy ?? null,
|
|
4828
|
+
createdAt: timestampBind(record.createdAt),
|
|
4829
|
+
updatedAt: timestampBind(record.updatedAt)
|
|
4643
4830
|
});
|
|
4644
4831
|
}
|
|
4645
4832
|
async function updateOMFlag(ctx, id, column, value, operation) {
|
|
@@ -4663,7 +4850,7 @@ async function updateOMColumns(ctx, id, operation, columns) {
|
|
|
4663
4850
|
WHERE id = :id`, {
|
|
4664
4851
|
...binds,
|
|
4665
4852
|
id,
|
|
4666
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
4853
|
+
updatedAt: timestampBind(/* @__PURE__ */ new Date())
|
|
4667
4854
|
})).rowsAffected, operation, id);
|
|
4668
4855
|
});
|
|
4669
4856
|
}
|
|
@@ -4701,6 +4888,7 @@ function omSelect() {
|
|
|
4701
4888
|
${OM_LAST_BUFFERED_AT_TOKENS} AS "lastBufferedAtTokens",
|
|
4702
4889
|
${OM_LAST_BUFFERED_AT_TIME} AS "lastBufferedAtTime",
|
|
4703
4890
|
metadata AS "metadata",
|
|
4891
|
+
${OM_SUPERSEDED_BY} AS "supersededBy",
|
|
4704
4892
|
${OM_CREATED_AT} AS "createdAt",
|
|
4705
4893
|
${OM_UPDATED_AT} AS "updatedAt"`;
|
|
4706
4894
|
}
|
|
@@ -4736,15 +4924,60 @@ function parseOMRow(row) {
|
|
|
4736
4924
|
config: parseJson(row.config),
|
|
4737
4925
|
metadata: parseOptionalJsonObject(row.metadata, { emptyObjectAsUndefined: true }),
|
|
4738
4926
|
observedMessageIds: parseOptionalStringArray(row.observedMessageIds),
|
|
4739
|
-
observedTimezone: row.observedTimezone ? String(row.observedTimezone) : void 0
|
|
4927
|
+
observedTimezone: row.observedTimezone ? String(row.observedTimezone) : void 0,
|
|
4928
|
+
supersededBy: row.supersededBy ? String(row.supersededBy) : null
|
|
4740
4929
|
};
|
|
4741
4930
|
}
|
|
4931
|
+
/** The canonical head row of a lookup key (non-locking read). */
|
|
4932
|
+
async function getHeadOMRow(ctx, connection, lookupKey) {
|
|
4933
|
+
return rows(await connection.execute(`${omSelect()} FROM ${table(ctx, TABLE_OBSERVATIONAL_MEMORY)}
|
|
4934
|
+
WHERE ${OM_LOOKUP_KEY} = :lookupKey
|
|
4935
|
+
ORDER BY ${OM_HEAD_ORDER}
|
|
4936
|
+
FETCH FIRST 1 ROWS ONLY`, asBindParameters({ lookupKey }), executeOptions()))[0];
|
|
4937
|
+
}
|
|
4938
|
+
/** Row-locks one record by primary key for the rest of the transaction; `undefined` when missing. */
|
|
4939
|
+
async function findOMRowForUpdate(ctx, connection, id) {
|
|
4940
|
+
return rows(await connection.execute(`${omSelect()} FROM ${table(ctx, TABLE_OBSERVATIONAL_MEMORY)} WHERE id = :id FOR UPDATE`, { id }, executeOptions()))[0];
|
|
4941
|
+
}
|
|
4942
|
+
/**
|
|
4943
|
+
* Row-locks one record by primary key. Observational memory updates are incremental and
|
|
4944
|
+
* order-sensitive, so mutating paths derive their next state from a locked row.
|
|
4945
|
+
* Throws "record not found" when it is missing.
|
|
4946
|
+
*/
|
|
4947
|
+
async function lockOMRow(ctx, connection, id, operation) {
|
|
4948
|
+
const row = await findOMRowForUpdate(ctx, connection, id);
|
|
4949
|
+
if (!row) {
|
|
4950
|
+
assertRowsAffected(0, operation, id);
|
|
4951
|
+
throw new Error(`Observational memory record not found: ${id}`);
|
|
4952
|
+
}
|
|
4953
|
+
return row;
|
|
4954
|
+
}
|
|
4955
|
+
/**
|
|
4956
|
+
* The live, row-locked record a lifecycle write aimed at `row` lands on: `row` itself while
|
|
4957
|
+
* live, otherwise the head of its lookup key (locked, then re-checked — a rollover may retire
|
|
4958
|
+
* it between the head read and the lock).
|
|
4959
|
+
*/
|
|
4960
|
+
async function resolveLiveOMRow(ctx, connection, row) {
|
|
4961
|
+
let current = row;
|
|
4962
|
+
for (let hop = 0; current.supersededBy; hop++) {
|
|
4963
|
+
const head = hop < OM_MAX_HEAD_HOPS ? await getHeadOMRow(ctx, connection, current.lookupKey) : void 0;
|
|
4964
|
+
const locked = head && head.id !== current.id ? await findOMRowForUpdate(ctx, connection, head.id) : void 0;
|
|
4965
|
+
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`));
|
|
4966
|
+
current = locked;
|
|
4967
|
+
}
|
|
4968
|
+
return current;
|
|
4969
|
+
}
|
|
4742
4970
|
//#endregion
|
|
4743
4971
|
//#region src/storage/domains/memory/observational-buffering.ts
|
|
4744
4972
|
async function updateBufferedObservations(ctx, input) {
|
|
4745
4973
|
try {
|
|
4746
|
-
await ctx.db.tx(async (_client, connection) => {
|
|
4747
|
-
const
|
|
4974
|
+
return await ctx.db.tx(async (_client, connection) => {
|
|
4975
|
+
const row = await resolveLiveOMRow(ctx, connection, await lockOMRow(ctx, connection, input.id, "UPDATE_BUFFERED_OBSERVATIONS"));
|
|
4976
|
+
const existingChunks = parseBufferedChunks(row.bufferedObservationChunks);
|
|
4977
|
+
if (existingChunks.some((existing) => existing.cycleId === input.chunk.cycleId) || isBufferedChunkCoveredByCursor(input.chunk.lastObservedAt, row.lastObservedAt)) return {
|
|
4978
|
+
persisted: false,
|
|
4979
|
+
recordId: row.id
|
|
4980
|
+
};
|
|
4748
4981
|
const newChunk = {
|
|
4749
4982
|
id: `ombuf-${globalThis.crypto.randomUUID()}`,
|
|
4750
4983
|
cycleId: input.chunk.cycleId,
|
|
@@ -4760,18 +4993,21 @@ async function updateBufferedObservations(ctx, input) {
|
|
|
4760
4993
|
extractedValues: input.chunk.extractedValues,
|
|
4761
4994
|
extractionFailures: input.chunk.extractionFailures
|
|
4762
4995
|
};
|
|
4763
|
-
const
|
|
4764
|
-
const lastBufferedAtTimeSql = input.lastBufferedAtTime === void 0 || input.lastBufferedAtTime === null ? "" : `,\n ${OM_LAST_BUFFERED_AT_TIME} = :lastBufferedAtTime`;
|
|
4765
|
-
const binds = {
|
|
4766
|
-
id: input.id,
|
|
4767
|
-
bufferedObservationChunks: nullableJsonBind(updatedChunks),
|
|
4768
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
4769
|
-
};
|
|
4770
|
-
if (input.lastBufferedAtTime !== void 0 && input.lastBufferedAtTime !== null) binds.lastBufferedAtTime = toDate(input.lastBufferedAtTime);
|
|
4996
|
+
const lastBufferedAtTime = maxObservationCursor(row.lastBufferedAtTime, input.lastBufferedAtTime);
|
|
4771
4997
|
assertRowsAffected((await connection.execute(`UPDATE ${table(ctx, TABLE_OBSERVATIONAL_MEMORY)}
|
|
4772
4998
|
SET ${OM_BUFFERED_OBSERVATION_CHUNKS} = :bufferedObservationChunks,
|
|
4773
|
-
${
|
|
4774
|
-
|
|
4999
|
+
${OM_LAST_BUFFERED_AT_TIME} = :lastBufferedAtTime,
|
|
5000
|
+
${OM_UPDATED_AT} = :updatedAt
|
|
5001
|
+
WHERE id = :id`, asBindParameters({
|
|
5002
|
+
id: row.id,
|
|
5003
|
+
bufferedObservationChunks: nullableJsonBind([...existingChunks, newChunk]),
|
|
5004
|
+
lastBufferedAtTime: timestampBind(lastBufferedAtTime),
|
|
5005
|
+
updatedAt: timestampBind(/* @__PURE__ */ new Date())
|
|
5006
|
+
}))).rowsAffected, "UPDATE_BUFFERED_OBSERVATIONS", row.id);
|
|
5007
|
+
return {
|
|
5008
|
+
persisted: true,
|
|
5009
|
+
recordId: row.id
|
|
5010
|
+
};
|
|
4775
5011
|
});
|
|
4776
5012
|
} catch (error) {
|
|
4777
5013
|
if (error instanceof MastraError) throw error;
|
|
@@ -4782,10 +5018,22 @@ async function swapBufferedToActive(ctx, input) {
|
|
|
4782
5018
|
try {
|
|
4783
5019
|
return await ctx.db.tx(async (_client, connection) => {
|
|
4784
5020
|
const row = await lockOMRow(ctx, connection, input.id, "SWAP_BUFFERED_TO_ACTIVE");
|
|
4785
|
-
|
|
5021
|
+
if (row.supersededBy) return {
|
|
5022
|
+
...emptySwapResult(),
|
|
5023
|
+
retired: true
|
|
5024
|
+
};
|
|
5025
|
+
const refreshedWeights = new Map((input.bufferedChunks ?? []).map((chunk) => [chunk.id, chunk.messageTokens]));
|
|
5026
|
+
const chunks = parseBufferedChunks(row.bufferedObservationChunks).map((chunk) => {
|
|
5027
|
+
const weight = refreshedWeights.get(chunk.id);
|
|
5028
|
+
return weight === void 0 ? chunk : {
|
|
5029
|
+
...chunk,
|
|
5030
|
+
messageTokens: weight
|
|
5031
|
+
};
|
|
5032
|
+
});
|
|
4786
5033
|
if (chunks.length === 0) return emptySwapResult();
|
|
4787
5034
|
const activation = calculateBufferedActivation(chunks, input);
|
|
4788
5035
|
const lastObservedAt = input.lastObservedAt ?? (activation.activatedChunks.at(-1)?.lastObservedAt ? toDate(activation.activatedChunks.at(-1).lastObservedAt) : /* @__PURE__ */ new Date());
|
|
5036
|
+
const storedCursor = maxObservationCursor(row.lastObservedAt, lastObservedAt);
|
|
4789
5037
|
const boundary = `\n\n--- message boundary (${lastObservedAt.toISOString()}) ---\n\n`;
|
|
4790
5038
|
const existingActive = stringOrEmpty(row.activeObservations);
|
|
4791
5039
|
const newActive = existingActive ? `${existingActive}${boundary}${activation.activatedContent}` : activation.activatedContent;
|
|
@@ -4803,8 +5051,8 @@ async function swapBufferedToActive(ctx, input) {
|
|
|
4803
5051
|
observationTokens: activation.activatedTokens,
|
|
4804
5052
|
pendingMessageTokens: pendingTokens,
|
|
4805
5053
|
bufferedObservationChunks: nullableJsonBind(activation.remainingChunks.length > 0 ? activation.remainingChunks : null),
|
|
4806
|
-
lastObservedAt,
|
|
4807
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
5054
|
+
lastObservedAt: timestampBind(storedCursor),
|
|
5055
|
+
updatedAt: timestampBind(/* @__PURE__ */ new Date())
|
|
4808
5056
|
})).rowsAffected, "SWAP_BUFFERED_TO_ACTIVE", input.id);
|
|
4809
5057
|
return activation.result;
|
|
4810
5058
|
});
|
|
@@ -4832,7 +5080,7 @@ async function updateBufferedReflection(ctx, input) {
|
|
|
4832
5080
|
tokenCount: Math.round(input.tokenCount),
|
|
4833
5081
|
inputTokenCount: Math.round(input.inputTokenCount),
|
|
4834
5082
|
reflectedObservationLineCount: Math.round(input.reflectedObservationLineCount),
|
|
4835
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
5083
|
+
updatedAt: timestampBind(/* @__PURE__ */ new Date())
|
|
4836
5084
|
})).rowsAffected, "UPDATE_BUFFERED_REFLECTION", input.id);
|
|
4837
5085
|
});
|
|
4838
5086
|
} catch (error) {
|
|
@@ -4841,66 +5089,32 @@ async function updateBufferedReflection(ctx, input) {
|
|
|
4841
5089
|
}
|
|
4842
5090
|
}
|
|
4843
5091
|
async function swapBufferedReflectionToActive(ctx, input) {
|
|
5092
|
+
const { currentRecord } = input;
|
|
4844
5093
|
try {
|
|
4845
5094
|
return await ctx.db.tx(async (_client, connection) => {
|
|
4846
|
-
const row = await
|
|
4847
|
-
|
|
4848
|
-
if (
|
|
4849
|
-
const
|
|
4850
|
-
const
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
const
|
|
4854
|
-
const
|
|
4855
|
-
|
|
4856
|
-
scope: lockedRecord.scope,
|
|
4857
|
-
threadId: lockedRecord.threadId,
|
|
4858
|
-
resourceId: lockedRecord.resourceId,
|
|
4859
|
-
createdAt: now,
|
|
4860
|
-
updatedAt: now,
|
|
4861
|
-
lastObservedAt: lockedRecord.lastObservedAt,
|
|
4862
|
-
originType: "reflection",
|
|
4863
|
-
generationCount: lockedRecord.generationCount + 1,
|
|
4864
|
-
activeObservations: newObservations,
|
|
4865
|
-
totalTokensObserved: lockedRecord.totalTokensObserved,
|
|
4866
|
-
observationTokenCount: Math.round(input.tokenCount),
|
|
4867
|
-
pendingMessageTokens: 0,
|
|
4868
|
-
isReflecting: false,
|
|
4869
|
-
isObserving: false,
|
|
4870
|
-
isBufferingObservation: false,
|
|
4871
|
-
isBufferingReflection: false,
|
|
4872
|
-
lastBufferedAtTokens: 0,
|
|
4873
|
-
lastBufferedAtTime: null,
|
|
4874
|
-
config: lockedRecord.config,
|
|
4875
|
-
metadata: lockedRecord.metadata,
|
|
4876
|
-
observedTimezone: lockedRecord.observedTimezone
|
|
4877
|
-
};
|
|
4878
|
-
await insertOMRecord(ctx, connection, newRecord, now);
|
|
5095
|
+
const row = await findOMRowForUpdate(ctx, connection, currentRecord.id);
|
|
5096
|
+
if (!row) return currentRecord;
|
|
5097
|
+
if (row.supersededBy) return parseOMRow(await resolveLiveOMRow(ctx, connection, row));
|
|
5098
|
+
const stored = parseOMRow(row);
|
|
5099
|
+
const bufferedReflection = stored.bufferedReflection ?? "";
|
|
5100
|
+
if (!bufferedReflection) throw storageError$1("SWAP_BUFFERED_REFLECTION_TO_ACTIVE", "NO_CONTENT", { id: currentRecord.id }, /* @__PURE__ */ new Error("No buffered reflection to swap"), ErrorCategory.USER);
|
|
5101
|
+
if (!isAppendOnlySince(stored.activeObservations, currentRecord.activeObservations)) return stored;
|
|
5102
|
+
const reflectedLineCount = stored.reflectedObservationLineCount ?? 0;
|
|
5103
|
+
const unreflectedContent = stored.activeObservations.split("\n").slice(reflectedLineCount).join("\n").trim();
|
|
5104
|
+
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);
|
|
4879
5105
|
assertRowsAffected((await connection.execute(`UPDATE ${table(ctx, TABLE_OBSERVATIONAL_MEMORY)}
|
|
4880
5106
|
SET ${OM_BUFFERED_REFLECTION} = NULL,
|
|
4881
5107
|
${OM_BUFFERED_REFLECTION_TOKENS} = NULL,
|
|
4882
5108
|
${OM_BUFFERED_REFLECTION_INPUT_TOKENS} = NULL,
|
|
4883
|
-
${OM_REFLECTED_OBSERVATION_LINE_COUNT} = NULL
|
|
4884
|
-
|
|
4885
|
-
WHERE id = :id`, {
|
|
4886
|
-
id: input.currentRecord.id,
|
|
4887
|
-
updatedAt: now
|
|
4888
|
-
})).rowsAffected, "SWAP_BUFFERED_REFLECTION_TO_ACTIVE", input.currentRecord.id);
|
|
5109
|
+
${OM_REFLECTED_OBSERVATION_LINE_COUNT} = NULL
|
|
5110
|
+
WHERE id = :id`, { id: stored.id })).rowsAffected, "SWAP_BUFFERED_REFLECTION_TO_ACTIVE", stored.id);
|
|
4889
5111
|
return newRecord;
|
|
4890
5112
|
});
|
|
4891
5113
|
} catch (error) {
|
|
4892
5114
|
if (error instanceof MastraError) throw error;
|
|
4893
|
-
throw storageError$1("SWAP_BUFFERED_REFLECTION_TO_ACTIVE", "FAILED", { id:
|
|
5115
|
+
throw storageError$1("SWAP_BUFFERED_REFLECTION_TO_ACTIVE", "FAILED", { id: currentRecord.id }, error);
|
|
4894
5116
|
}
|
|
4895
5117
|
}
|
|
4896
|
-
async function lockOMRow(ctx, connection, id, operation) {
|
|
4897
|
-
const row = rows(await connection.execute(`${omSelect()} FROM ${table(ctx, TABLE_OBSERVATIONAL_MEMORY)} WHERE id = :id FOR UPDATE`, { id }, executeOptions()))[0];
|
|
4898
|
-
if (!row) {
|
|
4899
|
-
assertRowsAffected(0, operation, id);
|
|
4900
|
-
throw new Error(`Observational memory record not found: ${id}`);
|
|
4901
|
-
}
|
|
4902
|
-
return row;
|
|
4903
|
-
}
|
|
4904
5118
|
function emptySwapResult() {
|
|
4905
5119
|
return {
|
|
4906
5120
|
chunksActivated: 0,
|
|
@@ -5435,6 +5649,15 @@ var MemoryOracle = class MemoryOracle extends MemoryStorage {
|
|
|
5435
5649
|
async init() {
|
|
5436
5650
|
await initMemorySchema(this.ctx);
|
|
5437
5651
|
}
|
|
5652
|
+
/**
|
|
5653
|
+
* Mark observational memory rows that are not their key's canonical head as superseded.
|
|
5654
|
+
* Runs as part of `init()`; `OracleStore` also runs it on every start when the memory schema
|
|
5655
|
+
* migration is unchanged and skipped.
|
|
5656
|
+
* @internal
|
|
5657
|
+
*/
|
|
5658
|
+
async backfillObservationalMemorySupersededBy() {
|
|
5659
|
+
await this.ctx.db.withConnection((connection) => backfillObservationalMemorySupersededBy(this.ctx, connection));
|
|
5660
|
+
}
|
|
5438
5661
|
async dangerouslyClearAll() {
|
|
5439
5662
|
await clearAllMemoryTables(this.ctx);
|
|
5440
5663
|
}
|
|
@@ -5571,6 +5794,9 @@ var MemoryOracle = class MemoryOracle extends MemoryStorage {
|
|
|
5571
5794
|
return insertObservationalMemoryRecord(this.ctx, record);
|
|
5572
5795
|
}
|
|
5573
5796
|
async updateActiveObservations(input) {
|
|
5797
|
+
assertActiveObservationsApplied(await this.commitActiveObservations(input), input.id);
|
|
5798
|
+
}
|
|
5799
|
+
async commitActiveObservations(input) {
|
|
5574
5800
|
return updateActiveObservations(this.ctx, input);
|
|
5575
5801
|
}
|
|
5576
5802
|
async createReflectionGeneration(input) {
|
|
@@ -5598,6 +5824,9 @@ var MemoryOracle = class MemoryOracle extends MemoryStorage {
|
|
|
5598
5824
|
return updateObservationalMemoryConfig(this.ctx, input);
|
|
5599
5825
|
}
|
|
5600
5826
|
async updateBufferedObservations(input) {
|
|
5827
|
+
await this.appendBufferedObservations(input);
|
|
5828
|
+
}
|
|
5829
|
+
async appendBufferedObservations(input) {
|
|
5601
5830
|
return updateBufferedObservations(this.ctx, input);
|
|
5602
5831
|
}
|
|
5603
5832
|
async swapBufferedToActive(input) {
|
|
@@ -8477,7 +8706,7 @@ function checksumMigration(input) {
|
|
|
8477
8706
|
//#region src/storage/index.ts
|
|
8478
8707
|
const STORE_NAME = "ORACLEDB";
|
|
8479
8708
|
const DOMAIN_SCHEMA_VERSIONS = {
|
|
8480
|
-
R001_MEMORY_SCHEMA:
|
|
8709
|
+
R001_MEMORY_SCHEMA: 2,
|
|
8481
8710
|
R002_WORKFLOWS_SCHEMA: 1,
|
|
8482
8711
|
R003_OBSERVABILITY_SCHEMA: 1,
|
|
8483
8712
|
R004_SCORES_SCHEMA: 1,
|
|
@@ -8565,6 +8794,10 @@ var OracleStore = class extends MastraCompositeStore {
|
|
|
8565
8794
|
async runMigrations(forceRepeatable) {
|
|
8566
8795
|
try {
|
|
8567
8796
|
const results = await this.migrationRegistry.run(this.storageMigrations(), { forceRepeatable });
|
|
8797
|
+
if (results.find((result) => result.id === "R001_MEMORY_SCHEMA")?.status === "skipped") {
|
|
8798
|
+
const memory = this.stores.memory;
|
|
8799
|
+
if (memory instanceof MemoryOracle) await memory.backfillObservationalMemorySupersededBy();
|
|
8800
|
+
}
|
|
8568
8801
|
this.isInitialized = true;
|
|
8569
8802
|
return results;
|
|
8570
8803
|
} catch (error) {
|