@mastra/oracledb 0.5.1 → 0.5.2-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +677 -170
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +678 -171
- package/dist/index.js.map +1 -1
- package/dist/storage/db/run-fencing.d.ts +32 -0
- package/dist/storage/db/run-fencing.d.ts.map +1 -0
- package/dist/storage/domains/memory/index.d.ts +28 -8
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/domains/memory/messages.d.ts +6 -4
- package/dist/storage/domains/memory/messages.d.ts.map +1 -1
- package/dist/storage/domains/memory/observational-buffering.d.ts +6 -5
- package/dist/storage/domains/memory/observational-buffering.d.ts.map +1 -1
- package/dist/storage/domains/memory/observational.d.ts +25 -3
- package/dist/storage/domains/memory/observational.d.ts.map +1 -1
- package/dist/storage/domains/memory/resources.d.ts +3 -2
- package/dist/storage/domains/memory/resources.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/threads.d.ts +5 -3
- package/dist/storage/domains/memory/threads.d.ts.map +1 -1
- package/dist/storage/domains/memory/utils.d.ts +15 -2
- package/dist/storage/domains/memory/utils.d.ts.map +1 -1
- package/dist/storage/domains/workflows/index.d.ts +19 -6
- package/dist/storage/domains/workflows/index.d.ts.map +1 -1
- package/dist/storage/index.d.ts.map +1 -1
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { MastraVector, validateTopK, validateUpsertInput } from "@mastra/core/ve
|
|
|
2
2
|
import oracledb from "oracledb";
|
|
3
3
|
import { ErrorCategory, ErrorDomain, MastraError } from "@mastra/core/error";
|
|
4
4
|
import { createHash } from "crypto";
|
|
5
|
-
import { AgentsStorage, MCPClientsStorage, MastraCompositeStore, MemoryStorage, ObservabilityStorage, ScorerDefinitionsStorage, ScoresStorage, TABLE_AGENTS, TABLE_AGENT_VERSIONS, TABLE_MCP_CLIENTS, TABLE_MCP_CLIENT_VERSIONS, TABLE_MESSAGES, TABLE_OBSERVATIONAL_MEMORY, TABLE_RESOURCES, TABLE_SCHEMAS, TABLE_SCORERS, TABLE_SCORER_DEFINITIONS, TABLE_SCORER_DEFINITION_VERSIONS, TABLE_SPANS, TABLE_THREADS, TABLE_WORKFLOW_SNAPSHOT, TraceStatus, WorkflowsStorage, calculatePagination, createStorageErrorId, createVectorErrorId, executeRetentionPrune, listLogsArgsSchema, listScoresArgsSchema, listTracesArgsSchema, matchesExpectedWorkflowStatus, normalizePerPage, resolveRetentionTargets, retentionCutoffMs, storageMessageMatchesMetadataFilter, toTraceSpans, transformScoreRow, validateStorageMetadataFilter } from "@mastra/core/storage";
|
|
5
|
+
import { AgentsStorage, MCPClientsStorage, MastraCompositeStore, MemoryStorage, ObservabilityStorage, RUN_FENCING_TABLE_SCHEMAS, RunFenceConflictError, ScorerDefinitionsStorage, ScoresStorage, TABLE_AGENTS, TABLE_AGENT_VERSIONS, TABLE_MCP_CLIENTS, TABLE_MCP_CLIENT_VERSIONS, TABLE_MEMORY_RUN_FENCES, TABLE_MESSAGES, TABLE_OBSERVATIONAL_MEMORY, TABLE_RESOURCES, TABLE_SCHEMAS, TABLE_SCORERS, TABLE_SCORER_DEFINITIONS, TABLE_SCORER_DEFINITION_VERSIONS, TABLE_SPANS, TABLE_THREADS, TABLE_WORKFLOW_RUN_OWNERS, TABLE_WORKFLOW_SNAPSHOT, TraceStatus, WorkflowsStorage, calculatePagination, createStorageErrorId, createVectorErrorId, executeRetentionPrune, isRunFenceConflictError, listLogsArgsSchema, listScoresArgsSchema, listTracesArgsSchema, matchesExpectedWorkflowStatus, matchesRunFence, normalizePerPage, resolveRetentionTargets, resolveRunFence, retentionCutoffMs, storageMessageMatchesMetadataFilter, toTraceSpans, transformScoreRow, validateStorageMetadataFilter } from "@mastra/core/storage";
|
|
6
6
|
import { MessageList } from "@mastra/core/agent";
|
|
7
7
|
import { saveScorePayloadSchema } from "@mastra/core/evals";
|
|
8
8
|
//#region src/shared/connection.ts
|
|
@@ -3345,6 +3345,124 @@ 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
|
|
3413
|
+
//#region src/storage/db/run-fencing.ts
|
|
3414
|
+
const SINCE_EPOCH = `(SYS_EXTRACT_UTC(SYSTIMESTAMP) - TIMESTAMP '1970-01-01 00:00:00')`;
|
|
3415
|
+
/**
|
|
3416
|
+
* Epoch milliseconds on the database clock. Oracle evaluates `SYSTIMESTAMP`
|
|
3417
|
+
* once per statement, so every reference here reads the same instant, and a
|
|
3418
|
+
* statement that waits on a lock compares against the time it started. That
|
|
3419
|
+
* error only makes an existing lease look longer and a new lease shorter.
|
|
3420
|
+
*/
|
|
3421
|
+
const DB_NOW_MS = `(EXTRACT(DAY FROM ${SINCE_EPOCH}) * 86400000 + EXTRACT(HOUR FROM ${SINCE_EPOCH}) * 3600000 + EXTRACT(MINUTE FROM ${SINCE_EPOCH}) * 60000 + FLOOR(EXTRACT(SECOND FROM ${SINCE_EPOCH}) * 1000))`;
|
|
3422
|
+
const ORA_UNIQUE_CONSTRAINT = -1;
|
|
3423
|
+
const ORA_DEADLOCK = -60;
|
|
3424
|
+
const MAX_CLAIM_ATTEMPTS = 5;
|
|
3425
|
+
/**
|
|
3426
|
+
* The check half of a fenced write; call it first inside the write's
|
|
3427
|
+
* transaction. Oracle has no shared row lock, so this takes the claim row
|
|
3428
|
+
* `FOR UPDATE`: a claim that updates the row waits for the write to commit,
|
|
3429
|
+
* and a write queued behind a claim re-reads the row once the claim commits
|
|
3430
|
+
* and sees the new generation. Writes from one run serialize on its row,
|
|
3431
|
+
* which they mostly do anyway.
|
|
3432
|
+
*/
|
|
3433
|
+
async function assertRunFence(client, claimsTable, fence, operation) {
|
|
3434
|
+
const row = await client.oneOrNone(`SELECT generation AS "generation", "ownerId" FROM ${claimsTable} WHERE "runId" = :runId FOR UPDATE`, { runId: fence.runId });
|
|
3435
|
+
if (!matchesRunFence(row ? {
|
|
3436
|
+
generation: Number(row.generation),
|
|
3437
|
+
ownerId: String(row.ownerId)
|
|
3438
|
+
} : null, fence)) throw new RunFenceConflictError(fence, operation);
|
|
3439
|
+
}
|
|
3440
|
+
/**
|
|
3441
|
+
* Runs `write` in a transaction, behind the fence check when it carries a
|
|
3442
|
+
* fence.
|
|
3443
|
+
*/
|
|
3444
|
+
function withRunFence(db, claimsTable, fence, operation, write) {
|
|
3445
|
+
return db.tx(async (client) => {
|
|
3446
|
+
if (fence) await assertRunFence(client, claimsTable, fence, operation);
|
|
3447
|
+
return write(client);
|
|
3448
|
+
});
|
|
3449
|
+
}
|
|
3450
|
+
/**
|
|
3451
|
+
* Runs a transaction that creates or updates a run's claim row, retrying when
|
|
3452
|
+
* two first claims race to insert it. `FOR UPDATE` locks nothing while the row
|
|
3453
|
+
* is missing, so racing first claims both insert; the second waits on the
|
|
3454
|
+
* primary key and fails once the first commits. On retry the row exists and
|
|
3455
|
+
* the claim serializes behind its row lock. A deadlock is retried too.
|
|
3456
|
+
*/
|
|
3457
|
+
async function claimTransaction(db, fn) {
|
|
3458
|
+
for (let attempt = 1;; attempt++) try {
|
|
3459
|
+
return await db.tx((client) => fn(client));
|
|
3460
|
+
} catch (error) {
|
|
3461
|
+
const retryable = isOracleErrorCode(error, [ORA_UNIQUE_CONSTRAINT, ORA_DEADLOCK]);
|
|
3462
|
+
if (attempt >= MAX_CLAIM_ATTEMPTS || !retryable) throw error;
|
|
3463
|
+
}
|
|
3464
|
+
}
|
|
3465
|
+
//#endregion
|
|
3348
3466
|
//#region src/storage/domains/memory/utils.ts
|
|
3349
3467
|
const STORE_NAME$5 = "ORACLEDB";
|
|
3350
3468
|
const EMPTY_STRING_SENTINEL = "__MASTRA_ORACLE_EMPTY_STRING__";
|
|
@@ -3352,6 +3470,14 @@ const STRING_SENTINEL_ESCAPE_PREFIX = "__MASTRA_ORACLE_ESCAPED__";
|
|
|
3352
3470
|
function table(ctx, tableName) {
|
|
3353
3471
|
return qualifyName(tableName, ctx.schemaName);
|
|
3354
3472
|
}
|
|
3473
|
+
/**
|
|
3474
|
+
* The fence check for a memory write; call it first inside the write's
|
|
3475
|
+
* transaction. `fence` is the one `MemoryOracle` already resolved against the
|
|
3476
|
+
* run fence scope, so an unfenced write skips the check.
|
|
3477
|
+
*/
|
|
3478
|
+
async function assertMemoryFence(ctx, client, fence, operation) {
|
|
3479
|
+
if (fence) await assertRunFence(client, table(ctx, TABLE_MEMORY_RUN_FENCES), fence, operation);
|
|
3480
|
+
}
|
|
3355
3481
|
function storageError$1(operation, reason, details, cause, category = ErrorCategory.THIRD_PARTY) {
|
|
3356
3482
|
return createOracleStorageError({
|
|
3357
3483
|
storeName: STORE_NAME$5,
|
|
@@ -3382,6 +3508,17 @@ function chunkValues(values, size = 900) {
|
|
|
3382
3508
|
for (let index = 0; index < values.length; index += size) chunks.push(values.slice(index, index + size));
|
|
3383
3509
|
return chunks;
|
|
3384
3510
|
}
|
|
3511
|
+
/**
|
|
3512
|
+
* Bind an instant as TIMESTAMP WITH TIME ZONE. A bare `Date` binds as TIMESTAMP WITH LOCAL
|
|
3513
|
+
* TIME ZONE, which the server converts with the session's fixed offset (captured at connect
|
|
3514
|
+
* time); an instant on the other side of a DST change is then stored shifted by an hour.
|
|
3515
|
+
*/
|
|
3516
|
+
function timestampBind(value) {
|
|
3517
|
+
return value === void 0 || value === null ? null : {
|
|
3518
|
+
type: oracledb.DB_TYPE_TIMESTAMP_TZ,
|
|
3519
|
+
val: toDate(value)
|
|
3520
|
+
};
|
|
3521
|
+
}
|
|
3385
3522
|
function boolToNumber(value) {
|
|
3386
3523
|
return value ? 1 : 0;
|
|
3387
3524
|
}
|
|
@@ -3530,12 +3667,48 @@ const OM_LAST_BUFFERED_AT_TOKENS = "\"lastBufferedAtTokens\"";
|
|
|
3530
3667
|
const OM_LAST_BUFFERED_AT_TIME = "\"lastBufferedAtTime\"";
|
|
3531
3668
|
const OM_CREATED_AT = "\"createdAt\"";
|
|
3532
3669
|
const OM_UPDATED_AT = "\"updatedAt\"";
|
|
3670
|
+
const OM_SUPERSEDED_BY = "\"supersededBy\"";
|
|
3533
3671
|
async function initMemorySchema(ctx) {
|
|
3534
3672
|
await ctx.db.withConnection(async (connection) => {
|
|
3535
3673
|
await createTables(ctx, connection);
|
|
3536
3674
|
await createIndexes(ctx, connection);
|
|
3675
|
+
await backfillObservationalMemorySupersededBy(ctx, connection);
|
|
3537
3676
|
});
|
|
3538
3677
|
}
|
|
3678
|
+
/**
|
|
3679
|
+
* Retire every live observational memory row that sorts after its key's canonical head
|
|
3680
|
+
* (`generationCount DESC, createdAt ASC, id ASC`), marking it superseded by the head. Rows
|
|
3681
|
+
* retired by older adapter versions (which never set `supersededBy`) become frozen. Idempotent.
|
|
3682
|
+
* One statement: the head's ordering key is part of the condition, so a row created by a
|
|
3683
|
+
* concurrent rollover (higher generation) never matches, and a row locked by one is
|
|
3684
|
+
* re-evaluated after it commits.
|
|
3685
|
+
*/
|
|
3686
|
+
async function backfillObservationalMemorySupersededBy(ctx, connection) {
|
|
3687
|
+
const om = table(ctx, TABLE_OBSERVATIONAL_MEMORY);
|
|
3688
|
+
const heads = `
|
|
3689
|
+
SELECT ${OM_LOOKUP_KEY} AS head_key, id AS head_id, ${OM_GENERATION_COUNT} AS head_gen, ${OM_CREATED_AT} AS head_created
|
|
3690
|
+
FROM (
|
|
3691
|
+
SELECT ${OM_LOOKUP_KEY}, id, ${OM_GENERATION_COUNT}, ${OM_CREATED_AT},
|
|
3692
|
+
ROW_NUMBER() OVER (PARTITION BY ${OM_LOOKUP_KEY} ORDER BY ${OM_GENERATION_COUNT} DESC, ${OM_CREATED_AT} ASC, id ASC) AS rn
|
|
3693
|
+
FROM ${om}
|
|
3694
|
+
WHERE ${OM_LOOKUP_KEY} IN (
|
|
3695
|
+
SELECT ${OM_LOOKUP_KEY} FROM ${om} WHERE ${OM_SUPERSEDED_BY} IS NULL GROUP BY ${OM_LOOKUP_KEY} HAVING COUNT(*) > 1
|
|
3696
|
+
)
|
|
3697
|
+
)
|
|
3698
|
+
WHERE rn = 1`;
|
|
3699
|
+
await connection.execute(`UPDATE ${om} r
|
|
3700
|
+
SET r.${OM_SUPERSEDED_BY} = (SELECT h.head_id FROM (${heads}) h WHERE h.head_key = r.${OM_LOOKUP_KEY})
|
|
3701
|
+
WHERE r.${OM_SUPERSEDED_BY} IS NULL
|
|
3702
|
+
AND EXISTS (
|
|
3703
|
+
SELECT 1 FROM (${heads}) h
|
|
3704
|
+
WHERE h.head_key = r.${OM_LOOKUP_KEY}
|
|
3705
|
+
AND (
|
|
3706
|
+
r.${OM_GENERATION_COUNT} < h.head_gen
|
|
3707
|
+
OR (r.${OM_GENERATION_COUNT} = h.head_gen AND r.${OM_CREATED_AT} > h.head_created)
|
|
3708
|
+
OR (r.${OM_GENERATION_COUNT} = h.head_gen AND r.${OM_CREATED_AT} = h.head_created AND r.id > h.head_id)
|
|
3709
|
+
)
|
|
3710
|
+
)`, {}, { autoCommit: true });
|
|
3711
|
+
}
|
|
3539
3712
|
async function clearAllMemoryTables(ctx) {
|
|
3540
3713
|
await ctx.db.tx(async (client) => {
|
|
3541
3714
|
await client.none(`DELETE FROM ${table(ctx, TABLE_OBSERVATIONAL_MEMORY)}`);
|
|
@@ -3609,7 +3782,8 @@ async function createTables(ctx, connection) {
|
|
|
3609
3782
|
${OM_LAST_BUFFERED_AT_TIME} TIMESTAMP WITH TIME ZONE,
|
|
3610
3783
|
metadata JSON,
|
|
3611
3784
|
${OM_CREATED_AT} TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
3612
|
-
${OM_UPDATED_AT} TIMESTAMP WITH TIME ZONE NOT NULL
|
|
3785
|
+
${OM_UPDATED_AT} TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
3786
|
+
${OM_SUPERSEDED_BY} VARCHAR2(512)
|
|
3613
3787
|
)`, [-955]);
|
|
3614
3788
|
await ensureObservationalMemoryColumns(ctx, connection);
|
|
3615
3789
|
await relaxObservationalMemoryNullability(ctx, connection);
|
|
@@ -3747,6 +3921,10 @@ async function ensureObservationalMemoryColumns(ctx, connection) {
|
|
|
3747
3921
|
{
|
|
3748
3922
|
name: OM_UPDATED_AT,
|
|
3749
3923
|
type: "TIMESTAMP WITH TIME ZONE"
|
|
3924
|
+
},
|
|
3925
|
+
{
|
|
3926
|
+
name: OM_SUPERSEDED_BY,
|
|
3927
|
+
type: "VARCHAR2(512)"
|
|
3750
3928
|
}
|
|
3751
3929
|
];
|
|
3752
3930
|
for (const column of columns) await executeDdl(connection, `ALTER TABLE ${table(ctx, TABLE_OBSERVATIONAL_MEMORY)} ADD (${column.name} ${column.type})`, [-1430]);
|
|
@@ -3857,7 +4035,7 @@ async function listMessagesByResourceId(ctx, args) {
|
|
|
3857
4035
|
orderBy
|
|
3858
4036
|
});
|
|
3859
4037
|
}
|
|
3860
|
-
async function saveMessages(ctx, { messages }) {
|
|
4038
|
+
async function saveMessages(ctx, { messages, fence }) {
|
|
3861
4039
|
if (messages.length === 0) return { messages: [] };
|
|
3862
4040
|
const threadIds = /* @__PURE__ */ new Set();
|
|
3863
4041
|
try {
|
|
@@ -3867,6 +4045,7 @@ async function saveMessages(ctx, { messages }) {
|
|
|
3867
4045
|
}
|
|
3868
4046
|
for (const threadId of threadIds) if (!await ctx.getThreadById({ threadId })) throw storageError$1("SAVE_MESSAGES", "FAILED", { threadId }, /* @__PURE__ */ new Error(`Thread ${threadId} not found`), ErrorCategory.USER);
|
|
3869
4047
|
await ctx.db.tx(async (client) => {
|
|
4048
|
+
await assertMemoryFence(ctx, client, fence, "saveMessages");
|
|
3870
4049
|
await insertMessageBatch(ctx, client, messages);
|
|
3871
4050
|
});
|
|
3872
4051
|
return { messages: new MessageList().add(messages, "memory").get.all.db() };
|
|
@@ -3944,7 +4123,7 @@ async function insertMessageBatch(ctx, client, messages) {
|
|
|
3944
4123
|
threadId: messageThreadId
|
|
3945
4124
|
})));
|
|
3946
4125
|
}
|
|
3947
|
-
async function updateMessages(ctx, { messages }) {
|
|
4126
|
+
async function updateMessages(ctx, { messages, fence }) {
|
|
3948
4127
|
if (messages.length === 0) return [];
|
|
3949
4128
|
const messageIds = messages.map((message) => message.id);
|
|
3950
4129
|
const existingMessages = (await ctx.listMessagesById({ messageIds })).messages;
|
|
@@ -3953,6 +4132,7 @@ async function updateMessages(ctx, { messages }) {
|
|
|
3953
4132
|
const threadIdsToUpdate = /* @__PURE__ */ new Set();
|
|
3954
4133
|
try {
|
|
3955
4134
|
await ctx.db.tx(async (client) => {
|
|
4135
|
+
await assertMemoryFence(ctx, client, fence, "updateMessages");
|
|
3956
4136
|
const semanticRecallMessageIds = [];
|
|
3957
4137
|
for (const updatePayload of messages) {
|
|
3958
4138
|
const existing = existingById.get(updatePayload.id);
|
|
@@ -3999,13 +4179,15 @@ async function updateMessages(ctx, { messages }) {
|
|
|
3999
4179
|
});
|
|
4000
4180
|
return (await ctx.listMessagesById({ messageIds })).messages;
|
|
4001
4181
|
} catch (error) {
|
|
4182
|
+
if (error instanceof MastraError) throw error;
|
|
4002
4183
|
throw storageError$1("UPDATE_MESSAGES", "FAILED", { messageIds: messageIds.join(",") }, error);
|
|
4003
4184
|
}
|
|
4004
4185
|
}
|
|
4005
|
-
async function deleteMessages(ctx, messageIds) {
|
|
4186
|
+
async function deleteMessages(ctx, messageIds, fence) {
|
|
4006
4187
|
if (!messageIds.length) return;
|
|
4007
4188
|
try {
|
|
4008
4189
|
await ctx.db.tx(async (client) => {
|
|
4190
|
+
await assertMemoryFence(ctx, client, fence, "deleteMessages");
|
|
4009
4191
|
const threadIds = /* @__PURE__ */ new Set();
|
|
4010
4192
|
for (const [chunkIndex, chunk] of chunkValues(messageIds).entries()) {
|
|
4011
4193
|
const { sql, binds } = inClause(`messageId${chunkIndex}`, chunk);
|
|
@@ -4021,6 +4203,7 @@ async function deleteMessages(ctx, messageIds) {
|
|
|
4021
4203
|
})));
|
|
4022
4204
|
});
|
|
4023
4205
|
} catch (error) {
|
|
4206
|
+
if (error instanceof MastraError) throw error;
|
|
4024
4207
|
throw storageError$1("DELETE_MESSAGES", "FAILED", { messageIds: messageIds.join(",") }, error);
|
|
4025
4208
|
}
|
|
4026
4209
|
}
|
|
@@ -4262,14 +4445,15 @@ function mergeMessageContent(existingContent, updateContent) {
|
|
|
4262
4445
|
}
|
|
4263
4446
|
//#endregion
|
|
4264
4447
|
//#region src/storage/domains/memory/observational.ts
|
|
4448
|
+
/** Hops a write aimed at a retired record may follow to reach the head before giving up. */
|
|
4449
|
+
const OM_MAX_HEAD_HOPS = 3;
|
|
4450
|
+
/** Canonical head order for a lookup key. */
|
|
4451
|
+
const OM_HEAD_ORDER = `${OM_GENERATION_COUNT} DESC, ${OM_CREATED_AT} ASC, id ASC`;
|
|
4265
4452
|
async function getObservationalMemory(ctx, threadId, resourceId) {
|
|
4266
4453
|
try {
|
|
4267
4454
|
const lookupKey = getOMKey(threadId, resourceId);
|
|
4268
4455
|
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];
|
|
4456
|
+
const row = await getHeadOMRow(ctx, connection, lookupKey);
|
|
4273
4457
|
return row ? parseOMRow(row) : null;
|
|
4274
4458
|
});
|
|
4275
4459
|
} catch (error) {
|
|
@@ -4301,11 +4485,11 @@ async function getObservationalMemoryHistory(ctx, threadId, resourceId, limit =
|
|
|
4301
4485
|
}
|
|
4302
4486
|
if (options?.from) {
|
|
4303
4487
|
conditions.push(`${OM_CREATED_AT} >= :fromDate`);
|
|
4304
|
-
binds.fromDate = options.from;
|
|
4488
|
+
binds.fromDate = timestampBind(options.from);
|
|
4305
4489
|
}
|
|
4306
4490
|
if (options?.to) {
|
|
4307
4491
|
conditions.push(`${OM_CREATED_AT} <= :toDate`);
|
|
4308
|
-
binds.toDate = options.to;
|
|
4492
|
+
binds.toDate = timestampBind(options.to);
|
|
4309
4493
|
}
|
|
4310
4494
|
if (options?.groupId !== void 0) {
|
|
4311
4495
|
conditions.push(`(DBMS_LOB.INSTR(${OM_ACTIVE_OBSERVATIONS}, :groupPrefix) > 0 OR EXISTS (
|
|
@@ -4344,6 +4528,7 @@ async function getObservationalMemoryHistory(ctx, threadId, resourceId, limit =
|
|
|
4344
4528
|
}
|
|
4345
4529
|
async function initializeObservationalMemory(ctx, input) {
|
|
4346
4530
|
const now = /* @__PURE__ */ new Date();
|
|
4531
|
+
const lookupKey = getOMKey(input.threadId, input.resourceId);
|
|
4347
4532
|
const record = {
|
|
4348
4533
|
id: globalThis.crypto.randomUUID(),
|
|
4349
4534
|
scope: input.scope,
|
|
@@ -4365,13 +4550,22 @@ async function initializeObservationalMemory(ctx, input) {
|
|
|
4365
4550
|
lastBufferedAtTokens: 0,
|
|
4366
4551
|
lastBufferedAtTime: null,
|
|
4367
4552
|
config: input.config,
|
|
4368
|
-
observedTimezone: input.observedTimezone
|
|
4553
|
+
observedTimezone: input.observedTimezone,
|
|
4554
|
+
supersededBy: null
|
|
4369
4555
|
};
|
|
4370
4556
|
try {
|
|
4371
|
-
await ctx.
|
|
4557
|
+
const existing = await getObservationalMemory(ctx, input.threadId, input.resourceId);
|
|
4558
|
+
if (existing) return existing;
|
|
4559
|
+
if (await ctx.db.tx(async (_client, connection) => {
|
|
4560
|
+
const omTable = table(ctx, TABLE_OBSERVATIONAL_MEMORY);
|
|
4561
|
+
await connection.execute(`LOCK TABLE ${omTable} IN SHARE ROW EXCLUSIVE MODE`);
|
|
4562
|
+
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
4563
|
await insertOMRecord(ctx, connection, record);
|
|
4373
|
-
|
|
4374
|
-
return record;
|
|
4564
|
+
return true;
|
|
4565
|
+
})) return record;
|
|
4566
|
+
const head = await getObservationalMemory(ctx, input.threadId, input.resourceId);
|
|
4567
|
+
if (!head) throw new Error(`Observational memory record not found after initialization: ${lookupKey}`);
|
|
4568
|
+
return head;
|
|
4375
4569
|
} catch (error) {
|
|
4376
4570
|
throw storageError$1("INITIALIZE_OBSERVATIONAL_MEMORY", "FAILED", {
|
|
4377
4571
|
threadId: input.threadId ?? "",
|
|
@@ -4391,9 +4585,20 @@ async function insertObservationalMemoryRecord(ctx, record) {
|
|
|
4391
4585
|
}, error);
|
|
4392
4586
|
}
|
|
4393
4587
|
}
|
|
4394
|
-
async function updateActiveObservations(ctx, input) {
|
|
4588
|
+
async function updateActiveObservations(ctx, input, fence) {
|
|
4395
4589
|
try {
|
|
4396
|
-
await ctx.db.tx(async (
|
|
4590
|
+
return await ctx.db.tx(async (client, connection) => {
|
|
4591
|
+
await assertMemoryFence(ctx, client, fence, "commitActiveObservations");
|
|
4592
|
+
const row = await lockOMRow(ctx, connection, input.id, "UPDATE_ACTIVE_OBSERVATIONS");
|
|
4593
|
+
if (row.supersededBy) return {
|
|
4594
|
+
applied: false,
|
|
4595
|
+
reason: "retired"
|
|
4596
|
+
};
|
|
4597
|
+
if (input.expectedActiveObservations !== void 0 && input.expectedActiveObservations !== stringOrEmpty(row.activeObservations)) return {
|
|
4598
|
+
applied: false,
|
|
4599
|
+
reason: "conflict"
|
|
4600
|
+
};
|
|
4601
|
+
const lastObservedAt = maxObservationCursor(row.lastObservedAt, input.lastObservedAt);
|
|
4397
4602
|
assertRowsAffected((await connection.execute(`
|
|
4398
4603
|
UPDATE ${table(ctx, TABLE_OBSERVATIONAL_MEMORY)}
|
|
4399
4604
|
SET ${OM_ACTIVE_OBSERVATIONS} = :activeObservations,
|
|
@@ -4407,52 +4612,88 @@ async function updateActiveObservations(ctx, input) {
|
|
|
4407
4612
|
WHERE id = :id`, {
|
|
4408
4613
|
id: input.id,
|
|
4409
4614
|
activeObservations: nullableClobBind(input.observations),
|
|
4410
|
-
lastObservedAt:
|
|
4615
|
+
lastObservedAt: timestampBind(lastObservedAt),
|
|
4411
4616
|
tokenCount: Math.round(input.tokenCount),
|
|
4412
4617
|
observedMessageIds: nullableJsonBind(input.observedMessageIds),
|
|
4413
4618
|
observedTimezone: input.observedTimezone ?? null,
|
|
4414
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
4619
|
+
updatedAt: timestampBind(/* @__PURE__ */ new Date())
|
|
4415
4620
|
})).rowsAffected, "UPDATE_ACTIVE_OBSERVATIONS", input.id);
|
|
4621
|
+
return { applied: true };
|
|
4416
4622
|
});
|
|
4417
4623
|
} catch (error) {
|
|
4418
4624
|
if (error instanceof MastraError) throw error;
|
|
4419
4625
|
throw storageError$1("UPDATE_ACTIVE_OBSERVATIONS", "FAILED", { id: input.id }, error);
|
|
4420
4626
|
}
|
|
4421
4627
|
}
|
|
4422
|
-
async function createReflectionGeneration(ctx, input) {
|
|
4628
|
+
async function createReflectionGeneration(ctx, input, fence) {
|
|
4629
|
+
const { currentRecord } = input;
|
|
4630
|
+
try {
|
|
4631
|
+
return await ctx.db.tx(async (client, connection) => {
|
|
4632
|
+
await assertMemoryFence(ctx, client, fence, "createReflectionGeneration");
|
|
4633
|
+
const row = await findOMRowForUpdate(ctx, connection, currentRecord.id);
|
|
4634
|
+
if (!row) return currentRecord;
|
|
4635
|
+
if (row.supersededBy) return parseOMRow(await resolveLiveOMRow(ctx, connection, row));
|
|
4636
|
+
const stored = parseOMRow(row);
|
|
4637
|
+
const plan = planReflectionGenerationText({
|
|
4638
|
+
storedObservations: stored.activeObservations,
|
|
4639
|
+
storedObservationTokenCount: stored.observationTokenCount,
|
|
4640
|
+
snapshotObservations: currentRecord.activeObservations,
|
|
4641
|
+
snapshotObservationTokenCount: currentRecord.observationTokenCount,
|
|
4642
|
+
reflection: input.reflection,
|
|
4643
|
+
tokenCount: input.tokenCount
|
|
4644
|
+
});
|
|
4645
|
+
if (!plan) return stored;
|
|
4646
|
+
return rollOverOMRow(ctx, connection, stored, plan.observations, plan.tokenCount, input.newRecordId);
|
|
4647
|
+
});
|
|
4648
|
+
} catch (error) {
|
|
4649
|
+
if (error instanceof MastraError) throw error;
|
|
4650
|
+
throw storageError$1("CREATE_REFLECTION_GENERATION", "FAILED", { id: currentRecord.id }, error);
|
|
4651
|
+
}
|
|
4652
|
+
}
|
|
4653
|
+
/**
|
|
4654
|
+
* Create the next generation from the stored (live, row-locked) record and retire it in the
|
|
4655
|
+
* same transaction. Buffered chunks move to the new generation; the cursor, buffering markers,
|
|
4656
|
+
* flags, and counters carry over; buffered reflection state does not.
|
|
4657
|
+
*/
|
|
4658
|
+
async function rollOverOMRow(ctx, connection, stored, observations, tokenCount, newRecordId) {
|
|
4423
4659
|
const now = /* @__PURE__ */ new Date();
|
|
4424
4660
|
const record = {
|
|
4425
|
-
id: globalThis.crypto.randomUUID(),
|
|
4426
|
-
scope:
|
|
4427
|
-
threadId:
|
|
4428
|
-
resourceId:
|
|
4661
|
+
id: newRecordId ?? globalThis.crypto.randomUUID(),
|
|
4662
|
+
scope: stored.scope,
|
|
4663
|
+
threadId: stored.threadId,
|
|
4664
|
+
resourceId: stored.resourceId,
|
|
4429
4665
|
createdAt: now,
|
|
4430
4666
|
updatedAt: now,
|
|
4431
|
-
lastObservedAt:
|
|
4667
|
+
lastObservedAt: stored.lastObservedAt,
|
|
4432
4668
|
originType: "reflection",
|
|
4433
|
-
generationCount:
|
|
4434
|
-
activeObservations:
|
|
4435
|
-
totalTokensObserved:
|
|
4436
|
-
observationTokenCount: Math.round(
|
|
4437
|
-
pendingMessageTokens:
|
|
4669
|
+
generationCount: stored.generationCount + 1,
|
|
4670
|
+
activeObservations: observations,
|
|
4671
|
+
totalTokensObserved: stored.totalTokensObserved,
|
|
4672
|
+
observationTokenCount: Math.round(tokenCount),
|
|
4673
|
+
pendingMessageTokens: stored.pendingMessageTokens,
|
|
4438
4674
|
isReflecting: false,
|
|
4439
4675
|
isObserving: false,
|
|
4440
|
-
isBufferingObservation:
|
|
4676
|
+
isBufferingObservation: stored.isBufferingObservation,
|
|
4441
4677
|
isBufferingReflection: false,
|
|
4442
|
-
lastBufferedAtTokens:
|
|
4443
|
-
lastBufferedAtTime: null,
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4678
|
+
lastBufferedAtTokens: stored.lastBufferedAtTokens,
|
|
4679
|
+
lastBufferedAtTime: stored.lastBufferedAtTime ?? null,
|
|
4680
|
+
bufferedObservationChunks: stored.bufferedObservationChunks?.length ? stored.bufferedObservationChunks : void 0,
|
|
4681
|
+
config: stored.config,
|
|
4682
|
+
metadata: stored.metadata,
|
|
4683
|
+
observedTimezone: stored.observedTimezone,
|
|
4684
|
+
supersededBy: null
|
|
4447
4685
|
};
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4686
|
+
await insertOMRecord(ctx, connection, record, now);
|
|
4687
|
+
if ((await connection.execute(`UPDATE ${table(ctx, TABLE_OBSERVATIONAL_MEMORY)}
|
|
4688
|
+
SET "supersededBy" = :newId,
|
|
4689
|
+
"bufferedObservationChunks" = NULL,
|
|
4690
|
+
"updatedAt" = :updatedAt
|
|
4691
|
+
WHERE id = :id AND "supersededBy" IS NULL`, {
|
|
4692
|
+
newId: record.id,
|
|
4693
|
+
updatedAt: timestampBind(now),
|
|
4694
|
+
id: stored.id
|
|
4695
|
+
})).rowsAffected !== 1) throw storageError$1("CREATE_REFLECTION_GENERATION", "RETIRE_FAILED", { id: stored.id }, /* @__PURE__ */ new Error(`Failed to retire observational memory record ${stored.id}`));
|
|
4696
|
+
return record;
|
|
4456
4697
|
}
|
|
4457
4698
|
async function setReflectingFlag(ctx, id, isReflecting) {
|
|
4458
4699
|
await updateOMFlag(ctx, id, OM_IS_REFLECTING, isReflecting, "SET_REFLECTING_FLAG");
|
|
@@ -4467,9 +4708,10 @@ async function setBufferingObservationFlag(ctx, id, isBuffering, lastBufferedAtT
|
|
|
4467
4708
|
const binds = {
|
|
4468
4709
|
id,
|
|
4469
4710
|
isBuffering: boolToNumber(isBuffering),
|
|
4470
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
4711
|
+
updatedAt: timestampBind(/* @__PURE__ */ new Date())
|
|
4471
4712
|
};
|
|
4472
4713
|
if (lastBufferedAtTokens !== void 0) binds.lastBufferedAtTokens = Math.round(lastBufferedAtTokens);
|
|
4714
|
+
binds.id = (await resolveLiveOMRow(ctx, connection, await lockOMRow(ctx, connection, id, "SET_BUFFERING_OBSERVATION_FLAG"))).id;
|
|
4473
4715
|
assertRowsAffected((await connection.execute(`UPDATE ${table(ctx, TABLE_OBSERVATIONAL_MEMORY)}
|
|
4474
4716
|
SET ${OM_IS_BUFFERING_OBSERVATION} = :isBuffering,
|
|
4475
4717
|
${OM_UPDATED_AT} = :updatedAt
|
|
@@ -4500,7 +4742,17 @@ async function clearObservationalMemory(ctx, threadId, resourceId) {
|
|
|
4500
4742
|
}
|
|
4501
4743
|
async function setPendingMessageTokens(ctx, id, tokenCount) {
|
|
4502
4744
|
try {
|
|
4503
|
-
await
|
|
4745
|
+
await ctx.db.tx(async (_client, connection) => {
|
|
4746
|
+
const target = await resolveLiveOMRow(ctx, connection, await lockOMRow(ctx, connection, id, "SET_PENDING_MESSAGE_TOKENS"));
|
|
4747
|
+
assertRowsAffected((await connection.execute(`UPDATE ${table(ctx, TABLE_OBSERVATIONAL_MEMORY)}
|
|
4748
|
+
SET ${OM_PENDING_MESSAGE_TOKENS} = :tokenCount,
|
|
4749
|
+
${OM_UPDATED_AT} = :updatedAt
|
|
4750
|
+
WHERE id = :id`, {
|
|
4751
|
+
tokenCount: Math.round(tokenCount),
|
|
4752
|
+
updatedAt: timestampBind(/* @__PURE__ */ new Date()),
|
|
4753
|
+
id: target.id
|
|
4754
|
+
})).rowsAffected, "SET_PENDING_MESSAGE_TOKENS", id);
|
|
4755
|
+
});
|
|
4504
4756
|
} catch (error) {
|
|
4505
4757
|
if (error instanceof MastraError) throw error;
|
|
4506
4758
|
throw storageError$1("SET_PENDING_MESSAGE_TOKENS", "FAILED", {
|
|
@@ -4522,7 +4774,7 @@ async function updateObservationalMemoryConfig(ctx, input) {
|
|
|
4522
4774
|
WHERE id = :id`, {
|
|
4523
4775
|
id: input.id,
|
|
4524
4776
|
config: jsonBind(merged),
|
|
4525
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
4777
|
+
updatedAt: timestampBind(/* @__PURE__ */ new Date())
|
|
4526
4778
|
})).rowsAffected, "UPDATE_OM_CONFIG", input.id);
|
|
4527
4779
|
});
|
|
4528
4780
|
} catch (error) {
|
|
@@ -4568,6 +4820,7 @@ async function insertOMRecord(ctx, connection, record, timestamp = record.create
|
|
|
4568
4820
|
${OM_LAST_BUFFERED_AT_TOKENS},
|
|
4569
4821
|
${OM_LAST_BUFFERED_AT_TIME},
|
|
4570
4822
|
metadata,
|
|
4823
|
+
${OM_SUPERSEDED_BY},
|
|
4571
4824
|
${OM_CREATED_AT},
|
|
4572
4825
|
${OM_UPDATED_AT}
|
|
4573
4826
|
) VALUES (
|
|
@@ -4603,6 +4856,7 @@ async function insertOMRecord(ctx, connection, record, timestamp = record.create
|
|
|
4603
4856
|
:lastBufferedAtTokens,
|
|
4604
4857
|
:lastBufferedAtTime,
|
|
4605
4858
|
:metadata,
|
|
4859
|
+
:supersededBy,
|
|
4606
4860
|
:createdAt,
|
|
4607
4861
|
:updatedAt
|
|
4608
4862
|
)`, {
|
|
@@ -4616,8 +4870,8 @@ async function insertOMRecord(ctx, connection, record, timestamp = record.create
|
|
|
4616
4870
|
originType: record.originType ?? "initial",
|
|
4617
4871
|
config: jsonBind(record.config ?? {}),
|
|
4618
4872
|
generationCount: record.generationCount ?? 0,
|
|
4619
|
-
lastObservedAt: record.lastObservedAt
|
|
4620
|
-
lastReflectionAt: record.originType === "reflection" ? timestamp : null,
|
|
4873
|
+
lastObservedAt: timestampBind(record.lastObservedAt),
|
|
4874
|
+
lastReflectionAt: record.originType === "reflection" ? timestampBind(timestamp) : null,
|
|
4621
4875
|
pendingMessageTokens: Math.round(record.pendingMessageTokens ?? 0),
|
|
4622
4876
|
totalTokensObserved: Math.round(record.totalTokensObserved ?? 0),
|
|
4623
4877
|
observationTokenCount: Math.round(record.observationTokenCount ?? 0),
|
|
@@ -4636,10 +4890,11 @@ async function insertOMRecord(ctx, connection, record, timestamp = record.create
|
|
|
4636
4890
|
isBufferingObservation: boolToNumber(record.isBufferingObservation),
|
|
4637
4891
|
isBufferingReflection: boolToNumber(record.isBufferingReflection),
|
|
4638
4892
|
lastBufferedAtTokens: Math.round(record.lastBufferedAtTokens ?? 0),
|
|
4639
|
-
lastBufferedAtTime: record.lastBufferedAtTime
|
|
4893
|
+
lastBufferedAtTime: timestampBind(record.lastBufferedAtTime),
|
|
4640
4894
|
metadata: nullableJsonBind(record.metadata),
|
|
4641
|
-
|
|
4642
|
-
|
|
4895
|
+
supersededBy: record.supersededBy ?? null,
|
|
4896
|
+
createdAt: timestampBind(record.createdAt),
|
|
4897
|
+
updatedAt: timestampBind(record.updatedAt)
|
|
4643
4898
|
});
|
|
4644
4899
|
}
|
|
4645
4900
|
async function updateOMFlag(ctx, id, column, value, operation) {
|
|
@@ -4663,7 +4918,7 @@ async function updateOMColumns(ctx, id, operation, columns) {
|
|
|
4663
4918
|
WHERE id = :id`, {
|
|
4664
4919
|
...binds,
|
|
4665
4920
|
id,
|
|
4666
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
4921
|
+
updatedAt: timestampBind(/* @__PURE__ */ new Date())
|
|
4667
4922
|
})).rowsAffected, operation, id);
|
|
4668
4923
|
});
|
|
4669
4924
|
}
|
|
@@ -4701,6 +4956,7 @@ function omSelect() {
|
|
|
4701
4956
|
${OM_LAST_BUFFERED_AT_TOKENS} AS "lastBufferedAtTokens",
|
|
4702
4957
|
${OM_LAST_BUFFERED_AT_TIME} AS "lastBufferedAtTime",
|
|
4703
4958
|
metadata AS "metadata",
|
|
4959
|
+
${OM_SUPERSEDED_BY} AS "supersededBy",
|
|
4704
4960
|
${OM_CREATED_AT} AS "createdAt",
|
|
4705
4961
|
${OM_UPDATED_AT} AS "updatedAt"`;
|
|
4706
4962
|
}
|
|
@@ -4736,15 +4992,61 @@ function parseOMRow(row) {
|
|
|
4736
4992
|
config: parseJson(row.config),
|
|
4737
4993
|
metadata: parseOptionalJsonObject(row.metadata, { emptyObjectAsUndefined: true }),
|
|
4738
4994
|
observedMessageIds: parseOptionalStringArray(row.observedMessageIds),
|
|
4739
|
-
observedTimezone: row.observedTimezone ? String(row.observedTimezone) : void 0
|
|
4995
|
+
observedTimezone: row.observedTimezone ? String(row.observedTimezone) : void 0,
|
|
4996
|
+
supersededBy: row.supersededBy ? String(row.supersededBy) : null
|
|
4740
4997
|
};
|
|
4741
4998
|
}
|
|
4999
|
+
/** The canonical head row of a lookup key (non-locking read). */
|
|
5000
|
+
async function getHeadOMRow(ctx, connection, lookupKey) {
|
|
5001
|
+
return rows(await connection.execute(`${omSelect()} FROM ${table(ctx, TABLE_OBSERVATIONAL_MEMORY)}
|
|
5002
|
+
WHERE ${OM_LOOKUP_KEY} = :lookupKey
|
|
5003
|
+
ORDER BY ${OM_HEAD_ORDER}
|
|
5004
|
+
FETCH FIRST 1 ROWS ONLY`, asBindParameters({ lookupKey }), executeOptions()))[0];
|
|
5005
|
+
}
|
|
5006
|
+
/** Row-locks one record by primary key for the rest of the transaction; `undefined` when missing. */
|
|
5007
|
+
async function findOMRowForUpdate(ctx, connection, id) {
|
|
5008
|
+
return rows(await connection.execute(`${omSelect()} FROM ${table(ctx, TABLE_OBSERVATIONAL_MEMORY)} WHERE id = :id FOR UPDATE`, { id }, executeOptions()))[0];
|
|
5009
|
+
}
|
|
5010
|
+
/**
|
|
5011
|
+
* Row-locks one record by primary key. Observational memory updates are incremental and
|
|
5012
|
+
* order-sensitive, so mutating paths derive their next state from a locked row.
|
|
5013
|
+
* Throws "record not found" when it is missing.
|
|
5014
|
+
*/
|
|
5015
|
+
async function lockOMRow(ctx, connection, id, operation) {
|
|
5016
|
+
const row = await findOMRowForUpdate(ctx, connection, id);
|
|
5017
|
+
if (!row) {
|
|
5018
|
+
assertRowsAffected(0, operation, id);
|
|
5019
|
+
throw new Error(`Observational memory record not found: ${id}`);
|
|
5020
|
+
}
|
|
5021
|
+
return row;
|
|
5022
|
+
}
|
|
5023
|
+
/**
|
|
5024
|
+
* The live, row-locked record a lifecycle write aimed at `row` lands on: `row` itself while
|
|
5025
|
+
* live, otherwise the head of its lookup key (locked, then re-checked — a rollover may retire
|
|
5026
|
+
* it between the head read and the lock).
|
|
5027
|
+
*/
|
|
5028
|
+
async function resolveLiveOMRow(ctx, connection, row) {
|
|
5029
|
+
let current = row;
|
|
5030
|
+
for (let hop = 0; current.supersededBy; hop++) {
|
|
5031
|
+
const head = hop < OM_MAX_HEAD_HOPS ? await getHeadOMRow(ctx, connection, current.lookupKey) : void 0;
|
|
5032
|
+
const locked = head && head.id !== current.id ? await findOMRowForUpdate(ctx, connection, head.id) : void 0;
|
|
5033
|
+
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`));
|
|
5034
|
+
current = locked;
|
|
5035
|
+
}
|
|
5036
|
+
return current;
|
|
5037
|
+
}
|
|
4742
5038
|
//#endregion
|
|
4743
5039
|
//#region src/storage/domains/memory/observational-buffering.ts
|
|
4744
|
-
async function updateBufferedObservations(ctx, input) {
|
|
5040
|
+
async function updateBufferedObservations(ctx, input, fence) {
|
|
4745
5041
|
try {
|
|
4746
|
-
await ctx.db.tx(async (
|
|
4747
|
-
|
|
5042
|
+
return await ctx.db.tx(async (client, connection) => {
|
|
5043
|
+
await assertMemoryFence(ctx, client, fence, "appendBufferedObservations");
|
|
5044
|
+
const row = await resolveLiveOMRow(ctx, connection, await lockOMRow(ctx, connection, input.id, "UPDATE_BUFFERED_OBSERVATIONS"));
|
|
5045
|
+
const existingChunks = parseBufferedChunks(row.bufferedObservationChunks);
|
|
5046
|
+
if (existingChunks.some((existing) => existing.cycleId === input.chunk.cycleId) || isBufferedChunkCoveredByCursor(input.chunk.lastObservedAt, row.lastObservedAt)) return {
|
|
5047
|
+
persisted: false,
|
|
5048
|
+
recordId: row.id
|
|
5049
|
+
};
|
|
4748
5050
|
const newChunk = {
|
|
4749
5051
|
id: `ombuf-${globalThis.crypto.randomUUID()}`,
|
|
4750
5052
|
cycleId: input.chunk.cycleId,
|
|
@@ -4760,32 +5062,48 @@ async function updateBufferedObservations(ctx, input) {
|
|
|
4760
5062
|
extractedValues: input.chunk.extractedValues,
|
|
4761
5063
|
extractionFailures: input.chunk.extractionFailures
|
|
4762
5064
|
};
|
|
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);
|
|
5065
|
+
const lastBufferedAtTime = maxObservationCursor(row.lastBufferedAtTime, input.lastBufferedAtTime);
|
|
4771
5066
|
assertRowsAffected((await connection.execute(`UPDATE ${table(ctx, TABLE_OBSERVATIONAL_MEMORY)}
|
|
4772
5067
|
SET ${OM_BUFFERED_OBSERVATION_CHUNKS} = :bufferedObservationChunks,
|
|
4773
|
-
${
|
|
4774
|
-
|
|
5068
|
+
${OM_LAST_BUFFERED_AT_TIME} = :lastBufferedAtTime,
|
|
5069
|
+
${OM_UPDATED_AT} = :updatedAt
|
|
5070
|
+
WHERE id = :id`, asBindParameters({
|
|
5071
|
+
id: row.id,
|
|
5072
|
+
bufferedObservationChunks: nullableJsonBind([...existingChunks, newChunk]),
|
|
5073
|
+
lastBufferedAtTime: timestampBind(lastBufferedAtTime),
|
|
5074
|
+
updatedAt: timestampBind(/* @__PURE__ */ new Date())
|
|
5075
|
+
}))).rowsAffected, "UPDATE_BUFFERED_OBSERVATIONS", row.id);
|
|
5076
|
+
return {
|
|
5077
|
+
persisted: true,
|
|
5078
|
+
recordId: row.id
|
|
5079
|
+
};
|
|
4775
5080
|
});
|
|
4776
5081
|
} catch (error) {
|
|
4777
5082
|
if (error instanceof MastraError) throw error;
|
|
4778
5083
|
throw storageError$1("UPDATE_BUFFERED_OBSERVATIONS", "FAILED", { id: input.id }, error);
|
|
4779
5084
|
}
|
|
4780
5085
|
}
|
|
4781
|
-
async function swapBufferedToActive(ctx, input) {
|
|
5086
|
+
async function swapBufferedToActive(ctx, input, fence) {
|
|
4782
5087
|
try {
|
|
4783
|
-
return await ctx.db.tx(async (
|
|
5088
|
+
return await ctx.db.tx(async (client, connection) => {
|
|
5089
|
+
await assertMemoryFence(ctx, client, fence, "swapBufferedToActive");
|
|
4784
5090
|
const row = await lockOMRow(ctx, connection, input.id, "SWAP_BUFFERED_TO_ACTIVE");
|
|
4785
|
-
|
|
5091
|
+
if (row.supersededBy) return {
|
|
5092
|
+
...emptySwapResult(),
|
|
5093
|
+
retired: true
|
|
5094
|
+
};
|
|
5095
|
+
const refreshedWeights = new Map((input.bufferedChunks ?? []).map((chunk) => [chunk.id, chunk.messageTokens]));
|
|
5096
|
+
const chunks = parseBufferedChunks(row.bufferedObservationChunks).map((chunk) => {
|
|
5097
|
+
const weight = refreshedWeights.get(chunk.id);
|
|
5098
|
+
return weight === void 0 ? chunk : {
|
|
5099
|
+
...chunk,
|
|
5100
|
+
messageTokens: weight
|
|
5101
|
+
};
|
|
5102
|
+
});
|
|
4786
5103
|
if (chunks.length === 0) return emptySwapResult();
|
|
4787
5104
|
const activation = calculateBufferedActivation(chunks, input);
|
|
4788
5105
|
const lastObservedAt = input.lastObservedAt ?? (activation.activatedChunks.at(-1)?.lastObservedAt ? toDate(activation.activatedChunks.at(-1).lastObservedAt) : /* @__PURE__ */ new Date());
|
|
5106
|
+
const storedCursor = maxObservationCursor(row.lastObservedAt, lastObservedAt);
|
|
4789
5107
|
const boundary = `\n\n--- message boundary (${lastObservedAt.toISOString()}) ---\n\n`;
|
|
4790
5108
|
const existingActive = stringOrEmpty(row.activeObservations);
|
|
4791
5109
|
const newActive = existingActive ? `${existingActive}${boundary}${activation.activatedContent}` : activation.activatedContent;
|
|
@@ -4803,8 +5121,8 @@ async function swapBufferedToActive(ctx, input) {
|
|
|
4803
5121
|
observationTokens: activation.activatedTokens,
|
|
4804
5122
|
pendingMessageTokens: pendingTokens,
|
|
4805
5123
|
bufferedObservationChunks: nullableJsonBind(activation.remainingChunks.length > 0 ? activation.remainingChunks : null),
|
|
4806
|
-
lastObservedAt,
|
|
4807
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
5124
|
+
lastObservedAt: timestampBind(storedCursor),
|
|
5125
|
+
updatedAt: timestampBind(/* @__PURE__ */ new Date())
|
|
4808
5126
|
})).rowsAffected, "SWAP_BUFFERED_TO_ACTIVE", input.id);
|
|
4809
5127
|
return activation.result;
|
|
4810
5128
|
});
|
|
@@ -4813,9 +5131,10 @@ async function swapBufferedToActive(ctx, input) {
|
|
|
4813
5131
|
throw storageError$1("SWAP_BUFFERED_TO_ACTIVE", "FAILED", { id: input.id }, error);
|
|
4814
5132
|
}
|
|
4815
5133
|
}
|
|
4816
|
-
async function updateBufferedReflection(ctx, input) {
|
|
5134
|
+
async function updateBufferedReflection(ctx, input, fence) {
|
|
4817
5135
|
try {
|
|
4818
|
-
await ctx.db.tx(async (
|
|
5136
|
+
await ctx.db.tx(async (client, connection) => {
|
|
5137
|
+
await assertMemoryFence(ctx, client, fence, "updateBufferedReflection");
|
|
4819
5138
|
assertRowsAffected((await connection.execute(`UPDATE ${table(ctx, TABLE_OBSERVATIONAL_MEMORY)}
|
|
4820
5139
|
SET ${OM_BUFFERED_REFLECTION} = CASE
|
|
4821
5140
|
WHEN ${OM_BUFFERED_REFLECTION} IS NOT NULL AND DBMS_LOB.GETLENGTH(${OM_BUFFERED_REFLECTION}) > 0
|
|
@@ -4832,7 +5151,7 @@ async function updateBufferedReflection(ctx, input) {
|
|
|
4832
5151
|
tokenCount: Math.round(input.tokenCount),
|
|
4833
5152
|
inputTokenCount: Math.round(input.inputTokenCount),
|
|
4834
5153
|
reflectedObservationLineCount: Math.round(input.reflectedObservationLineCount),
|
|
4835
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
5154
|
+
updatedAt: timestampBind(/* @__PURE__ */ new Date())
|
|
4836
5155
|
})).rowsAffected, "UPDATE_BUFFERED_REFLECTION", input.id);
|
|
4837
5156
|
});
|
|
4838
5157
|
} catch (error) {
|
|
@@ -4840,67 +5159,34 @@ async function updateBufferedReflection(ctx, input) {
|
|
|
4840
5159
|
throw storageError$1("UPDATE_BUFFERED_REFLECTION", "FAILED", { id: input.id }, error);
|
|
4841
5160
|
}
|
|
4842
5161
|
}
|
|
4843
|
-
async function swapBufferedReflectionToActive(ctx, input) {
|
|
5162
|
+
async function swapBufferedReflectionToActive(ctx, input, fence) {
|
|
5163
|
+
const { currentRecord } = input;
|
|
4844
5164
|
try {
|
|
4845
|
-
return await ctx.db.tx(async (
|
|
4846
|
-
|
|
4847
|
-
const
|
|
4848
|
-
if (!
|
|
4849
|
-
|
|
4850
|
-
const
|
|
4851
|
-
const
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
const
|
|
4855
|
-
|
|
4856
|
-
|
|
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);
|
|
5165
|
+
return await ctx.db.tx(async (client, connection) => {
|
|
5166
|
+
await assertMemoryFence(ctx, client, fence, "swapBufferedReflectionToActive");
|
|
5167
|
+
const row = await findOMRowForUpdate(ctx, connection, currentRecord.id);
|
|
5168
|
+
if (!row) return currentRecord;
|
|
5169
|
+
if (row.supersededBy) return parseOMRow(await resolveLiveOMRow(ctx, connection, row));
|
|
5170
|
+
const stored = parseOMRow(row);
|
|
5171
|
+
const bufferedReflection = stored.bufferedReflection ?? "";
|
|
5172
|
+
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);
|
|
5173
|
+
if (!isAppendOnlySince(stored.activeObservations, currentRecord.activeObservations)) return stored;
|
|
5174
|
+
const reflectedLineCount = stored.reflectedObservationLineCount ?? 0;
|
|
5175
|
+
const unreflectedContent = stored.activeObservations.split("\n").slice(reflectedLineCount).join("\n").trim();
|
|
5176
|
+
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
5177
|
assertRowsAffected((await connection.execute(`UPDATE ${table(ctx, TABLE_OBSERVATIONAL_MEMORY)}
|
|
4880
5178
|
SET ${OM_BUFFERED_REFLECTION} = NULL,
|
|
4881
5179
|
${OM_BUFFERED_REFLECTION_TOKENS} = NULL,
|
|
4882
5180
|
${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);
|
|
5181
|
+
${OM_REFLECTED_OBSERVATION_LINE_COUNT} = NULL
|
|
5182
|
+
WHERE id = :id`, { id: stored.id })).rowsAffected, "SWAP_BUFFERED_REFLECTION_TO_ACTIVE", stored.id);
|
|
4889
5183
|
return newRecord;
|
|
4890
5184
|
});
|
|
4891
5185
|
} catch (error) {
|
|
4892
5186
|
if (error instanceof MastraError) throw error;
|
|
4893
|
-
throw storageError$1("SWAP_BUFFERED_REFLECTION_TO_ACTIVE", "FAILED", { id:
|
|
5187
|
+
throw storageError$1("SWAP_BUFFERED_REFLECTION_TO_ACTIVE", "FAILED", { id: currentRecord.id }, error);
|
|
4894
5188
|
}
|
|
4895
5189
|
}
|
|
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
5190
|
function emptySwapResult() {
|
|
4905
5191
|
return {
|
|
4906
5192
|
chunksActivated: 0,
|
|
@@ -5030,17 +5316,24 @@ async function saveResource(ctx, { resource }) {
|
|
|
5030
5316
|
throw storageError$1("SAVE_RESOURCE", "FAILED", { resourceId: resource.id }, error);
|
|
5031
5317
|
}
|
|
5032
5318
|
}
|
|
5033
|
-
async function updateResource(ctx, { resourceId, workingMemory, metadata }) {
|
|
5319
|
+
async function updateResource(ctx, { resourceId, workingMemory, metadata, fence }) {
|
|
5034
5320
|
const existing = await ctx.getResourceById({ resourceId });
|
|
5035
|
-
|
|
5321
|
+
const newResource = () => ({
|
|
5036
5322
|
id: resourceId,
|
|
5037
5323
|
workingMemory,
|
|
5038
5324
|
metadata: metadata ?? {},
|
|
5039
5325
|
createdAt: /* @__PURE__ */ new Date(),
|
|
5040
5326
|
updatedAt: /* @__PURE__ */ new Date()
|
|
5041
|
-
}
|
|
5327
|
+
});
|
|
5328
|
+
if (!existing && !fence) return ctx.saveResource({ resource: newResource() });
|
|
5042
5329
|
try {
|
|
5043
|
-
return await ctx.db.tx(async (
|
|
5330
|
+
return await ctx.db.tx(async (client, connection) => {
|
|
5331
|
+
await assertMemoryFence(ctx, client, fence, "updateResource");
|
|
5332
|
+
if (!existing) {
|
|
5333
|
+
const resource = newResource();
|
|
5334
|
+
await mergeResourceRow(ctx, connection, resource);
|
|
5335
|
+
return resource;
|
|
5336
|
+
}
|
|
5044
5337
|
const row = rows(await connection.execute(`${resourceSelect()} FROM ${table(ctx, TABLE_RESOURCES)} WHERE id = :resourceId FOR UPDATE`, { resourceId }, executeOptions()))[0];
|
|
5045
5338
|
const locked = row ? parseResource(row) : existing;
|
|
5046
5339
|
const updated = {
|
|
@@ -5134,11 +5427,15 @@ async function getThreadById(ctx, { threadId, resourceId }) {
|
|
|
5134
5427
|
throw storageError$1("GET_THREAD_BY_ID", "FAILED", { threadId }, error);
|
|
5135
5428
|
}
|
|
5136
5429
|
}
|
|
5137
|
-
async function saveThread(ctx, { thread }) {
|
|
5430
|
+
async function saveThread(ctx, { thread, fence }) {
|
|
5138
5431
|
try {
|
|
5139
|
-
await
|
|
5432
|
+
await ctx.db.tx(async (client) => {
|
|
5433
|
+
await assertMemoryFence(ctx, client, fence, "saveThread");
|
|
5434
|
+
await mergeThreadRow(ctx, client, thread);
|
|
5435
|
+
});
|
|
5140
5436
|
return thread;
|
|
5141
5437
|
} catch (error) {
|
|
5438
|
+
if (error instanceof MastraError) throw error;
|
|
5142
5439
|
throw storageError$1("SAVE_THREAD", "FAILED", { threadId: thread.id }, error);
|
|
5143
5440
|
}
|
|
5144
5441
|
}
|
|
@@ -5223,7 +5520,7 @@ async function insertThreadRow(ctx, client, thread) {
|
|
|
5223
5520
|
updatedAt: thread.updatedAt ?? /* @__PURE__ */ new Date()
|
|
5224
5521
|
});
|
|
5225
5522
|
}
|
|
5226
|
-
async function updateThread(ctx, { id, title, metadata }) {
|
|
5523
|
+
async function updateThread(ctx, { id, title, metadata, fence }) {
|
|
5227
5524
|
const existingThread = await ctx.getThreadById({ threadId: id });
|
|
5228
5525
|
if (!existingThread) throw storageError$1("UPDATE_THREAD", "FAILED", {
|
|
5229
5526
|
threadId: id,
|
|
@@ -5235,16 +5532,19 @@ async function updateThread(ctx, { id, title, metadata }) {
|
|
|
5235
5532
|
};
|
|
5236
5533
|
const updatedAt = /* @__PURE__ */ new Date();
|
|
5237
5534
|
try {
|
|
5238
|
-
await ctx.db.
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5535
|
+
await ctx.db.tx(async (client) => {
|
|
5536
|
+
await assertMemoryFence(ctx, client, fence, "updateThread");
|
|
5537
|
+
await client.none(`
|
|
5538
|
+
UPDATE ${table(ctx, TABLE_THREADS)}
|
|
5539
|
+
SET title = COALESCE(:title, title),
|
|
5540
|
+
metadata = :metadata,
|
|
5541
|
+
${THREAD_UPDATED_AT} = :updatedAt
|
|
5542
|
+
WHERE id = :id`, {
|
|
5543
|
+
id,
|
|
5544
|
+
title: optionalStringBind(title),
|
|
5545
|
+
metadata: jsonBind(mergedMetadata),
|
|
5546
|
+
updatedAt
|
|
5547
|
+
});
|
|
5248
5548
|
});
|
|
5249
5549
|
const updatedThread = await ctx.getThreadById({ threadId: id });
|
|
5250
5550
|
if (!updatedThread) throw storageError$1("UPDATE_THREAD", "FAILED", {
|
|
@@ -5396,7 +5696,8 @@ var MemoryOracle = class MemoryOracle extends MemoryStorage {
|
|
|
5396
5696
|
TABLE_THREADS,
|
|
5397
5697
|
TABLE_MESSAGES,
|
|
5398
5698
|
TABLE_RESOURCES,
|
|
5399
|
-
TABLE_OBSERVATIONAL_MEMORY
|
|
5699
|
+
TABLE_OBSERVATIONAL_MEMORY,
|
|
5700
|
+
TABLE_MEMORY_RUN_FENCES
|
|
5400
5701
|
];
|
|
5401
5702
|
db;
|
|
5402
5703
|
schemaName;
|
|
@@ -5434,18 +5735,77 @@ var MemoryOracle = class MemoryOracle extends MemoryStorage {
|
|
|
5434
5735
|
}
|
|
5435
5736
|
async init() {
|
|
5436
5737
|
await initMemorySchema(this.ctx);
|
|
5738
|
+
await this.db.createTable({
|
|
5739
|
+
tableName: TABLE_MEMORY_RUN_FENCES,
|
|
5740
|
+
schema: RUN_FENCING_TABLE_SCHEMAS[TABLE_MEMORY_RUN_FENCES]
|
|
5741
|
+
});
|
|
5742
|
+
}
|
|
5743
|
+
/**
|
|
5744
|
+
* Mark observational memory rows that are not their key's canonical head as superseded.
|
|
5745
|
+
* Runs as part of `init()`; `OracleStore` also runs it on every start when the memory schema
|
|
5746
|
+
* migration is unchanged and skipped.
|
|
5747
|
+
* @internal
|
|
5748
|
+
*/
|
|
5749
|
+
async backfillObservationalMemorySupersededBy() {
|
|
5750
|
+
await this.ctx.db.withConnection((connection) => backfillObservationalMemorySupersededBy(this.ctx, connection));
|
|
5437
5751
|
}
|
|
5438
5752
|
async dangerouslyClearAll() {
|
|
5439
5753
|
await clearAllMemoryTables(this.ctx);
|
|
5754
|
+
await this.db.clearTable(TABLE_MEMORY_RUN_FENCES);
|
|
5755
|
+
}
|
|
5756
|
+
supportsRunFencing() {
|
|
5757
|
+
return true;
|
|
5758
|
+
}
|
|
5759
|
+
async raiseRunFence(fence) {
|
|
5760
|
+
const fences = table(this.ctx, TABLE_MEMORY_RUN_FENCES);
|
|
5761
|
+
try {
|
|
5762
|
+
return await claimTransaction(this.db, async (client) => {
|
|
5763
|
+
const row = await client.oneOrNone(`SELECT generation AS "generation", "ownerId" FROM ${fences} WHERE "runId" = :runId FOR UPDATE`, { runId: fence.runId });
|
|
5764
|
+
const binds = {
|
|
5765
|
+
runId: fence.runId,
|
|
5766
|
+
generation: fence.generation,
|
|
5767
|
+
ownerId: fence.ownerId
|
|
5768
|
+
};
|
|
5769
|
+
if (!row) {
|
|
5770
|
+
await client.none(`INSERT INTO ${fences} ("runId", generation, "ownerId") VALUES (:runId, :generation, :ownerId)`, binds);
|
|
5771
|
+
return true;
|
|
5772
|
+
}
|
|
5773
|
+
const generation = Number(row.generation);
|
|
5774
|
+
if (!(generation < fence.generation) && !(generation === fence.generation && String(row.ownerId) === fence.ownerId)) return false;
|
|
5775
|
+
await client.none(`UPDATE ${fences} SET generation = :generation, "ownerId" = :ownerId, "retiredAt" = NULL WHERE "runId" = :runId`, binds);
|
|
5776
|
+
return true;
|
|
5777
|
+
});
|
|
5778
|
+
} catch (error) {
|
|
5779
|
+
throw storageError$1("RAISE_RUN_FENCE", "FAILED", { runId: fence.runId }, error);
|
|
5780
|
+
}
|
|
5781
|
+
}
|
|
5782
|
+
async retireRunFence(fence) {
|
|
5783
|
+
const fences = table(this.ctx, TABLE_MEMORY_RUN_FENCES);
|
|
5784
|
+
try {
|
|
5785
|
+
return await this.db.tx(async (client) => {
|
|
5786
|
+
const row = await client.oneOrNone(`SELECT generation AS "generation", "ownerId" FROM ${fences} WHERE "runId" = :runId FOR UPDATE`, { runId: fence.runId });
|
|
5787
|
+
if (!row || Number(row.generation) !== fence.generation || String(row.ownerId) !== fence.ownerId) return false;
|
|
5788
|
+
await client.none(`UPDATE ${fences} SET "retiredAt" = ${DB_NOW_MS} WHERE "runId" = :runId`, { runId: fence.runId });
|
|
5789
|
+
return true;
|
|
5790
|
+
});
|
|
5791
|
+
} catch (error) {
|
|
5792
|
+
throw storageError$1("RETIRE_RUN_FENCE", "FAILED", { runId: fence.runId }, error);
|
|
5793
|
+
}
|
|
5440
5794
|
}
|
|
5441
5795
|
async getThreadById(args) {
|
|
5442
5796
|
return getThreadById(this.ctx, args);
|
|
5443
5797
|
}
|
|
5444
|
-
async saveThread(args) {
|
|
5445
|
-
return saveThread(this.ctx,
|
|
5798
|
+
async saveThread({ fence, ...args }) {
|
|
5799
|
+
return saveThread(this.ctx, {
|
|
5800
|
+
...args,
|
|
5801
|
+
fence: resolveRunFence(this, fence)
|
|
5802
|
+
});
|
|
5446
5803
|
}
|
|
5447
|
-
async updateThread(args) {
|
|
5448
|
-
return updateThread(this.ctx,
|
|
5804
|
+
async updateThread({ fence, ...args }) {
|
|
5805
|
+
return updateThread(this.ctx, {
|
|
5806
|
+
...args,
|
|
5807
|
+
fence: resolveRunFence(this, fence)
|
|
5808
|
+
});
|
|
5449
5809
|
}
|
|
5450
5810
|
async deleteThread(args) {
|
|
5451
5811
|
return deleteThread(this.ctx, args);
|
|
@@ -5465,14 +5825,20 @@ var MemoryOracle = class MemoryOracle extends MemoryStorage {
|
|
|
5465
5825
|
async listMessagesByResourceId(args) {
|
|
5466
5826
|
return listMessagesByResourceId(this.ctx, args);
|
|
5467
5827
|
}
|
|
5468
|
-
async saveMessages(args) {
|
|
5469
|
-
return saveMessages(this.ctx,
|
|
5828
|
+
async saveMessages({ fence, ...args }) {
|
|
5829
|
+
return saveMessages(this.ctx, {
|
|
5830
|
+
...args,
|
|
5831
|
+
fence: resolveRunFence(this, fence)
|
|
5832
|
+
});
|
|
5470
5833
|
}
|
|
5471
|
-
async updateMessages(args) {
|
|
5472
|
-
return updateMessages(this.ctx,
|
|
5834
|
+
async updateMessages({ fence, ...args }) {
|
|
5835
|
+
return updateMessages(this.ctx, {
|
|
5836
|
+
...args,
|
|
5837
|
+
fence: resolveRunFence(this, fence)
|
|
5838
|
+
});
|
|
5473
5839
|
}
|
|
5474
|
-
async deleteMessages(messageIds) {
|
|
5475
|
-
return deleteMessages(this.ctx, messageIds);
|
|
5840
|
+
async deleteMessages(messageIds, options) {
|
|
5841
|
+
return deleteMessages(this.ctx, messageIds, resolveRunFence(this, options?.fence));
|
|
5476
5842
|
}
|
|
5477
5843
|
async getResourceById(args) {
|
|
5478
5844
|
return getResourceById(this.ctx, args);
|
|
@@ -5480,8 +5846,11 @@ var MemoryOracle = class MemoryOracle extends MemoryStorage {
|
|
|
5480
5846
|
async saveResource(args) {
|
|
5481
5847
|
return saveResource(this.ctx, args);
|
|
5482
5848
|
}
|
|
5483
|
-
async updateResource(args) {
|
|
5484
|
-
return updateResource(this.ctx,
|
|
5849
|
+
async updateResource({ fence, ...args }) {
|
|
5850
|
+
return updateResource(this.ctx, {
|
|
5851
|
+
...args,
|
|
5852
|
+
fence: resolveRunFence(this, fence)
|
|
5853
|
+
});
|
|
5485
5854
|
}
|
|
5486
5855
|
async cloneThread(args) {
|
|
5487
5856
|
const sourceThread = await this.getThreadById({ threadId: args.sourceThreadId });
|
|
@@ -5571,10 +5940,13 @@ var MemoryOracle = class MemoryOracle extends MemoryStorage {
|
|
|
5571
5940
|
return insertObservationalMemoryRecord(this.ctx, record);
|
|
5572
5941
|
}
|
|
5573
5942
|
async updateActiveObservations(input) {
|
|
5574
|
-
|
|
5943
|
+
assertActiveObservationsApplied(await this.commitActiveObservations(input), input.id);
|
|
5944
|
+
}
|
|
5945
|
+
async commitActiveObservations(input) {
|
|
5946
|
+
return updateActiveObservations(this.ctx, input, resolveRunFence(this, void 0));
|
|
5575
5947
|
}
|
|
5576
5948
|
async createReflectionGeneration(input) {
|
|
5577
|
-
return createReflectionGeneration(this.ctx, input);
|
|
5949
|
+
return createReflectionGeneration(this.ctx, input, resolveRunFence(this, void 0));
|
|
5578
5950
|
}
|
|
5579
5951
|
async setReflectingFlag(id, isReflecting) {
|
|
5580
5952
|
return setReflectingFlag(this.ctx, id, isReflecting);
|
|
@@ -5598,16 +5970,19 @@ var MemoryOracle = class MemoryOracle extends MemoryStorage {
|
|
|
5598
5970
|
return updateObservationalMemoryConfig(this.ctx, input);
|
|
5599
5971
|
}
|
|
5600
5972
|
async updateBufferedObservations(input) {
|
|
5601
|
-
|
|
5973
|
+
await this.appendBufferedObservations(input);
|
|
5974
|
+
}
|
|
5975
|
+
async appendBufferedObservations(input) {
|
|
5976
|
+
return updateBufferedObservations(this.ctx, input, resolveRunFence(this, void 0));
|
|
5602
5977
|
}
|
|
5603
5978
|
async swapBufferedToActive(input) {
|
|
5604
|
-
return swapBufferedToActive(this.ctx, input);
|
|
5979
|
+
return swapBufferedToActive(this.ctx, input, resolveRunFence(this, void 0));
|
|
5605
5980
|
}
|
|
5606
5981
|
async updateBufferedReflection(input) {
|
|
5607
|
-
return updateBufferedReflection(this.ctx, input);
|
|
5982
|
+
return updateBufferedReflection(this.ctx, input, resolveRunFence(this, void 0));
|
|
5608
5983
|
}
|
|
5609
5984
|
async swapBufferedReflectionToActive(input) {
|
|
5610
|
-
return swapBufferedReflectionToActive(this.ctx, input);
|
|
5985
|
+
return swapBufferedReflectionToActive(this.ctx, input, resolveRunFence(this, void 0));
|
|
5611
5986
|
}
|
|
5612
5987
|
};
|
|
5613
5988
|
//#endregion
|
|
@@ -7952,8 +8327,19 @@ const STORE_NAME$1 = "ORACLEDB";
|
|
|
7952
8327
|
const WORKFLOW_CREATED_AT = "\"createdAt\"";
|
|
7953
8328
|
const WORKFLOW_UPDATED_AT = "\"updatedAt\"";
|
|
7954
8329
|
const WORKFLOW_RESOURCE_ID = "\"resourceId\"";
|
|
8330
|
+
const RUN_OWNER_COLUMNS = `generation AS "generation", "ownerId", "leaseExpiresAt", ${DB_NOW_MS} AS "nowMs"`;
|
|
8331
|
+
function toRunOwnershipRecord(runId, row) {
|
|
8332
|
+
const leaseExpiresAt = row.leaseExpiresAt === null ? null : Number(row.leaseExpiresAt);
|
|
8333
|
+
return {
|
|
8334
|
+
runId,
|
|
8335
|
+
generation: Number(row.generation),
|
|
8336
|
+
ownerId: String(row.ownerId),
|
|
8337
|
+
leaseExpiresAt: leaseExpiresAt === null ? null : new Date(leaseExpiresAt),
|
|
8338
|
+
live: leaseExpiresAt !== null && leaseExpiresAt > Number(row.nowMs)
|
|
8339
|
+
};
|
|
8340
|
+
}
|
|
7955
8341
|
var WorkflowsOracle = class WorkflowsOracle extends WorkflowsStorage {
|
|
7956
|
-
static MANAGED_TABLES = [TABLE_WORKFLOW_SNAPSHOT];
|
|
8342
|
+
static MANAGED_TABLES = [TABLE_WORKFLOW_SNAPSHOT, TABLE_WORKFLOW_RUN_OWNERS];
|
|
7957
8343
|
db;
|
|
7958
8344
|
schemaName;
|
|
7959
8345
|
skipDefaultIndexes;
|
|
@@ -7974,10 +8360,114 @@ var WorkflowsOracle = class WorkflowsOracle extends WorkflowsStorage {
|
|
|
7974
8360
|
}
|
|
7975
8361
|
async dangerouslyClearAll() {
|
|
7976
8362
|
await this.db.clearTable(TABLE_WORKFLOW_SNAPSHOT);
|
|
8363
|
+
await this.db.clearTable(TABLE_WORKFLOW_RUN_OWNERS);
|
|
8364
|
+
}
|
|
8365
|
+
supportsRunFencing() {
|
|
8366
|
+
return true;
|
|
8367
|
+
}
|
|
8368
|
+
async claimRunOwnership({ runId, ownerId, leaseMs, force, expectedGeneration }) {
|
|
8369
|
+
const table = this.runOwnersTable();
|
|
8370
|
+
try {
|
|
8371
|
+
return await claimTransaction(this.db, async (client) => {
|
|
8372
|
+
const existing = await client.oneOrNone(`SELECT generation AS "generation" FROM ${table} WHERE "runId" = :runId FOR UPDATE`, { runId });
|
|
8373
|
+
if (!existing) {
|
|
8374
|
+
if (expectedGeneration !== void 0 && expectedGeneration !== 0) return {
|
|
8375
|
+
acquired: false,
|
|
8376
|
+
record: null
|
|
8377
|
+
};
|
|
8378
|
+
await client.none(`INSERT INTO ${table} ("runId", generation, "ownerId", "leaseExpiresAt", "updatedAt")
|
|
8379
|
+
VALUES (:runId, ${DB_NOW_MS}, :ownerId, ${DB_NOW_MS} + :leaseMs, ${DB_NOW_MS})`, {
|
|
8380
|
+
runId,
|
|
8381
|
+
ownerId,
|
|
8382
|
+
leaseMs
|
|
8383
|
+
});
|
|
8384
|
+
return {
|
|
8385
|
+
acquired: true,
|
|
8386
|
+
record: await this.readRunOwner(client, runId)
|
|
8387
|
+
};
|
|
8388
|
+
}
|
|
8389
|
+
await client.none(`UPDATE ${table}
|
|
8390
|
+
SET generation = generation + 1, "ownerId" = :ownerId, "leaseExpiresAt" = ${DB_NOW_MS} + :leaseMs,
|
|
8391
|
+
"updatedAt" = ${DB_NOW_MS}
|
|
8392
|
+
WHERE "runId" = :runId
|
|
8393
|
+
AND (:expectedGeneration IS NULL OR generation = :expectedGeneration)
|
|
8394
|
+
AND (:force = 1 OR "leaseExpiresAt" IS NULL OR "leaseExpiresAt" <= ${DB_NOW_MS})`, {
|
|
8395
|
+
runId,
|
|
8396
|
+
ownerId,
|
|
8397
|
+
leaseMs,
|
|
8398
|
+
expectedGeneration: expectedGeneration ?? null,
|
|
8399
|
+
force: force === true ? 1 : 0
|
|
8400
|
+
});
|
|
8401
|
+
const record = await this.readRunOwner(client, runId);
|
|
8402
|
+
return record && record.generation === Number(existing.generation) + 1 ? {
|
|
8403
|
+
acquired: true,
|
|
8404
|
+
record
|
|
8405
|
+
} : {
|
|
8406
|
+
acquired: false,
|
|
8407
|
+
record
|
|
8408
|
+
};
|
|
8409
|
+
});
|
|
8410
|
+
} catch (error) {
|
|
8411
|
+
throw this.storageError("CLAIM_RUN_OWNERSHIP", "FAILED", { runId }, error);
|
|
8412
|
+
}
|
|
8413
|
+
}
|
|
8414
|
+
async renewRunOwnership({ leaseMs, ...fence }) {
|
|
8415
|
+
try {
|
|
8416
|
+
return await this.db.tx(async (client) => {
|
|
8417
|
+
await client.none(`UPDATE ${this.runOwnersTable()}
|
|
8418
|
+
SET "leaseExpiresAt" = ${DB_NOW_MS} + :leaseMs, "updatedAt" = ${DB_NOW_MS}
|
|
8419
|
+
WHERE "runId" = :runId AND generation = :generation AND "ownerId" = :ownerId
|
|
8420
|
+
AND "leaseExpiresAt" IS NOT NULL`, {
|
|
8421
|
+
runId: fence.runId,
|
|
8422
|
+
generation: fence.generation,
|
|
8423
|
+
ownerId: fence.ownerId,
|
|
8424
|
+
leaseMs
|
|
8425
|
+
});
|
|
8426
|
+
const record = await this.readRunOwner(client, fence.runId);
|
|
8427
|
+
return record !== null && record.generation === fence.generation && record.ownerId === fence.ownerId && record.leaseExpiresAt !== null ? {
|
|
8428
|
+
renewed: true,
|
|
8429
|
+
record
|
|
8430
|
+
} : {
|
|
8431
|
+
renewed: false,
|
|
8432
|
+
record
|
|
8433
|
+
};
|
|
8434
|
+
});
|
|
8435
|
+
} catch (error) {
|
|
8436
|
+
throw this.storageError("RENEW_RUN_OWNERSHIP", "FAILED", { runId: fence.runId }, error);
|
|
8437
|
+
}
|
|
8438
|
+
}
|
|
8439
|
+
async releaseRunOwnership(fence) {
|
|
8440
|
+
try {
|
|
8441
|
+
return await this.db.tx(async (client) => {
|
|
8442
|
+
await client.none(`UPDATE ${this.runOwnersTable()} SET "leaseExpiresAt" = NULL, "updatedAt" = ${DB_NOW_MS}
|
|
8443
|
+
WHERE "runId" = :runId AND generation = :generation AND "ownerId" = :ownerId`, {
|
|
8444
|
+
runId: fence.runId,
|
|
8445
|
+
generation: fence.generation,
|
|
8446
|
+
ownerId: fence.ownerId
|
|
8447
|
+
});
|
|
8448
|
+
const record = await this.readRunOwner(client, fence.runId);
|
|
8449
|
+
return record !== null && record.generation === fence.generation && record.ownerId === fence.ownerId;
|
|
8450
|
+
});
|
|
8451
|
+
} catch (error) {
|
|
8452
|
+
throw this.storageError("RELEASE_RUN_OWNERSHIP", "FAILED", { runId: fence.runId }, error);
|
|
8453
|
+
}
|
|
7977
8454
|
}
|
|
7978
|
-
async
|
|
8455
|
+
async getRunOwnership({ runId }) {
|
|
8456
|
+
try {
|
|
8457
|
+
return await this.readRunOwner(this.db, runId);
|
|
8458
|
+
} catch (error) {
|
|
8459
|
+
throw this.storageError("GET_RUN_OWNERSHIP", "FAILED", { runId }, error);
|
|
8460
|
+
}
|
|
8461
|
+
}
|
|
8462
|
+
async readRunOwner(client, runId) {
|
|
8463
|
+
const row = await client.oneOrNone(`SELECT ${RUN_OWNER_COLUMNS} FROM ${this.runOwnersTable()} WHERE "runId" = :runId`, { runId });
|
|
8464
|
+
return row ? toRunOwnershipRecord(runId, row) : null;
|
|
8465
|
+
}
|
|
8466
|
+
async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext, state, fence: explicitFence }) {
|
|
8467
|
+
const fence = resolveRunFence(this, explicitFence, runId);
|
|
7979
8468
|
try {
|
|
7980
8469
|
return await this.db.tx(async (client) => {
|
|
8470
|
+
if (fence) await assertRunFence(client, this.runOwnersTable(), fence, "updateWorkflowResults");
|
|
7981
8471
|
await this.ensureWorkflowRunRow(client, workflowName, runId);
|
|
7982
8472
|
const existing = await client.one(`SELECT snapshot AS "snapshot", ${WORKFLOW_RESOURCE_ID} AS "resourceId" FROM ${this.table()} WHERE workflow_name = :workflowName AND run_id = :runId FOR UPDATE`, {
|
|
7983
8473
|
workflowName,
|
|
@@ -8001,6 +8491,7 @@ var WorkflowsOracle = class WorkflowsOracle extends WorkflowsStorage {
|
|
|
8001
8491
|
return snapshot.context;
|
|
8002
8492
|
});
|
|
8003
8493
|
} catch (error) {
|
|
8494
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
8004
8495
|
throw this.storageError("UPDATE_WORKFLOW_RESULTS", "FAILED", {
|
|
8005
8496
|
workflowName,
|
|
8006
8497
|
runId,
|
|
@@ -8008,9 +8499,11 @@ var WorkflowsOracle = class WorkflowsOracle extends WorkflowsStorage {
|
|
|
8008
8499
|
}, error);
|
|
8009
8500
|
}
|
|
8010
8501
|
}
|
|
8011
|
-
async updateWorkflowState({ workflowName, runId, opts }) {
|
|
8502
|
+
async updateWorkflowState({ workflowName, runId, opts, fence: explicitFence }) {
|
|
8503
|
+
const fence = resolveRunFence(this, explicitFence, runId);
|
|
8012
8504
|
try {
|
|
8013
8505
|
return await this.db.tx(async (client) => {
|
|
8506
|
+
if (fence) await assertRunFence(client, this.runOwnersTable(), fence, "updateWorkflowState");
|
|
8014
8507
|
const existing = await client.oneOrNone(`SELECT snapshot AS "snapshot" FROM ${this.table()} WHERE workflow_name = :workflowName AND run_id = :runId FOR UPDATE`, {
|
|
8015
8508
|
workflowName,
|
|
8016
8509
|
runId
|
|
@@ -8033,24 +8526,26 @@ var WorkflowsOracle = class WorkflowsOracle extends WorkflowsStorage {
|
|
|
8033
8526
|
return updatedSnapshot;
|
|
8034
8527
|
});
|
|
8035
8528
|
} catch (error) {
|
|
8529
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
8036
8530
|
throw this.storageError("UPDATE_WORKFLOW_STATE", "FAILED", {
|
|
8037
8531
|
workflowName,
|
|
8038
8532
|
runId
|
|
8039
8533
|
}, error);
|
|
8040
8534
|
}
|
|
8041
8535
|
}
|
|
8042
|
-
async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt }) {
|
|
8536
|
+
async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt, fence }) {
|
|
8043
8537
|
try {
|
|
8044
8538
|
const now = /* @__PURE__ */ new Date();
|
|
8045
|
-
await this.db.none(this.workflowMergeSql(), {
|
|
8539
|
+
await withRunFence(this.db, this.runOwnersTable(), resolveRunFence(this, fence, runId), "persistWorkflowSnapshot", (client) => client.none(this.workflowMergeSql(), {
|
|
8046
8540
|
workflowName,
|
|
8047
8541
|
runId,
|
|
8048
8542
|
resourceId: resourceId ?? null,
|
|
8049
8543
|
snapshot: jsonBind(snapshot),
|
|
8050
8544
|
createdAt: createdAt ?? now,
|
|
8051
8545
|
updatedAt: updatedAt ?? now
|
|
8052
|
-
});
|
|
8546
|
+
}));
|
|
8053
8547
|
} catch (error) {
|
|
8548
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
8054
8549
|
throw this.storageError("PERSIST_WORKFLOW_SNAPSHOT", "FAILED", {
|
|
8055
8550
|
workflowName,
|
|
8056
8551
|
runId
|
|
@@ -8114,13 +8609,14 @@ var WorkflowsOracle = class WorkflowsOracle extends WorkflowsStorage {
|
|
|
8114
8609
|
}, error);
|
|
8115
8610
|
}
|
|
8116
8611
|
}
|
|
8117
|
-
async deleteWorkflowRunById({ runId, workflowName }) {
|
|
8612
|
+
async deleteWorkflowRunById({ runId, workflowName, fence }) {
|
|
8118
8613
|
try {
|
|
8119
|
-
await this.db.none(`DELETE FROM ${this.table()} WHERE workflow_name = :workflowName AND run_id = :runId`, {
|
|
8614
|
+
await withRunFence(this.db, this.runOwnersTable(), resolveRunFence(this, fence, runId), "deleteWorkflowRunById", (client) => client.none(`DELETE FROM ${this.table()} WHERE workflow_name = :workflowName AND run_id = :runId`, {
|
|
8120
8615
|
workflowName,
|
|
8121
8616
|
runId
|
|
8122
|
-
});
|
|
8617
|
+
}));
|
|
8123
8618
|
} catch (error) {
|
|
8619
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
8124
8620
|
throw this.storageError("DELETE_WORKFLOW_RUN_BY_ID", "FAILED", {
|
|
8125
8621
|
runId,
|
|
8126
8622
|
workflowName
|
|
@@ -8139,6 +8635,10 @@ var WorkflowsOracle = class WorkflowsOracle extends WorkflowsStorage {
|
|
|
8139
8635
|
CONSTRAINT ${this.constraint("MASTRA_WORKFLOW_SNAPSHOT_PK")} PRIMARY KEY (workflow_name, run_id)
|
|
8140
8636
|
)`, [-955]);
|
|
8141
8637
|
await this.db.executeDdl(`ALTER TABLE ${this.table()} ADD (${WORKFLOW_RESOURCE_ID} VARCHAR2(512))`, [-1430]);
|
|
8638
|
+
await this.db.createTable({
|
|
8639
|
+
tableName: TABLE_WORKFLOW_RUN_OWNERS,
|
|
8640
|
+
schema: RUN_FENCING_TABLE_SCHEMAS[TABLE_WORKFLOW_RUN_OWNERS]
|
|
8641
|
+
});
|
|
8142
8642
|
}
|
|
8143
8643
|
async createIndexes() {
|
|
8144
8644
|
await this.db.withConnection(async (connection) => {
|
|
@@ -8276,6 +8776,9 @@ var WorkflowsOracle = class WorkflowsOracle extends WorkflowsStorage {
|
|
|
8276
8776
|
table() {
|
|
8277
8777
|
return qualifyName(TABLE_WORKFLOW_SNAPSHOT, this.schemaName);
|
|
8278
8778
|
}
|
|
8779
|
+
runOwnersTable() {
|
|
8780
|
+
return qualifyName(TABLE_WORKFLOW_RUN_OWNERS, this.schemaName);
|
|
8781
|
+
}
|
|
8279
8782
|
indexName(indexName) {
|
|
8280
8783
|
return indexNameForTable(indexName, "IDX");
|
|
8281
8784
|
}
|
|
@@ -8477,7 +8980,7 @@ function checksumMigration(input) {
|
|
|
8477
8980
|
//#region src/storage/index.ts
|
|
8478
8981
|
const STORE_NAME = "ORACLEDB";
|
|
8479
8982
|
const DOMAIN_SCHEMA_VERSIONS = {
|
|
8480
|
-
R001_MEMORY_SCHEMA:
|
|
8983
|
+
R001_MEMORY_SCHEMA: 2,
|
|
8481
8984
|
R002_WORKFLOWS_SCHEMA: 1,
|
|
8482
8985
|
R003_OBSERVABILITY_SCHEMA: 1,
|
|
8483
8986
|
R004_SCORES_SCHEMA: 1,
|
|
@@ -8565,6 +9068,10 @@ var OracleStore = class extends MastraCompositeStore {
|
|
|
8565
9068
|
async runMigrations(forceRepeatable) {
|
|
8566
9069
|
try {
|
|
8567
9070
|
const results = await this.migrationRegistry.run(this.storageMigrations(), { forceRepeatable });
|
|
9071
|
+
if (results.find((result) => result.id === "R001_MEMORY_SCHEMA")?.status === "skipped") {
|
|
9072
|
+
const memory = this.stores.memory;
|
|
9073
|
+
if (memory instanceof MemoryOracle) await memory.backfillObservationalMemorySupersededBy();
|
|
9074
|
+
}
|
|
8568
9075
|
this.isInitialized = true;
|
|
8569
9076
|
return results;
|
|
8570
9077
|
} catch (error) {
|