mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-03 14:31:40 +00:00
* refactor(transcripts): move meeting transcripts to sqlite * perf(transcripts): batch legacy utterance staging * fix(transcripts): report recovery moves on migration failure * fix(transcripts): refresh archive membership after recovery * fix(transcripts): omit failed summary export paths * fix(transcripts): type restore metadata tuple * fix(transcripts): align migration contract gates * fix(transcripts): verify case-aliased export ownership * fix(transcripts): allowlist doctor verifier sqlite query * fix(transcripts): preflight partial artifact recovery * fix(transcripts): stabilize exports and legacy path checks * fix(transcripts): resolve case-renamed doctor ownership * fix(transcripts): harden canonical export recovery * fix(transcripts): preserve pending import boundaries * refactor(transcripts): split migration insert transaction * perf(transcripts): query selected summary existence
71 lines
2.7 KiB
TypeScript
71 lines
2.7 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
import fs from "node:fs/promises";
|
|
import { writeExternalFileWithinRoot } from "../infra/fs-safe.js";
|
|
import { executeSqliteQuerySync, executeSqliteQueryTakeFirstSync } from "../infra/kysely-sync.js";
|
|
import {
|
|
openOpenClawStateDatabase,
|
|
type OpenClawStateDatabaseOptions,
|
|
} from "../state/openclaw-state-db.js";
|
|
import type { TranscriptSessionDescriptor } from "./provider-types.js";
|
|
import { ensureMeetingTranscriptsSchema } from "./sqlite-schema.js";
|
|
import { meetingTranscriptDb, utteranceFromRow } from "./store-sqlite.js";
|
|
|
|
const TRANSCRIPT_EXPORT_ROW_BATCH_SIZE = 64;
|
|
|
|
export async function writeTranscriptJsonlArtifact(params: {
|
|
sessionDir: string;
|
|
session: TranscriptSessionDescriptor;
|
|
databaseOptions: OpenClawStateDatabaseOptions;
|
|
}): Promise<string> {
|
|
ensureMeetingTranscriptsSchema(params.databaseOptions);
|
|
const database = openOpenClawStateDatabase(params.databaseOptions);
|
|
const sequenceHead = executeSqliteQueryTakeFirstSync(
|
|
database.db,
|
|
meetingTranscriptDb(database.db)
|
|
.selectFrom("meeting_transcript_sessions")
|
|
.select("next_utterance_seq")
|
|
.where("session_id", "=", params.session.sessionId)
|
|
.where("started_at", "=", params.session.startedAt),
|
|
)?.next_utterance_seq;
|
|
if (sequenceHead === undefined) {
|
|
throw new Error(`transcripts session not found: ${params.session.sessionId}`);
|
|
}
|
|
const digest = createHash("sha256");
|
|
await writeExternalFileWithinRoot({
|
|
rootDir: params.sessionDir,
|
|
path: "transcript.jsonl",
|
|
write: async (tempPath) => {
|
|
const handle = await fs.open(tempPath, "w", 0o600);
|
|
try {
|
|
let nextSequence = 0;
|
|
while (nextSequence < sequenceHead) {
|
|
const rows = executeSqliteQuerySync(
|
|
database.db,
|
|
meetingTranscriptDb(database.db)
|
|
.selectFrom("meeting_transcript_utterances")
|
|
.selectAll()
|
|
.where("session_id", "=", params.session.sessionId)
|
|
.where("session_started_at", "=", params.session.startedAt)
|
|
.where("sequence", ">=", nextSequence)
|
|
.where("sequence", "<", sequenceHead)
|
|
.orderBy("sequence", "asc")
|
|
.limit(TRANSCRIPT_EXPORT_ROW_BATCH_SIZE),
|
|
).rows;
|
|
if (rows.length === 0) {
|
|
break;
|
|
}
|
|
nextSequence = rows.at(-1)!.sequence + 1;
|
|
const lines = rows.map((row) => `${JSON.stringify(utteranceFromRow(row))}\n`);
|
|
for (const line of lines) {
|
|
await handle.writeFile(line);
|
|
digest.update(line);
|
|
}
|
|
}
|
|
} finally {
|
|
await handle.close();
|
|
}
|
|
},
|
|
});
|
|
return digest.digest("hex");
|
|
}
|