mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-17 10:43:03 +00:00
fix: patch legacy sqlite session keys
This commit is contained in:
@@ -171,6 +171,28 @@ describe("SQLite session row key normalization", () => {
|
||||
expect(store[CANONICAL_KEY]?.updatedAt).toBeGreaterThan(100);
|
||||
});
|
||||
|
||||
it("patches and migrates legacy direct mixed-case rows", async () => {
|
||||
seedRawSessionEntry(MIXED_CASE_KEY, {
|
||||
sessionId: "legacy-session",
|
||||
updatedAt: 1,
|
||||
chatType: "direct",
|
||||
channel: "webchat",
|
||||
});
|
||||
|
||||
await patchSessionEntry({
|
||||
agentId: "main",
|
||||
sessionKey: MIXED_CASE_KEY,
|
||||
update: () => ({ updatedAt: 200, modelOverride: "gpt-5.5" }),
|
||||
});
|
||||
|
||||
const store = readMainSessionRows();
|
||||
expect(Object.keys(store)).toEqual([CANONICAL_KEY]);
|
||||
expect(store[CANONICAL_KEY]).toMatchObject({
|
||||
sessionId: "legacy-session",
|
||||
modelOverride: "gpt-5.5",
|
||||
});
|
||||
});
|
||||
|
||||
it("does not migrate legacy mixed-case entries during runtime updates", async () => {
|
||||
seedRawSessionEntry(MIXED_CASE_KEY, {
|
||||
sessionId: "legacy-session",
|
||||
|
||||
@@ -41,6 +41,7 @@ export type MoveSqliteSessionEntryKeyOptions = SqliteSessionEntriesOptions & {
|
||||
export type ApplySqliteSessionEntriesPatchOptions = SqliteSessionEntriesOptions & {
|
||||
upsertEntries?: Readonly<Record<string, SessionEntry>>;
|
||||
expectedEntries?: ReadonlyMap<string, SessionEntry | null>;
|
||||
deleteEntries?: readonly string[];
|
||||
};
|
||||
|
||||
export type SqliteSessionDeliveryContext = {
|
||||
@@ -764,6 +765,23 @@ export function applySqliteSessionEntriesPatch(
|
||||
}),
|
||||
),
|
||||
);
|
||||
for (const sessionKey of options.deleteEntries ?? []) {
|
||||
if (Object.prototype.hasOwnProperty.call(upsertEntries, sessionKey)) {
|
||||
continue;
|
||||
}
|
||||
executeSqliteQuerySync(
|
||||
database.db,
|
||||
getNodeSqliteKysely<SessionEntriesDatabase>(database.db)
|
||||
.deleteFrom("session_entries")
|
||||
.where("session_key", "=", sessionKey),
|
||||
);
|
||||
executeSqliteQuerySync(
|
||||
database.db,
|
||||
getNodeSqliteKysely<SessionEntriesDatabase>(database.db)
|
||||
.deleteFrom("session_routes")
|
||||
.where("session_key", "=", sessionKey),
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}, options);
|
||||
}
|
||||
|
||||
@@ -100,6 +100,24 @@ export function moveSessionEntryKey(
|
||||
return moveSqliteSessionEntryKey(options);
|
||||
}
|
||||
|
||||
function resolvePatchSessionEntry(options: SessionEntryRowOptions & { sessionKey: string }): {
|
||||
entry?: SessionEntry;
|
||||
entryKey: string;
|
||||
normalizedKey: string;
|
||||
} {
|
||||
const trimmedKey = options.sessionKey.trim();
|
||||
const normalizedKey = normalizeSessionRowKey(trimmedKey);
|
||||
const canonical = readSqliteSessionEntry({ ...options, sessionKey: normalizedKey });
|
||||
if (canonical) {
|
||||
return { entry: canonical, entryKey: normalizedKey, normalizedKey };
|
||||
}
|
||||
const direct =
|
||||
trimmedKey === normalizedKey
|
||||
? undefined
|
||||
: readSqliteSessionEntry({ ...options, sessionKey: trimmedKey });
|
||||
return { entry: direct, entryKey: direct ? trimmedKey : normalizedKey, normalizedKey };
|
||||
}
|
||||
|
||||
export async function patchSessionEntry(
|
||||
options: SessionEntryRowOptions & {
|
||||
sessionKey: string;
|
||||
@@ -110,10 +128,10 @@ export async function patchSessionEntry(
|
||||
},
|
||||
): Promise<SessionEntry | null> {
|
||||
for (let attempt = 0; attempt < SESSION_ROW_PATCH_RETRY_LIMIT; attempt += 1) {
|
||||
const stored = getSessionEntry(options);
|
||||
const expected = stored ? structuredClone(stored) : null;
|
||||
const existing = stored
|
||||
? structuredClone(stored)
|
||||
const resolved = resolvePatchSessionEntry(options);
|
||||
const expected = resolved.entry ? structuredClone(resolved.entry) : null;
|
||||
const existing = resolved.entry
|
||||
? structuredClone(resolved.entry)
|
||||
: options.fallbackEntry
|
||||
? structuredClone(options.fallbackEntry)
|
||||
: undefined;
|
||||
@@ -125,13 +143,17 @@ export async function patchSessionEntry(
|
||||
return existing;
|
||||
}
|
||||
const next = mergeSessionEntry(existing, patch);
|
||||
const normalizedKey = normalizeSessionRowKey(options.sessionKey);
|
||||
const expectedEntries = new Map([[resolved.entryKey, expected]]);
|
||||
if (resolved.entryKey !== resolved.normalizedKey) {
|
||||
expectedEntries.set(resolved.normalizedKey, null);
|
||||
}
|
||||
const applied = applySqliteSessionEntriesPatch({
|
||||
agentId: options.agentId,
|
||||
env: options.env,
|
||||
path: options.path,
|
||||
upsertEntries: { [normalizedKey]: next },
|
||||
expectedEntries: new Map([[normalizedKey, expected]]),
|
||||
upsertEntries: { [resolved.normalizedKey]: next },
|
||||
expectedEntries,
|
||||
deleteEntries: resolved.entryKey === resolved.normalizedKey ? undefined : [resolved.entryKey],
|
||||
});
|
||||
if (applied) {
|
||||
return next;
|
||||
|
||||
Reference in New Issue
Block a user