mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-17 02:37:33 +00:00
refactor: remove transcript file mapping table
This commit is contained in:
@@ -103,16 +103,16 @@ The branch already has a real shared SQLite base:
|
||||
file-to-database import remains in doctor code, and branch-local
|
||||
database upgrade helpers have been deleted.
|
||||
- Relational ownership is enforced where the ownership boundary is canonical:
|
||||
transcript-file mappings cascade from `agent_databases`, source migration
|
||||
rows cascade from `migration_runs`, task delivery state cascades from
|
||||
`task_runs`, and transcript identity rows cascade from transcript events.
|
||||
source migration rows cascade from `migration_runs`, task delivery state
|
||||
cascades from `task_runs`, and transcript identity rows cascade from
|
||||
transcript events.
|
||||
- Current shared tables include `kv`, `agents`, `agent_databases`,
|
||||
`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`,
|
||||
`tui_last_sessions`, `task_runs`, `task_delivery_state`, `flow_runs`,
|
||||
`subagent_runs`, `migration_runs`, and `backup_runs`.
|
||||
`plugin_state_entries`, `plugin_blob_entries`, `capture_sessions`,
|
||||
`capture_events`, `capture_blobs`, `sandbox_registry_entries`,
|
||||
`cron_run_logs`, `cron_jobs`, `commitments`, `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
|
||||
|
||||
@@ -364,7 +364,6 @@ kv(scope, key, value_json, updated_at)
|
||||
agents(agent_id, config_json, created_at, updated_at)
|
||||
session_entries(agent_id, session_key, entry_json, updated_at)
|
||||
transcript_events(agent_id, session_id, seq, event_json, created_at)
|
||||
transcript_files(agent_id, session_id, path, imported_at, exported_at)
|
||||
vfs_entries(agent_id, namespace, path, kind, content_blob, metadata_json, updated_at)
|
||||
tool_artifacts(agent_id, run_id, artifact_id, kind, metadata_json, blob, created_at)
|
||||
```
|
||||
|
||||
@@ -83,9 +83,9 @@ Per agent, on the Gateway host:
|
||||
sources after durable verification. Gateway startup leaves legacy indexes
|
||||
alone.
|
||||
- Transcripts: runtime transcript events live in the per-agent database
|
||||
(`transcript_events` and `transcript_event_identities`). The global
|
||||
`transcript_files` table maps legacy/export/debug path-shaped locators to
|
||||
`{ agentId, sessionId }`; JSONL files are not runtime sidecars.
|
||||
(`transcript_events` and `transcript_event_identities`). Session file values
|
||||
are canonical `sqlite-transcript://<agentId>/<sessionId>.jsonl` locators;
|
||||
JSONL files are doctor migration inputs, not runtime sidecars.
|
||||
- Telegram topic handles: `.../<sessionId>-topic-<threadId>.jsonl`
|
||||
|
||||
OpenClaw resolves these via `src/config/sessions/*`.
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
type SessionEntry,
|
||||
} from "../../config/sessions.js";
|
||||
import {
|
||||
listSqliteSessionTranscriptFiles,
|
||||
listSqliteSessionTranscriptLocators,
|
||||
loadSqliteSessionTranscriptEvents,
|
||||
resolveSqliteSessionTranscriptScope,
|
||||
} from "../../config/sessions/transcript-store.sqlite.js";
|
||||
@@ -233,7 +233,7 @@ function resolveSqliteSessionTranscriptPath(params: {
|
||||
return undefined;
|
||||
}
|
||||
const agentId = params.sessionKey ? resolveAgentIdFromSessionKey(params.sessionKey) : undefined;
|
||||
const candidates = listSqliteSessionTranscriptFiles().filter(
|
||||
const candidates = listSqliteSessionTranscriptLocators().filter(
|
||||
(entry) => (!agentId || entry.agentId === agentId) && entry.sessionId === sessionId,
|
||||
);
|
||||
if (candidates.length === 0) {
|
||||
|
||||
@@ -6,10 +6,7 @@ import {
|
||||
closeOpenClawAgentDatabasesForTest,
|
||||
openOpenClawAgentDatabase,
|
||||
} from "../../state/openclaw-agent-db.js";
|
||||
import {
|
||||
closeOpenClawStateDatabaseForTest,
|
||||
openOpenClawStateDatabase,
|
||||
} from "../../state/openclaw-state-db.js";
|
||||
import { closeOpenClawStateDatabaseForTest } from "../../state/openclaw-state-db.js";
|
||||
import { createSqliteSessionTranscriptLocator } from "./paths.js";
|
||||
import {
|
||||
appendSqliteSessionTranscriptEvent,
|
||||
@@ -206,24 +203,6 @@ describe("SQLite session transcript store", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not write runtime transcript file mappings", () => {
|
||||
const stateDir = createTempDir();
|
||||
appendSqliteSessionTranscriptEvent({
|
||||
env: { OPENCLAW_STATE_DIR: stateDir },
|
||||
agentId: "main",
|
||||
sessionId: "session-1",
|
||||
transcriptPath: path.join(stateDir, "session.jsonl"),
|
||||
event: { type: "session", id: "session-1" },
|
||||
});
|
||||
|
||||
const stateDatabase = openOpenClawStateDatabase({
|
||||
env: { OPENCLAW_STATE_DIR: stateDir },
|
||||
});
|
||||
expect(
|
||||
stateDatabase.db.prepare("SELECT COUNT(*) AS count FROM transcript_files").get(),
|
||||
).toEqual({ count: 0 });
|
||||
});
|
||||
|
||||
it("deletes transcript snapshots with the transcript", () => {
|
||||
const stateDir = createTempDir();
|
||||
const env = { OPENCLAW_STATE_DIR: stateDir };
|
||||
|
||||
@@ -57,7 +57,7 @@ export type SqliteSessionTranscriptScope = {
|
||||
sessionId: string;
|
||||
};
|
||||
|
||||
export type SqliteSessionTranscriptFile = SqliteSessionTranscriptScope & {
|
||||
export type SqliteSessionTranscriptLocator = SqliteSessionTranscriptScope & {
|
||||
path: string;
|
||||
updatedAt: number;
|
||||
};
|
||||
@@ -116,6 +116,12 @@ function getAgentTranscriptKysely(db: import("node:sqlite").DatabaseSync) {
|
||||
return getNodeSqliteKysely<AgentTranscriptDatabase>(db);
|
||||
}
|
||||
|
||||
function openTranscriptAgentDatabase(
|
||||
options: SqliteSessionTranscriptStoreOptions,
|
||||
): OpenClawAgentDatabase {
|
||||
return openOpenClawAgentDatabase({ env: options.env, agentId: options.agentId });
|
||||
}
|
||||
|
||||
function bindTranscriptEvent(params: {
|
||||
sessionId: string;
|
||||
seq: number;
|
||||
@@ -265,9 +271,9 @@ export function resolveSqliteSessionTranscriptScope(
|
||||
};
|
||||
}
|
||||
|
||||
export function listSqliteSessionTranscriptFiles(
|
||||
export function listSqliteSessionTranscriptLocators(
|
||||
options: OpenClawStateDatabaseOptions = {},
|
||||
): SqliteSessionTranscriptFile[] {
|
||||
): SqliteSessionTranscriptLocator[] {
|
||||
return listSqliteSessionTranscripts(options).map((transcript) => ({
|
||||
agentId: transcript.agentId,
|
||||
sessionId: transcript.sessionId,
|
||||
@@ -352,7 +358,7 @@ export function getSqliteSessionTranscriptStats(
|
||||
options: SqliteSessionTranscriptStoreOptions,
|
||||
): Pick<SqliteSessionTranscript, "sessionId" | "updatedAt" | "eventCount"> | null {
|
||||
const { sessionId } = normalizeTranscriptScope(options);
|
||||
const database = openOpenClawAgentDatabase(options);
|
||||
const database = openTranscriptAgentDatabase(options);
|
||||
const row = executeSqliteQueryTakeFirstSync(
|
||||
database.db,
|
||||
getAgentTranscriptKysely(database.db)
|
||||
@@ -519,7 +525,7 @@ export function loadSqliteSessionTranscriptEvents(
|
||||
options: SqliteSessionTranscriptStoreOptions,
|
||||
): SqliteSessionTranscriptEvent[] {
|
||||
const { sessionId } = normalizeTranscriptScope(options);
|
||||
const database = openOpenClawAgentDatabase(options);
|
||||
const database = openTranscriptAgentDatabase(options);
|
||||
return executeSqliteQuerySync(
|
||||
database.db,
|
||||
getAgentTranscriptKysely(database.db)
|
||||
@@ -542,7 +548,7 @@ export function hasSqliteSessionTranscriptEvents(
|
||||
options: SqliteSessionTranscriptStoreOptions,
|
||||
): boolean {
|
||||
const { sessionId } = normalizeTranscriptScope(options);
|
||||
const database = openOpenClawAgentDatabase(options);
|
||||
const database = openTranscriptAgentDatabase(options);
|
||||
const row = executeSqliteQueryTakeFirstSync(
|
||||
database.db,
|
||||
getAgentTranscriptKysely(database.db)
|
||||
@@ -598,7 +604,7 @@ export function hasSqliteSessionTranscriptSnapshot(
|
||||
): boolean {
|
||||
const { sessionId } = normalizeTranscriptScope(options);
|
||||
const snapshotId = normalizeSessionId(options.snapshotId);
|
||||
const database = openOpenClawAgentDatabase(options);
|
||||
const database = openTranscriptAgentDatabase(options);
|
||||
const row = executeSqliteQueryTakeFirstSync(
|
||||
database.db,
|
||||
getAgentTranscriptKysely(database.db)
|
||||
|
||||
@@ -4,10 +4,7 @@ import path from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import * as transcriptEvents from "../../sessions/transcript-events.js";
|
||||
import { closeOpenClawAgentDatabasesForTest } from "../../state/openclaw-agent-db.js";
|
||||
import {
|
||||
closeOpenClawStateDatabaseForTest,
|
||||
openOpenClawStateDatabase,
|
||||
} from "../../state/openclaw-state-db.js";
|
||||
import { closeOpenClawStateDatabaseForTest } from "../../state/openclaw-state-db.js";
|
||||
import { createSqliteSessionTranscriptLocator } from "./paths.js";
|
||||
import { upsertSessionEntry } from "./store.js";
|
||||
import { useTempSessionsFixture } from "./test-helpers.js";
|
||||
@@ -632,11 +629,6 @@ describe("appendAssistantMessageToSessionTranscript", () => {
|
||||
}).map((entry) => entry.event as { type?: string; message?: unknown });
|
||||
expect(events.map((event) => event.type)).toEqual(["session", "message"]);
|
||||
|
||||
const stateDatabase = openOpenClawStateDatabase({ env });
|
||||
expect(
|
||||
stateDatabase.db.prepare("SELECT COUNT(*) AS count FROM transcript_files").get(),
|
||||
).toEqual({ count: 0 });
|
||||
|
||||
fs.rmSync(stateDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||
import { createSqliteSessionTranscriptLocator } from "../config/sessions/paths.js";
|
||||
import { replaceSqliteSessionTranscriptEvents } from "../config/sessions/transcript-store.sqlite.js";
|
||||
import { closeOpenClawStateDatabaseForTest } from "../state/openclaw-state-db.js";
|
||||
import { createSuiteTempRootTracker } from "../test-helpers/temp-dir.js";
|
||||
@@ -32,8 +33,8 @@ describe("session cost usage", () => {
|
||||
|
||||
const makeRoot = async (prefix: string): Promise<string> => await suiteRootTracker.make(prefix);
|
||||
|
||||
const sessionPath = (root: string, sessionId: string, agentId = "main") =>
|
||||
path.join(root, "agents", agentId, "sessions", `${sessionId}.jsonl`);
|
||||
const sessionPath = (_root: string, sessionId: string, agentId = "main") =>
|
||||
createSqliteSessionTranscriptLocator({ agentId, sessionId });
|
||||
|
||||
const writeTranscript = (params: {
|
||||
agentId?: string;
|
||||
@@ -41,11 +42,20 @@ describe("session cost usage", () => {
|
||||
transcriptPath?: string;
|
||||
events: unknown[];
|
||||
}) => {
|
||||
const eventTimestamp = params.events
|
||||
.map((event) =>
|
||||
event && typeof event === "object"
|
||||
? Date.parse(String((event as { timestamp?: unknown }).timestamp ?? ""))
|
||||
: NaN,
|
||||
)
|
||||
.find((value) => Number.isFinite(value));
|
||||
replaceSqliteSessionTranscriptEvents({
|
||||
agentId: params.agentId ?? "main",
|
||||
sessionId: params.sessionId,
|
||||
transcriptPath: params.transcriptPath,
|
||||
transcriptPath:
|
||||
params.transcriptPath ?? sessionPath("", params.sessionId, params.agentId ?? "main"),
|
||||
events: [{ type: "session", version: 1, id: params.sessionId }, ...params.events],
|
||||
...(eventTimestamp !== undefined ? { now: () => eventTimestamp } : {}),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -61,6 +71,14 @@ describe("session cost usage", () => {
|
||||
}) => ({
|
||||
type: "message",
|
||||
timestamp: params.timestamp,
|
||||
provider: params.provider ?? "openai",
|
||||
model: params.model ?? "gpt-5.4",
|
||||
usage: {
|
||||
input: params.input,
|
||||
output: params.output,
|
||||
totalTokens: params.totalTokens ?? params.input + params.output,
|
||||
...(params.cost === undefined ? {} : { cost: { total: params.cost } }),
|
||||
},
|
||||
message: {
|
||||
role: "assistant",
|
||||
provider: params.provider ?? "openai",
|
||||
|
||||
@@ -3,7 +3,7 @@ import { normalizeUsage } from "../agents/usage.js";
|
||||
import { stripInboundMetadata } from "../auto-reply/reply/strip-inbound-meta.js";
|
||||
import { createSqliteSessionTranscriptLocator } from "../config/sessions/paths.js";
|
||||
import {
|
||||
listSqliteSessionTranscriptFiles,
|
||||
listSqliteSessionTranscriptLocators,
|
||||
listSqliteSessionTranscripts,
|
||||
loadSqliteSessionTranscriptEvents,
|
||||
resolveSqliteSessionTranscriptScopeForPath,
|
||||
@@ -263,7 +263,7 @@ const applyCostTotal = (totals: CostUsageTotals, costTotal: number | undefined)
|
||||
};
|
||||
|
||||
function getRememberedTranscriptPath(agentId: string, sessionId: string): string | undefined {
|
||||
return listSqliteSessionTranscriptFiles().find(
|
||||
return listSqliteSessionTranscriptLocators().find(
|
||||
(entry) => entry.agentId === agentId && entry.sessionId === sessionId,
|
||||
)?.path;
|
||||
}
|
||||
|
||||
9
src/state/openclaw-state-db.generated.d.ts
vendored
9
src/state/openclaw-state-db.generated.d.ts
vendored
@@ -329,14 +329,6 @@ export interface TaskRuns {
|
||||
terminal_summary: string | null;
|
||||
}
|
||||
|
||||
export interface TranscriptFiles {
|
||||
agent_id: string;
|
||||
exported_at: number | null;
|
||||
imported_at: number | null;
|
||||
path: string;
|
||||
session_id: string;
|
||||
}
|
||||
|
||||
export interface TuiLastSessions {
|
||||
scope_key: string;
|
||||
session_key: string;
|
||||
@@ -368,6 +360,5 @@ export interface DB {
|
||||
subagent_runs: SubagentRuns;
|
||||
task_delivery_state: TaskDeliveryState;
|
||||
task_runs: TaskRuns;
|
||||
transcript_files: TranscriptFiles;
|
||||
tui_last_sessions: TuiLastSessions;
|
||||
}
|
||||
|
||||
@@ -188,22 +188,6 @@ CREATE INDEX IF NOT EXISTS idx_commitments_scope_due
|
||||
CREATE INDEX IF NOT EXISTS idx_commitments_status_due
|
||||
ON commitments(status, due_earliest_ms, due_latest_ms);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS transcript_files (
|
||||
agent_id TEXT NOT NULL,
|
||||
session_id TEXT NOT NULL,
|
||||
path TEXT NOT NULL,
|
||||
imported_at INTEGER,
|
||||
exported_at INTEGER,
|
||||
PRIMARY KEY (agent_id, session_id, path),
|
||||
FOREIGN KEY (agent_id) REFERENCES agent_databases(agent_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_transcript_files_path_updated
|
||||
ON transcript_files(path, imported_at DESC, exported_at DESC, agent_id, session_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_transcript_files_session_updated
|
||||
ON transcript_files(agent_id, session_id, imported_at DESC, exported_at DESC, path);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS cron_run_logs (
|
||||
store_key TEXT NOT NULL,
|
||||
job_id TEXT NOT NULL,
|
||||
|
||||
@@ -183,22 +183,6 @@ CREATE INDEX IF NOT EXISTS idx_commitments_scope_due
|
||||
CREATE INDEX IF NOT EXISTS idx_commitments_status_due
|
||||
ON commitments(status, due_earliest_ms, due_latest_ms);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS transcript_files (
|
||||
agent_id TEXT NOT NULL,
|
||||
session_id TEXT NOT NULL,
|
||||
path TEXT NOT NULL,
|
||||
imported_at INTEGER,
|
||||
exported_at INTEGER,
|
||||
PRIMARY KEY (agent_id, session_id, path),
|
||||
FOREIGN KEY (agent_id) REFERENCES agent_databases(agent_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_transcript_files_path_updated
|
||||
ON transcript_files(path, imported_at DESC, exported_at DESC, agent_id, session_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_transcript_files_session_updated
|
||||
ON transcript_files(agent_id, session_id, imported_at DESC, exported_at DESC, path);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS cron_run_logs (
|
||||
store_key TEXT NOT NULL,
|
||||
job_id TEXT NOT NULL,
|
||||
|
||||
Reference in New Issue
Block a user