v1.11: opencode-style compaction port
- compaction.ts: usable/isOverflow/estimate/turns/select/buildPrompt/process
- compaction-prompt.ts: SUMMARY_TEMPLATE verbatim from opencode
- schema: messages.{compacted_at,summary,tail_start_id} + chats.needs_compaction
- inference: auto-trigger on overflow, pre-fetch compaction before next turn
- /compact slash command rewired to new path
- WS: chat_status working/idle around compaction + compacted frame
- frontend: SummaryCard + sonner toast on compacted
- 24 unit tests for pure functions
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import type { Message, WsFrame } from '@/api/types';
|
||||
import { api } from '@/api/client';
|
||||
import { sessionEvents } from './sessionEvents';
|
||||
|
||||
// session_renamed frame removed from WsFrame — it was declared but never
|
||||
@@ -161,6 +163,12 @@ function applyFrame(state: State, frame: WsFrame): State {
|
||||
: state.messages;
|
||||
return { ...state, messages: next, error: frame.error };
|
||||
}
|
||||
case 'compacted': {
|
||||
// v1.11: side effects (refetch + toast) live in ws.onmessage; the
|
||||
// reducer just no-ops so TS exhaustiveness is satisfied without
|
||||
// duplicating async work inside a synchronous reducer.
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,6 +204,25 @@ export function useSessionStream(sessionId: string | undefined) {
|
||||
ws.onmessage = (ev) => {
|
||||
try {
|
||||
const frame = JSON.parse(typeof ev.data === 'string' ? ev.data : '') as WsFrame;
|
||||
// v1.11: on a compaction completion, re-fetch the message list so
|
||||
// the new summary row + the cohort of compacted_at-stamped older
|
||||
// rows render correctly. We dispatch the fresh list as a synthetic
|
||||
// 'snapshot' frame so the reducer's existing path handles state
|
||||
// replacement (no need for a parallel "refetched" path).
|
||||
// The toast is purely UX feedback; missing it would still leave
|
||||
// the chat in a valid state.
|
||||
if (frame.type === 'compacted') {
|
||||
toast.success('Context compacted to free space');
|
||||
void api.messages
|
||||
.list(frame.session_id)
|
||||
.then((messages) => {
|
||||
setState((s) => applyFrame(s, { type: 'snapshot', messages }));
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
console.warn('compacted refetch failed', err);
|
||||
});
|
||||
return;
|
||||
}
|
||||
setState((s) => applyFrame(s, frame));
|
||||
} catch (err) {
|
||||
console.warn('bad ws frame', err);
|
||||
|
||||
Reference in New Issue
Block a user