mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-20 05:07:34 +00:00
refactor: store tui last sessions in sqlite table
This commit is contained in:
@@ -104,9 +104,9 @@ The branch already has a real shared SQLite base:
|
||||
`plugin_state_entries`, `plugin_blob_entries`, `transcript_files`,
|
||||
`capture_sessions`, `capture_events`, `capture_blobs`,
|
||||
`sandbox_registry_entries`, `cron_run_logs`, `cron_jobs`, `commitments`,
|
||||
`delivery_queue_entries`, `current_conversation_bindings`, `task_runs`,
|
||||
`task_delivery_state`, `flow_runs`, `subagent_runs`, `migration_runs`, and
|
||||
`backup_runs`.
|
||||
`delivery_queue_entries`, `current_conversation_bindings`,
|
||||
`tui_last_sessions`, `task_runs`, `task_delivery_state`, `flow_runs`,
|
||||
`subagent_runs`, `migration_runs`, and `backup_runs`.
|
||||
- `src/state/openclaw-agent-db.ts` opens
|
||||
`agents/<agentId>/agent/openclaw-agent.sqlite`, registers the database in the
|
||||
global DB, and owns agent-local session, transcript, VFS, artifact, and cache
|
||||
@@ -119,6 +119,9 @@ The branch already has a real shared SQLite base:
|
||||
- Current conversation bindings now live in typed shared
|
||||
`current_conversation_bindings` rows keyed by normalized conversation id and
|
||||
indexed by target session. The old KV scope is migration input only.
|
||||
- TUI last-session restore pointers now live in typed shared
|
||||
`tui_last_sessions` rows keyed by the hashed TUI connection/session scope.
|
||||
The old JSON file and `tui:last-session` KV scope are migration input only.
|
||||
- `src/agents/filesystem/virtual-agent-fs.sqlite.ts` implements a SQLite VFS
|
||||
over the agent database `vfs_entries` table.
|
||||
- `src/agents/runtime-worker.entry.ts` creates per-run SQLite VFS, tool artifact,
|
||||
@@ -534,6 +537,7 @@ task_delivery_state(...)
|
||||
flow_runs(...)
|
||||
subagent_runs(run_id, child_session_key, requester_session_key, controller_session_key, created_at, ended_at, cleanup_handled, payload_json)
|
||||
current_conversation_bindings(binding_key, binding_id, channel, account_id, conversation_id, target_session_key, status, bound_at, expires_at, record_json)
|
||||
tui_last_sessions(scope_key, session_key, updated_at)
|
||||
plugin_state_entries(plugin_id, namespace, entry_key, value_json, created_at, expires_at)
|
||||
plugin_blob_entries(plugin_id, namespace, entry_key, metadata_json, blob, created_at, expires_at)
|
||||
media_blobs(subdir, id, content_type, size_bytes, blob, created_at, updated_at)
|
||||
@@ -619,9 +623,8 @@ Move these into the global database:
|
||||
- Cron job definitions, schedule state, and run history now use shared SQLite;
|
||||
doctor imports/removes legacy `jobs.json`, `jobs-state.json`, and
|
||||
`cron/runs/*.jsonl` files
|
||||
- Device identity/auth/bootstrap, pairing, push, update check, commitments, TUI
|
||||
pointers, OpenRouter model cache, installed plugin index, and app-server
|
||||
bindings
|
||||
- Device identity/auth/bootstrap, pairing, push, update check, commitments,
|
||||
OpenRouter model cache, installed plugin index, and app-server bindings
|
||||
- Device-pair notification subscribers and delivered-request markers now use the
|
||||
shared SQLite plugin-state table instead of `device-pair-notify.json`.
|
||||
- Voice-call call records now use the shared SQLite plugin-state table under the
|
||||
|
||||
@@ -22,9 +22,11 @@ import {
|
||||
import { readMemoryHostEvents } from "../memory-host-sdk/events.js";
|
||||
import { loadNodeHostConfig } from "../node-host/config.js";
|
||||
import { listChannelPairingRequests, readChannelAllowFromStore } from "../pairing/pairing-store.js";
|
||||
import { openOpenClawStateDatabase } from "../state/openclaw-state-db.js";
|
||||
import { readOpenClawStateKvJson } from "../state/openclaw-state-kv.js";
|
||||
import { withEnvAsync } from "../test-utils/env.js";
|
||||
import { withTempDir } from "../test-utils/temp-dir.js";
|
||||
import { readTuiLastSessionKey } from "../tui/tui-last-session.js";
|
||||
|
||||
const noteMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
@@ -491,16 +493,17 @@ describe("maybeRepairLegacyRuntimeStateFiles", () => {
|
||||
await expect(fs.stat(path.join(legacyMediaDir, "legacy-media.txt"))).rejects.toMatchObject({
|
||||
code: "ENOENT",
|
||||
});
|
||||
expect(readOpenClawStateKvJson("subagent_runs", "run-legacy", { env })).toMatchObject({
|
||||
childSessionKey: "agent:main:subagent:legacy",
|
||||
});
|
||||
expect(
|
||||
openOpenClawStateDatabase({ env })
|
||||
.db.prepare("SELECT child_session_key FROM subagent_runs WHERE run_id = ?")
|
||||
.get("run-legacy"),
|
||||
).toEqual({ child_session_key: "agent:main:subagent:legacy" });
|
||||
await expect(fs.stat(path.join(stateDir, "subagents", "runs.json"))).rejects.toMatchObject({
|
||||
code: "ENOENT",
|
||||
});
|
||||
expect(readOpenClawStateKvJson("tui:last-session", "legacy-tui-scope", { env })).toEqual({
|
||||
sessionKey: "agent:main:tui-legacy",
|
||||
updatedAt: 1000,
|
||||
});
|
||||
await expect(
|
||||
readTuiLastSessionKey({ scopeKey: "legacy-tui-scope", stateDir }),
|
||||
).resolves.toBe("agent:main:tui-legacy");
|
||||
await expect(
|
||||
fs.stat(path.join(stateDir, "tui", "last-session.json")),
|
||||
).rejects.toMatchObject({ code: "ENOENT" });
|
||||
|
||||
7
src/state/openclaw-state-db.generated.d.ts
vendored
7
src/state/openclaw-state-db.generated.d.ts
vendored
@@ -344,6 +344,12 @@ export interface TranscriptFiles {
|
||||
session_id: string;
|
||||
}
|
||||
|
||||
export interface TuiLastSessions {
|
||||
scope_key: string;
|
||||
session_key: string;
|
||||
updated_at: number;
|
||||
}
|
||||
|
||||
export interface DB {
|
||||
acp_replay_events: AcpReplayEvents;
|
||||
acp_replay_sessions: AcpReplaySessions;
|
||||
@@ -371,4 +377,5 @@ export interface DB {
|
||||
task_delivery_state: TaskDeliveryState;
|
||||
task_runs: TaskRuns;
|
||||
transcript_files: TranscriptFiles;
|
||||
tui_last_sessions: TuiLastSessions;
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ describe("openclaw state database", () => {
|
||||
|
||||
expect(columns.some((column) => column.name === "sort_order")).toBe(true);
|
||||
expect(index?.sql).toContain("sort_order ASC");
|
||||
expect(version.user_version).toBe(21);
|
||||
expect(version.user_version).toBe(22);
|
||||
});
|
||||
|
||||
it("migrates legacy cron runtime state from kv into cron job columns", () => {
|
||||
@@ -215,7 +215,7 @@ describe("openclaw state database", () => {
|
||||
.prepare("SELECT COUNT(*) AS count FROM kv WHERE scope = ?")
|
||||
.get("cron.jobs.state"),
|
||||
).toEqual({ count: 0 });
|
||||
expect(database.db.prepare("PRAGMA user_version").get()).toEqual({ user_version: 21 });
|
||||
expect(database.db.prepare("PRAGMA user_version").get()).toEqual({ user_version: 22 });
|
||||
});
|
||||
|
||||
it("migrates persisted subagent runs from kv into subagent run rows", () => {
|
||||
@@ -264,7 +264,7 @@ describe("openclaw state database", () => {
|
||||
expect(
|
||||
database.db.prepare("SELECT COUNT(*) AS count FROM kv WHERE scope = ?").get("subagent_runs"),
|
||||
).toEqual({ count: 0 });
|
||||
expect(database.db.prepare("PRAGMA user_version").get()).toEqual({ user_version: 21 });
|
||||
expect(database.db.prepare("PRAGMA user_version").get()).toEqual({ user_version: 22 });
|
||||
});
|
||||
|
||||
it("migrates current conversation bindings from kv into binding rows", () => {
|
||||
@@ -314,7 +314,55 @@ describe("openclaw state database", () => {
|
||||
.prepare("SELECT COUNT(*) AS count FROM kv WHERE scope = ?")
|
||||
.get("current-conversation-bindings"),
|
||||
).toEqual({ count: 0 });
|
||||
expect(database.db.prepare("PRAGMA user_version").get()).toEqual({ user_version: 21 });
|
||||
expect(database.db.prepare("PRAGMA user_version").get()).toEqual({ user_version: 22 });
|
||||
});
|
||||
|
||||
it("migrates TUI last-session pointers from kv into dedicated rows", () => {
|
||||
const stateDir = createTempStateDir();
|
||||
const dbPath = resolveOpenClawStateSqlitePath({ OPENCLAW_STATE_DIR: stateDir });
|
||||
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
||||
const sqlite = requireNodeSqlite();
|
||||
const oldDb = new sqlite.DatabaseSync(dbPath);
|
||||
oldDb.exec(`
|
||||
CREATE TABLE kv (
|
||||
scope TEXT NOT NULL,
|
||||
key TEXT NOT NULL,
|
||||
value_json TEXT NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
PRIMARY KEY (scope, key)
|
||||
);
|
||||
INSERT INTO kv (scope, key, value_json, updated_at)
|
||||
VALUES (
|
||||
'tui:last-session',
|
||||
'scope-main',
|
||||
'{"sessionKey":"agent:main:tui-legacy","updatedAt":1000}',
|
||||
1001
|
||||
);
|
||||
PRAGMA user_version = 21;
|
||||
`);
|
||||
oldDb.close();
|
||||
|
||||
const database = openOpenClawStateDatabase({
|
||||
env: { OPENCLAW_STATE_DIR: stateDir },
|
||||
});
|
||||
|
||||
expect(
|
||||
database.db
|
||||
.prepare(
|
||||
"SELECT scope_key, session_key, updated_at FROM tui_last_sessions WHERE scope_key = ?",
|
||||
)
|
||||
.get("scope-main"),
|
||||
).toEqual({
|
||||
scope_key: "scope-main",
|
||||
session_key: "agent:main:tui-legacy",
|
||||
updated_at: 1000,
|
||||
});
|
||||
expect(
|
||||
database.db
|
||||
.prepare("SELECT COUNT(*) AS count FROM kv WHERE scope = ?")
|
||||
.get("tui:last-session"),
|
||||
).toEqual({ count: 0 });
|
||||
expect(database.db.prepare("PRAGMA user_version").get()).toEqual({ user_version: 22 });
|
||||
});
|
||||
|
||||
it("upgrades task delivery state with task-run cascade integrity", () => {
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
} from "./openclaw-state-db.paths.js";
|
||||
import { OPENCLAW_STATE_SCHEMA_SQL } from "./openclaw-state-schema.generated.js";
|
||||
|
||||
const OPENCLAW_STATE_SCHEMA_VERSION = 21;
|
||||
const OPENCLAW_STATE_SCHEMA_VERSION = 22;
|
||||
export const OPENCLAW_SQLITE_BUSY_TIMEOUT_MS = 30_000;
|
||||
const OPENCLAW_STATE_DIR_MODE = 0o700;
|
||||
const OPENCLAW_STATE_FILE_MODE = 0o600;
|
||||
@@ -457,6 +457,49 @@ function migrateCurrentConversationBindingsFromKv(db: DatabaseSync): void {
|
||||
db.prepare("DELETE FROM kv WHERE scope = 'current-conversation-bindings'").run();
|
||||
}
|
||||
|
||||
function migrateTuiLastSessionsFromKv(db: DatabaseSync): void {
|
||||
const legacyRows = db
|
||||
.prepare("SELECT key, value_json FROM kv WHERE scope = 'tui:last-session'")
|
||||
.all() as Array<{ key?: unknown; value_json?: unknown }>;
|
||||
if (legacyRows.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const insert = db.prepare(`
|
||||
INSERT OR REPLACE INTO tui_last_sessions (
|
||||
scope_key,
|
||||
session_key,
|
||||
updated_at
|
||||
)
|
||||
VALUES (?, ?, ?)
|
||||
`);
|
||||
|
||||
for (const row of legacyRows) {
|
||||
if (typeof row.key !== "string" || typeof row.value_json !== "string") {
|
||||
continue;
|
||||
}
|
||||
let parsed: unknown;
|
||||
try {
|
||||
parsed = JSON.parse(row.value_json);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
||||
continue;
|
||||
}
|
||||
const record = parsed as Record<string, unknown>;
|
||||
const scopeKey = row.key.trim();
|
||||
const sessionKey = readString(record.sessionKey);
|
||||
const updatedAt = readFiniteNumber(record.updatedAt);
|
||||
if (!scopeKey || !sessionKey || updatedAt === null) {
|
||||
continue;
|
||||
}
|
||||
insert.run(scopeKey, sessionKey, updatedAt);
|
||||
}
|
||||
|
||||
db.prepare("DELETE FROM kv WHERE scope = 'tui:last-session'").run();
|
||||
}
|
||||
|
||||
function rebuildTaskDeliveryStateWithForeignKey(db: DatabaseSync): void {
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS task_delivery_state_next (
|
||||
@@ -539,6 +582,9 @@ function migrateStateSchema(db: DatabaseSync, fromVersion: number): void {
|
||||
if (fromVersion < 21) {
|
||||
migrateCurrentConversationBindingsFromKv(db);
|
||||
}
|
||||
if (fromVersion < 22) {
|
||||
migrateTuiLastSessionsFromKv(db);
|
||||
}
|
||||
}
|
||||
|
||||
function ensureSchema(db: DatabaseSync, pathname: string): void {
|
||||
|
||||
@@ -375,6 +375,15 @@ CREATE INDEX IF NOT EXISTS idx_current_conversation_bindings_target
|
||||
CREATE INDEX IF NOT EXISTS idx_current_conversation_bindings_expires
|
||||
ON current_conversation_bindings(expires_at, binding_key);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tui_last_sessions (
|
||||
scope_key TEXT NOT NULL PRIMARY KEY,
|
||||
session_key TEXT NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_tui_last_sessions_session_key
|
||||
ON tui_last_sessions(session_key, updated_at DESC, scope_key);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS task_delivery_state (
|
||||
task_id TEXT NOT NULL PRIMARY KEY,
|
||||
requester_origin_json TEXT,
|
||||
|
||||
@@ -370,6 +370,15 @@ CREATE INDEX IF NOT EXISTS idx_current_conversation_bindings_target
|
||||
CREATE INDEX IF NOT EXISTS idx_current_conversation_bindings_expires
|
||||
ON current_conversation_bindings(expires_at, binding_key);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tui_last_sessions (
|
||||
scope_key TEXT NOT NULL PRIMARY KEY,
|
||||
session_key TEXT NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_tui_last_sessions_session_key
|
||||
ON tui_last_sessions(session_key, updated_at DESC, scope_key);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS task_delivery_state (
|
||||
task_id TEXT NOT NULL PRIMARY KEY,
|
||||
requester_origin_json TEXT,
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import type { Insertable, Selectable } from "kysely";
|
||||
import { resolveStateDir } from "../config/paths.js";
|
||||
import { executeSqliteQuerySync, getNodeSqliteKysely } from "../infra/kysely-sync.js";
|
||||
import { privateFileStore } from "../infra/private-file-store.js";
|
||||
import { normalizeAgentId, parseAgentSessionKey } from "../routing/session-key.js";
|
||||
import type { DB as OpenClawStateKyselyDatabase } from "../state/openclaw-state-db.generated.js";
|
||||
import {
|
||||
deleteOpenClawStateKvJson,
|
||||
listOpenClawStateKvJson,
|
||||
readOpenClawStateKvJson,
|
||||
writeOpenClawStateKvJson,
|
||||
type OpenClawStateJsonValue,
|
||||
} from "../state/openclaw-state-kv.js";
|
||||
type OpenClawStateDatabaseOptions,
|
||||
openOpenClawStateDatabase,
|
||||
runOpenClawStateWriteTransaction,
|
||||
} from "../state/openclaw-state-db.js";
|
||||
import type { TuiSessionList } from "./tui-backend.js";
|
||||
import type { SessionScope } from "./tui-types.js";
|
||||
|
||||
@@ -20,7 +22,9 @@ type LastSessionRecord = {
|
||||
};
|
||||
|
||||
type LastSessionStore = Record<string, LastSessionRecord>;
|
||||
const TUI_LAST_SESSION_KV_SCOPE = "tui:last-session";
|
||||
type TuiLastSessionsTable = OpenClawStateKyselyDatabase["tui_last_sessions"];
|
||||
type TuiLastSessionRow = Selectable<TuiLastSessionsTable>;
|
||||
type TuiLastSessionDatabase = Pick<OpenClawStateKyselyDatabase, "tui_last_sessions">;
|
||||
|
||||
export function resolveLegacyTuiLastSessionStatePath(stateDir = resolveStateDir()): string {
|
||||
return path.join(stateDir, "tui", "last-session.json");
|
||||
@@ -56,7 +60,7 @@ async function deleteStore(filePath: string): Promise<void> {
|
||||
await fs.rm(filePath, { force: true });
|
||||
}
|
||||
|
||||
function stateKvOptionsForStateDir(stateDir?: string) {
|
||||
function sqliteOptionsForStateDir(stateDir?: string): OpenClawStateDatabaseOptions {
|
||||
return stateDir ? { env: { ...process.env, OPENCLAW_STATE_DIR: stateDir } } : {};
|
||||
}
|
||||
|
||||
@@ -73,17 +77,51 @@ function normalizeLastSessionRecord(value: unknown): LastSessionRecord | null {
|
||||
return { sessionKey, updatedAt };
|
||||
}
|
||||
|
||||
function writeTuiLastSessionKv(params: {
|
||||
function getTuiLastSessionKysely(db: DatabaseSync) {
|
||||
return getNodeSqliteKysely<TuiLastSessionDatabase>(db);
|
||||
}
|
||||
|
||||
function recordToRow(params: {
|
||||
scopeKey: string;
|
||||
record: LastSessionRecord;
|
||||
}): Insertable<TuiLastSessionsTable> {
|
||||
return {
|
||||
scope_key: params.scopeKey,
|
||||
session_key: params.record.sessionKey,
|
||||
updated_at: params.record.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
function rowToRecord(row: TuiLastSessionRow | undefined): LastSessionRecord | null {
|
||||
if (!row) {
|
||||
return null;
|
||||
}
|
||||
return normalizeLastSessionRecord({
|
||||
sessionKey: row.session_key,
|
||||
updatedAt: row.updated_at,
|
||||
});
|
||||
}
|
||||
|
||||
function writeTuiLastSessionRow(params: {
|
||||
scopeKey: string;
|
||||
record: LastSessionRecord;
|
||||
stateDir?: string;
|
||||
}): void {
|
||||
writeOpenClawStateKvJson<OpenClawStateJsonValue>(
|
||||
TUI_LAST_SESSION_KV_SCOPE,
|
||||
params.scopeKey,
|
||||
params.record,
|
||||
stateKvOptionsForStateDir(params.stateDir),
|
||||
);
|
||||
runOpenClawStateWriteTransaction((stateDatabase) => {
|
||||
const db = getTuiLastSessionKysely(stateDatabase.db);
|
||||
executeSqliteQuerySync(
|
||||
stateDatabase.db,
|
||||
db
|
||||
.insertInto("tui_last_sessions")
|
||||
.values(recordToRow(params))
|
||||
.onConflict((conflict) =>
|
||||
conflict.column("scope_key").doUpdateSet({
|
||||
session_key: (eb) => eb.ref("excluded.session_key"),
|
||||
updated_at: (eb) => eb.ref("excluded.updated_at"),
|
||||
}),
|
||||
),
|
||||
);
|
||||
}, sqliteOptionsForStateDir(params.stateDir));
|
||||
}
|
||||
|
||||
async function readLegacyTuiLastSessionStore(params: {
|
||||
@@ -123,7 +161,7 @@ export async function importLegacyTuiLastSessionStoreToSqlite(
|
||||
if (!record) {
|
||||
continue;
|
||||
}
|
||||
writeTuiLastSessionKv({
|
||||
writeTuiLastSessionRow({
|
||||
scopeKey,
|
||||
record,
|
||||
stateDir: params.stateDir,
|
||||
@@ -162,17 +200,13 @@ export async function readTuiLastSessionKey(params: {
|
||||
scopeKey: string;
|
||||
stateDir?: string;
|
||||
}): Promise<string | null> {
|
||||
const kvValue = readOpenClawStateKvJson(
|
||||
TUI_LAST_SESSION_KV_SCOPE,
|
||||
params.scopeKey,
|
||||
stateKvOptionsForStateDir(params.stateDir),
|
||||
);
|
||||
const kvRecord = normalizeLastSessionRecord(kvValue);
|
||||
if (kvRecord) {
|
||||
return kvRecord.sessionKey;
|
||||
}
|
||||
|
||||
return null;
|
||||
const stateDatabase = openOpenClawStateDatabase(sqliteOptionsForStateDir(params.stateDir));
|
||||
const db = getTuiLastSessionKysely(stateDatabase.db);
|
||||
const row = executeSqliteQuerySync<TuiLastSessionRow>(
|
||||
stateDatabase.db,
|
||||
db.selectFrom("tui_last_sessions").selectAll().where("scope_key", "=", params.scopeKey),
|
||||
).rows[0];
|
||||
return rowToRecord(row)?.sessionKey ?? null;
|
||||
}
|
||||
|
||||
export async function writeTuiLastSessionKey(params: {
|
||||
@@ -188,7 +222,7 @@ export async function writeTuiLastSessionKey(params: {
|
||||
sessionKey,
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
writeTuiLastSessionKv({
|
||||
writeTuiLastSessionRow({
|
||||
scopeKey: params.scopeKey,
|
||||
record,
|
||||
stateDir: params.stateDir,
|
||||
@@ -202,21 +236,17 @@ export async function clearTuiLastSessionPointers(params: {
|
||||
if (params.sessionKeys.size === 0) {
|
||||
return 0;
|
||||
}
|
||||
const removedScopeKeys = new Set<string>();
|
||||
const kvOptions = stateKvOptionsForStateDir(params.stateDir);
|
||||
for (const entry of listOpenClawStateKvJson<LastSessionRecord>(
|
||||
TUI_LAST_SESSION_KV_SCOPE,
|
||||
kvOptions,
|
||||
)) {
|
||||
const record = normalizeLastSessionRecord(entry.value);
|
||||
if (record && params.sessionKeys.has(record.sessionKey)) {
|
||||
if (deleteOpenClawStateKvJson(TUI_LAST_SESSION_KV_SCOPE, entry.key, kvOptions)) {
|
||||
removedScopeKeys.add(entry.key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return removedScopeKeys.size;
|
||||
let removed = 0;
|
||||
const sessionKeys = [...params.sessionKeys];
|
||||
runOpenClawStateWriteTransaction((stateDatabase) => {
|
||||
const db = getTuiLastSessionKysely(stateDatabase.db);
|
||||
const result = executeSqliteQuerySync(
|
||||
stateDatabase.db,
|
||||
db.deleteFrom("tui_last_sessions").where("session_key", "in", sessionKeys),
|
||||
);
|
||||
removed = Number(result.numAffectedRows ?? 0n);
|
||||
}, sqliteOptionsForStateDir(params.stateDir));
|
||||
return removed;
|
||||
}
|
||||
|
||||
export function resolveRememberedTuiSessionKey(params: {
|
||||
|
||||
Reference in New Issue
Block a user