3.4 KiB
Session Assist
Server-side watcher that generates a short recap of the agent's last reply
and one suggested user follow-up with the small model
(lib/small-model), storing both on the session's metadata under
metadata.openchamber.assist.
Flow
createSessionAssistRuntimeis a consumer of the server's global SSE fan-out (index.js→onPayload), riding the same upstream connection as notifications. Purely event-driven — dormant sessions never generate anything, there is no backfill and no session scanning.session.status: idlearms a 60-second per-session timer; anybusy/retrystatus or a usermessage.updatedclears it (the "1 minute of quiet" rule).- On fire: fetch the session (skip sub-agent sessions with
parentID), take the LAST exchange only — the final assistant reply plus the user message it answered (assistantparentID→ user id) — and callgenerateSmallModelTextwith the session's own provider/model taken from the last assistant message — so the utility call spends the same subscription as the conversation.restrictToPreferredProviderforbids the resolver's global fallback: conversation content never goes to a provider the user didn't pick for the session, unless the small model was chosen explicitly (settings override or opencode config). A resolver 404 is silently skipped. - The requested JSON fields (
recap,suggestion, or both) are clamped and PATCHed onto the session metadata together withforMessageID(the last assistant message id) andgeneratedAt. Before writing, the session tail is re-checked (a stale result is dropped) and the metadata is merged from a fresh session read so concurrent metadata writes made during generation are preserved.
Settings gate
sessionRecapEnabled and sessionSuggestionEnabled in OpenChamber settings
(Settings → Chat, default on) are hard generation switches checked at fire
time. When both are off, no small-model calls run and nothing is written. When
one is on, the runtime still makes at most one small-model call and asks only
for that field. The UI also hides disabled payload types immediately.
Freshness contract (no clearing writes)
Clients do not need the payload to be deleted: they render it only while
assist.forMessageID still equals the session's last assistant message id
(and the session is idle). Any new message invalidates the payload
everywhere instantly and offline; the next idle cycle overwrites it.
UI consumers (packages/ui)
lib/sessionAssistMetadata.ts— payload parsing.hooks/useSessionAssist.ts— freshness gating + the 1-minute quiet window for the recap (single timeout to the boundary, no polling).components/chat/SessionRecapSpacer.tsx— renders the recap inside the fixed-height reserved gap under the last message (height never changes).components/chat/SessionSuggestionChip.tsx— one tappable suggestion chip near the composer (desktop chips row + above the mobile pill); hidden as soon as the composer has any content. Tap fills the input, never sends.
Limitations
- The watcher lives in the web server, so VS Code (extension-only, no web
server) does not generate assists; it still renders payloads produced by a
web/desktop instance of the same OpenCode server via
session.updated. - Metadata payloads ride every
session.updatedevent — keep the clamps (RECAP_CHAR_LIMIT,SUGGESTION_CHAR_LIMIT) small.