diff --git a/src/config/sessions/transcript-store.sqlite.test.ts b/src/config/sessions/transcript-store.sqlite.test.ts index 5bcd08c085f..efa42252358 100644 --- a/src/config/sessions/transcript-store.sqlite.test.ts +++ b/src/config/sessions/transcript-store.sqlite.test.ts @@ -458,6 +458,38 @@ describe("SQLite session transcript store", () => { ]); }); + it("preserves an explicit transcript database path when listing by agent", () => { + const stateDir = createTempDir(); + const env = { OPENCLAW_STATE_DIR: stateDir }; + const customPath = path.join(stateDir, "custom-agent.sqlite"); + + appendSqliteSessionTranscriptEvent({ + env, + path: customPath, + agentId: "worker-1", + sessionId: "session-1", + event: { type: "message", id: "m1" }, + now: () => 100, + }); + + const [scope] = listSqliteSessionTranscripts({ + env, + path: customPath, + agentId: "worker-1", + }); + + expect(scope).toEqual({ + agentId: "worker-1", + path: customPath, + sessionId: "session-1", + updatedAt: 100, + eventCount: 1, + }); + expect( + scope ? loadSqliteSessionTranscriptEvents(scope).map((entry) => entry.event) : [], + ).toEqual([{ type: "message", id: "m1" }]); + }); + it("deletes transcript snapshots with the transcript", () => { const stateDir = createTempDir(); const env = { OPENCLAW_STATE_DIR: stateDir }; diff --git a/src/config/sessions/transcript-store.sqlite.ts b/src/config/sessions/transcript-store.sqlite.ts index 0117ba66cdb..cd1049ed223 100644 --- a/src/config/sessions/transcript-store.sqlite.ts +++ b/src/config/sessions/transcript-store.sqlite.ts @@ -479,7 +479,7 @@ export function listSqliteSessionTranscripts( ? [ { agentId: normalizeAgentId(options.agentId), - path: undefined, + path: options.path, }, ] : listOpenClawRegisteredAgentDatabases(options); diff --git a/src/plugin-sdk/sqlite-state-lock.test.ts b/src/plugin-sdk/sqlite-state-lock.test.ts index e9e3846e0c9..68b73a73cdd 100644 --- a/src/plugin-sdk/sqlite-state-lock.test.ts +++ b/src/plugin-sdk/sqlite-state-lock.test.ts @@ -117,6 +117,23 @@ describe("withOpenClawStateLock", () => { }); }); + it("releases the lease when the guarded task throws synchronously", async () => { + await withTempDir({ prefix: "openclaw-state-lock-sync-throw-" }, async (dir) => { + const dbPath = path.join(dir, "state.sqlite"); + expect.assertions(2); + + await expect( + withOpenClawStateLock("shared", { path: dbPath, retries: FAST_RETRY }, () => { + throw new Error("boom"); + }), + ).rejects.toThrow("boom"); + + await expect( + withOpenClawStateLock("shared", { path: dbPath, retries: FAST_RETRY }, async () => "ok"), + ).resolves.toBe("ok"); + }); + }); + it("rejects and aborts the guarded task when lease renewal loses ownership", async () => { await withTempDir({ prefix: "openclaw-state-lock-lost-" }, async (dir) => { const dbPath = path.join(dir, "state.sqlite"); @@ -135,16 +152,19 @@ describe("withOpenClawStateLock", () => { }); await entered; - runOpenClawStateWriteTransaction((database) => { - const db = getNodeSqliteKysely(database.db); - executeSqliteQuerySync( - database.db, - db - .deleteFrom("state_leases") - .where("scope", "=", "runtime.lock") - .where("lease_key", "=", "shared"), - ); - }, { path: dbPath }); + runOpenClawStateWriteTransaction( + (database) => { + const db = getNodeSqliteKysely(database.db); + executeSqliteQuerySync( + database.db, + db + .deleteFrom("state_leases") + .where("scope", "=", "runtime.lock") + .where("lease_key", "=", "shared"), + ); + }, + { path: dbPath }, + ); await expect(locked).rejects.toThrow("Lost SQLite state lock runtime.lock:shared"); expect(signal.aborted).toBe(true); diff --git a/src/plugin-sdk/sqlite-state-lock.ts b/src/plugin-sdk/sqlite-state-lock.ts index 82bd0890f83..a80259e476d 100644 --- a/src/plugin-sdk/sqlite-state-lock.ts +++ b/src/plugin-sdk/sqlite-state-lock.ts @@ -277,9 +277,9 @@ export async function withOpenClawStateLock( } }, renewEveryMs); renewal.unref?.(); - const taskPromise = task(abortController.signal); - void taskPromise.catch(() => {}); try { + const taskPromise = Promise.resolve(task(abortController.signal)); + void taskPromise.catch(() => {}); return await Promise.race([taskPromise, lostLock]); } finally { clearInterval(renewal);