Saved conversations: browse, snapshot, export (v1)
Saved conversations: browse, snapshot, export (v1)
The Chats page reads the original assistant tables and schema-15 subordinate choice metadata. It adds no runtime, permission or filesystem endpoint. List/read/export fields remain compatible; prospective creation and historical continuation follow the version-1 choices contract. The legacy list defaults to 50, ordered by updated timestamp then ID descending.
GET /api/assistant/sessions/browse accepts limit (default 50, 1–100),
before_id (positive SQLite 64-bit integer), through_id (nonnegative SQLite 64-bit integer) and q (up to
200 characters). Absent through_id chooses the current maximum session ID, or 0.
Rows sort by ID descending, with id <= through_id and optional id < before_id.
The response contains sessions, through_id, next_before_id (nullable) and
has_more, derived by requesting one extra row. Each row includes ID, displayed
title, stored runtime/model/timestamps and message count. q is a parameterized,
literal substring over the displayed title (New conversation for absent/empty
stored titles); %, _ and the escape character are escaped. SQLite folds ASCII
case; non-ASCII characters match literally. This freezes an ID ceiling, not row
contents, title membership or a historical total. Refresh includes newly created IDs.
GET /api/assistant/sessions/{id} preserves session, messages, totals and
running. The SQLite fields now share one read transaction, rolled back on exit.
A connection with pending writes is refused rather than committed or called persisted.
Additive snapshot fields: captured_at_utc, basis=persisted_messages_only,
message_count, last_message_id (null for no messages). Additive
activity_observation fields: observed_at_utc, turn_claimed, cli_running,
basis=in_memory_observation_not_atomic_with_sqlite. running still means the
registered CLI-process observation. None establishes historical completion.
Session/list/browse rows add choices (null for unknown historical settings or
{unreadable:true} for malformed metadata). Detail adds choices_version=1 and
turn_choices, read in the same SQLite transaction as messages and totals. Each turn
identifies its user message, optional persisted assistant message, immutable requested
choices and ordered literal runtime model reports. Absence of reports is not a model,
effort or completion claim. A corrupt cross-session reply link is marked unreadable.
GET /api/assistant/sessions/{id}/export?format=json|markdown returns a JSON envelope:
session_id, format, filename, content_type, text, and snapshot. Filenames
are resmon-chat-<id>.json or .md; MIME types are application/json and
text/markdown. Generated UTF-8 text may be at most 8 MiB per format. Larger output
returns 413 with an explicit refusal and no truncation. Missing sessions return 404;
invalid query parameters/format return 422. These reads do not require an available CLI.
Both serializers use the same snapshot helper. JSON version 1 contains an explicit
session allowlist (ID, stored title, runtime/model, created/updated strings), ordered
messages (ID, role, exact content, timestamp, nullable recorded tokens/cost), snapshot,
separately observed current activity, completion_status=unknown and limitations.
Each tool field is {data, unreadable}; absent storage has null data. Unparseable JSON
also carries raw, preserving the original stored text. No CLI coordination IDs,
settings, environment, config paths or unrelated chats are added as metadata. Saved
message/tool text is preserved even if it contains sensitive information. Review it
before sharing. Recorded cost is not a billing claim; null is not zero.
Markdown places every variable value inside a literal code fence longer than any backtick run in that value. This preserves text while preventing it from becoming active Markdown links or HTML. Framing is formatting, not file byte equality. Neither format includes live-only fragments, pending cards, inferred approval/effect history, current effort or a promise of final completion.
JSON remains version 1 with additive choices_version=1, choices and turn_choices.
Markdown adds these same public fields in a literal fenced section. The private route
comparison digest, configured locator and native session coordination ID are excluded
from generated choice metadata. All metadata counts toward the existing 8 MiB limit.
The renderer validates session/format/filename/type and JSON content identity before
requesting a Blob download. Changing selection invalidates a pending export. A download
request is not confirmation of a native save. Scripted Electron setSavePath checks
prove saved bytes separately from any human chooser observation.
Continue opens the same local session in the existing Ask provider without sending. Its first historical send requires explicit same-runtime-kind confirmation. API confirmation discloses sending saved user/assistant text to the chosen future provider; Claude confirmation discloses a fresh native session without earlier local messages. Previously bound choices stay fixed; a choice change starts a new empty conversation. One active turn owns its session synchronously in this renderer. Other chats remain browsable/readable/exportable; target changes/new chat/active-session deletion wait. Opening the active session preserves its stream and cards. Stop sends the existing cancel request and retains ownership until both that request and the stream reader settle. History refresh follows settlement. Late selection, creation, permission and stream callbacks cannot retarget a later selection. Another renderer’s active claim is an observation, not a reattachment feature; backend 409 remains visible.