Carry conversations into new sessions with a visible summary

- Bots get continue_in_new_session: the bot writes a title and summary,
  the session closes for good (read-only) and the new one opens with the
  summary as a "Carried over from the last session" message that links
  back to the old session.
- Continue in new session (conversation menu, sessions modal) and the
  automatic rollover at the session length do the same; New session now
  starts a fresh, unlinked session.
- Session length is an app setting in Settings > General.
- ?session_id= opens a session; a carried-over one shows a bar linking to
  where the conversation went on instead of the composer.
- The thread can open the previous session above the current one (shown
  only, never in the prompt), and replies get search_past_sessions and
  read_past_session.
- Sessions are named from their opening messages, not their latest, and
  a carried summary is labelled as the earlier session's.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-26 05:35:52 +01:00
co-authored by Claude Opus 5.5
parent 1e6833b5d8
commit cc109bc361
64 changed files with 2074 additions and 128 deletions
@@ -0,0 +1,84 @@
import type { BUN_SQLITE_COBBLE_BOT_MESSAGES } from "@/db/types/db";
import { AppData } from "@/src/data/app-data";
import { SessionContinuePrompt } from "@/src/data/harness-prompts";
import parseSessionName from "@/src/bots-queue/parse-session-name";
import sessionNameTranscript from "@/src/bots-queue/session-name-transcript";
import generateText from "@/src/harness/text/generate-text";
import { runWithHarnessUsageScope } from "@/src/harness/usage/harness-usage-scope";
import AppError from "@/src/utils/app-error";
import grabBotMessages from "../bots/grab-bot-messages";
import carryOverBotSession from "./carry-over-bot-session";
import grabActiveBotSession from "./grab-active-bot-session";
import hasOpenSessionJob from "./has-open-session-job";
type Params = {
user_id: number | string;
bot_id: number;
workspace_id?: number | null;
};
/**
* Continue in new session, asked by the user from the conversation's
* menu (and the automatic rollover, `rollOverBotSession`): does what the
* bot does with `continue_in_new_session`. The bot's
* text model writes the new session's title and the summary from the
* session's latest messages, then `carryOverBotSession` closes this
* session for good and opens the new one with the summary. Refused while
* the bot is still working in the session, since its answer would land in
* the closed one. Returns the summary's message, for the caller to push.
*/
export default async function continueBotSession({
user_id,
bot_id,
workspace_id,
}: Params): Promise<BUN_SQLITE_COBBLE_BOT_MESSAGES> {
const active = await grabActiveBotSession({ user_id, bot_id, workspace_id });
if (!active?.id) throw new AppError(`This conversation has no session to continue from yet.`);
if (await hasOpenSessionJob({ session_id: active.id })) {
throw new AppError(`The bot is still working in this session. Wait for it to finish, or stop it, then continue.`);
}
const messages = await grabBotMessages({
session_id: active.id,
limit: AppData.BotSessionSummaryMessages,
});
if (!messages.length) throw new AppError(`This session has no messages yet: it's already fresh.`);
const result = await runWithHarnessUsageScope({
scope: {
user_id,
bot_id,
workspace_id: workspace_id || null,
session_id: active.id,
job_id: null,
message_id: null,
},
task: () =>
generateText({
messages: [
{ role: "system", content: SessionContinuePrompt },
{ role: "user", content: sessionNameTranscript({ messages }) },
],
bot_id,
workspace_id,
}),
});
const written = parseSessionName({ text: result.text });
if (!written?.summary) {
throw new AppError(`Couldn't write the summary to carry over. Try again.`);
}
return await carryOverBotSession({
session_id: active.id,
user_id,
bot_id,
workspace_id,
summary: written.summary,
title: written.title,
});
}