Files
cobble/src/bots-queue/grab-session-carry-over.ts
T
tben 4d5195447b Name sessions with the model and keep the rail list live
Sessions no longer take their title from the first message: once the first exchange is sent, the text model writes the title and summary, which the rail and sessions list show. The summary records how many messages it covers so a stale one is rewritten before it is carried into the next session.

The sessions hook now reloads on live message and session events, so a session started by the first message, a rollover or another tab appears without a reload. New sessions are pushed as server:bot-session, both from the process that changed them and through the live relay.
2026-10-19 18:05:57 +01:00

92 lines
3.0 KiB
TypeScript

import BunSQLite from "@moduletrace/bun-sqlite";
import type { BUN_SQLITE_COBBLE_BOT_SESSIONS } from "@/db/types/db";
import { AppData } from "@/src/data/app-data";
import { PreviousSessionNote, SessionSummaryPrompt } from "@/src/data/harness-prompts";
import grabBotMessages from "@/src/functions/backend/db/bots/grab-bot-messages";
import generateText from "@/src/harness/text/generate-text";
type Params = {
session_id: number | string;
bot_id: number | string;
workspace_id?: number | string | null;
signal?: AbortSignal;
};
/**
* The previous session's summary for a session's prompt (markdown), or ""
* when it doesn't carry on from one. The summary written as the session
* went covers only its first exchange, so a closed session whose summary
* is stale (it covers fewer messages than the session ended with) is
* summarized again from its latest messages, and kept.
*/
export default async function grabSessionCarryOver({
session_id,
bot_id,
workspace_id,
signal,
}: Params) {
const previous = (
await BunSQLite.sql<BUN_SQLITE_COBBLE_BOT_SESSIONS & { message_count: number }>({
sql: `SELECT p.*,
(SELECT COUNT(*) FROM bot_messages m WHERE m.session_id = p.id) AS message_count
FROM bot_sessions s JOIN bot_sessions p ON p.id = s.previous_session_id
WHERE s.id = ? AND p.bot_id = s.bot_id LIMIT 1`,
values: [session_id],
})
).payload?.[0];
if (!previous?.id) return "";
const message_count = Number(previous.message_count) || 0;
if (
previous.summary?.trim() &&
Number(previous.summary_message_count) >= message_count
) {
return `${PreviousSessionNote}\n\n${previous.summary.trim()}`;
}
const messages = await grabBotMessages({
session_id: previous.id,
limit: AppData.BotSessionSummaryMessages,
});
if (!messages.length) return "";
const transcript = messages
.map((message) => `${message.author == "user" ? "User" : "You"}: ${message.text || "(files only)"}`)
.join("\n\n");
const result = await generateText({
messages: [
{ role: "system", content: SessionSummaryPrompt },
{ role: "user", content: transcript },
],
bot_id,
workspace_id,
signal,
}).catch((error: unknown) => {
if (signal?.aborted) throw error;
return null;
});
const summary = (result?.text || "").trim().slice(0, AppData.BotSessionSummaryMaxChars);
/**
* A model that can't rewrite it now must not lose what the earlier
* one already said.
*/
if (!summary) {
return previous.summary?.trim()
? `${PreviousSessionNote}\n\n${previous.summary.trim()}`
: "";
}
await BunSQLite.sql({
sql: `UPDATE bot_sessions SET summary = ?, summary_message_count = ?, updated_at = ? WHERE id = ?`,
values: [summary, message_count, Date.now(), previous.id],
});
return `${PreviousSessionNote}\n\n${summary}`;
}