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
@@ -7,13 +7,28 @@ type Props = {
loading: boolean;
error?: string;
onLoad: () => void;
/**
* Defaults to "Show earlier messages".
*/
label?: string;
/**
* Loads only on a click, never as it scrolls into view.
*/
manual?: boolean;
};
/**
* The top of a thread with older messages: loads them as it scrolls into
* view, with a button for when it can't (or a load failed).
* view (unless `manual`), with a button for when it can't (or a load
* failed).
*/
export default function OlderMessagesLoader({ loading, error, onLoad }: Props) {
export default function OlderMessagesLoader({
loading,
error,
onLoad,
label = "Show earlier messages",
manual,
}: Props) {
const ref = useRef<HTMLDivElement>(null);
const load = useRef(onLoad);
@@ -22,7 +37,7 @@ export default function OlderMessagesLoader({ loading, error, onLoad }: Props) {
useEffect(() => {
const el = ref.current;
if (!el || error || typeof IntersectionObserver == "undefined") return;
if (!el || error || manual || typeof IntersectionObserver == "undefined") return;
const observer = new IntersectionObserver(
(entries) => {
@@ -34,7 +49,7 @@ export default function OlderMessagesLoader({ loading, error, onLoad }: Props) {
observer.observe(el);
return () => observer.disconnect();
}, [error, loading]);
}, [error, loading, manual]);
return (
<Stack
@@ -45,14 +60,14 @@ export default function OlderMessagesLoader({ loading, error, onLoad }: Props) {
<Loading size="smaller" />
) : (
<Button
title="Show earlier messages"
title={label}
type="button"
variant="ghost"
color="text"
onClick={onLoad}
className="text-[13px]! leading-[18px]! text-ink-secondary"
>
{error ? `${error} Try again` : "Show earlier messages"}
{error ? `${error} Try again` : label}
</Button>
)}
</Stack>