compaction: live progress signal + user-message cap

COMPACTING event drives a 'Compacting context…' transient in the GUI.
Cap the compacted block's user-message list at 40 with an honest omitted count.
This commit is contained in:
Rohit C Prasad
2026-07-30 06:24:39 -07:00
parent b303823934
commit f9f51c97c6
9 changed files with 144 additions and 25 deletions
+16 -2
View File
@@ -172,6 +172,10 @@ export function App() {
const [mode, setMode] = useState("interactive");
const [connected, setConnected] = useState(false);
const [running, setRunning] = useState(false);
// Transient "Compacting context…" indicator (OPE-27): set by the `compacting` event,
// cleared by whatever the engine emits next — the summarizer call is otherwise a
// multi-second silent stall mid-turn.
const [compacting, setCompacting] = useState(false);
const [items, setItems] = useState<Item[]>([]);
const [streaming, setStreamingState] = useState("");
// Ref mirror of `streaming`: the WS handler closure is built once per socket and can't read
@@ -576,6 +580,9 @@ export function App() {
},
]);
};
// Any engine event after `compacting` means the summarizer finished (compacted /
// silent no-op / failure prompt) — the transient must never outlive it.
if (ev.type !== "compacting") setCompacting(false);
switch (ev.type) {
case "ready":
setConnected(true);
@@ -709,6 +716,9 @@ export function App() {
if (d.model) setModel(d.model);
setItems((p) => [...p, { kind: "notice", tone: "info", text: d.text || "Model switched" }]);
break;
case "compacting":
setCompacting(true);
break;
case "compacted":
// Auto-compaction marker (OPE-27): outbound-only — the transcript stays intact,
// this divider just shows where the model's memory was summarized.
@@ -1519,7 +1529,11 @@ export function App() {
<ThinkingBlock text={reasoningStream} live />
</div>
)}
{/* Compaction runs between provider turns (nothing streams during it), so
the transient takes over the waiting slot with a specific label. */}
{running && compacting && <WaitingForAgent label="Compacting context…" />}
{running &&
!compacting &&
!reasoningStream &&
(!streaming || streamMode(streaming, items, running) === "hold") &&
!lastItemIsAssistant(items) && <WaitingForAgent />}
@@ -1685,12 +1699,12 @@ function lastItemIsAssistant(items: Item[]): boolean {
return false;
}
function WaitingForAgent() {
function WaitingForAgent({ label }: { label?: string }) {
return (
<div className="waiting-transcript">
<div className="waiting-row" aria-live="polite">
<span className="waiting-spinner" />
<span>Waiting for agent...</span>
<span>{label || "Waiting for agent..."}</span>
</div>
</div>
);
+1
View File
@@ -18,6 +18,7 @@ export type EventType =
| "input_rejected"
| "interrupted"
| "model_changed"
| "compacting"
| "compacted"
| "turn_done";