Skip to content

Commit b109329

Browse files
committed
🤖 refactor: Clear TODO/status on user message, not stream events
## Problem Previous implementation cleared state at different times: - TODOs: Cleared on stream-end (via cleanupStreamState) - Status: Cleared on stream-start This created historical/live disparity because stream events aren't persisted in chat.jsonl. After page reload, TODOs would stick around (desirable!) but the implementation was inconsistent. ## Solution Clear BOTH todos and agentStatus when new user message arrives. Why user messages? - ✅ Persisted in chat.jsonl (unlike stream events) - ✅ Consistent live/historical behavior - ✅ Semantic: New question = new task = clear previous state ## Changes 1. Removed TODO clearing from cleanupStreamState() 2. Removed status clearing from handleStreamStart() 3. Added centralized clearing in handleMessage() when user message arrives 4. Updated test: 'stream-start' → 'new user message' ## Benefits - Consistent behavior whether loading from history or processing live - TODOs persist until next question (user preference!) - Simpler: One place to clear state, not scattered across handlers - Architecture: Relies on persisted data, not ephemeral events ## Testing - All 842 tests pass - Updated test reflects new clearing behavior - Typecheck and lint pass
1 parent 9eef8ae commit b109329

File tree

2 files changed

+24
-18
lines changed

2 files changed

+24
-18
lines changed

src/utils/messages/StreamingMessageAggregator.status.test.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ describe("StreamingMessageAggregator - Agent Status", () => {
199199
expect(aggregator.getAgentStatus()).toBeUndefined();
200200
});
201201

202-
it("should clear agent status on stream-start (different from TODO behavior)", () => {
202+
it("should clear agent status when new user message arrives", () => {
203203
const aggregator = new StreamingMessageAggregator("2024-01-01T00:00:00.000Z");
204204

205205
// Start first stream and set status
@@ -245,16 +245,16 @@ describe("StreamingMessageAggregator - Agent Status", () => {
245245
// Status persists after stream ends
246246
expect(aggregator.getAgentStatus()?.message).toBe("First task");
247247

248-
// Start a NEW stream - status should be cleared
249-
aggregator.handleStreamStart({
250-
type: "stream-start",
251-
workspaceId: "workspace1",
252-
messageId: "msg2",
253-
model: "test-model",
254-
historySequence: 2,
255-
});
248+
// User sends a NEW message - status should be cleared
249+
const newUserMessage = {
250+
id: "msg2",
251+
role: "user" as const,
252+
parts: [{ type: "text" as const, text: "What's next?" }],
253+
metadata: { timestamp: Date.now(), historySequence: 2 },
254+
};
255+
aggregator.handleMessage(newUserMessage);
256256

257-
// Status should be cleared on new stream start
257+
// Status should be cleared on new user message
258258
expect(aggregator.getAgentStatus()).toBeUndefined();
259259
});
260260

src/utils/messages/StreamingMessageAggregator.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -167,11 +167,12 @@ export class StreamingMessageAggregator {
167167
/**
168168
* Clean up stream-scoped state when stream ends (normally or abnormally).
169169
* Called by handleStreamEnd, handleStreamAbort, and handleStreamError.
170+
*
171+
* NOTE: Does NOT clear todos or agentStatus - those are cleared when a new
172+
* user message arrives (see handleMessage), ensuring consistent behavior
173+
* whether loading from history or processing live events.
170174
*/
171175
private cleanupStreamState(messageId: string): void {
172-
this.currentTodos = [];
173-
// NOTE: agentStatus is NOT cleared here - it persists after stream completion
174-
// to show the last activity. This is different from todos which are stream-scoped.
175176
this.activeStreams.delete(messageId);
176177
}
177178

@@ -311,10 +312,9 @@ export class StreamingMessageAggregator {
311312
// Clear pending stream start timestamp - stream has started
312313
this.setPendingStreamStartTime(null);
313314

314-
// Clear agent status on stream start (unlike todos which persist across streams).
315-
// Rationale: Status represents current activity, so it should be cleared and reset
316-
// for each new stream. Todos represent pending work, so they persist until completion.
317-
this.agentStatus = undefined;
315+
// NOTE: We do NOT clear agentStatus or currentTodos here.
316+
// They are cleared when a new user message arrives (see handleMessage),
317+
// ensuring consistent behavior whether loading from history or processing live events.
318318

319319
// Detect if this stream is compacting by checking if last user message is a compaction-request
320320
const messages = this.getAllMessages();
@@ -649,8 +649,14 @@ export class StreamingMessageAggregator {
649649
// Now add the new message
650650
this.addMessage(incomingMessage);
651651

652-
// If this is a user message, record timestamp for pending stream detection
652+
// If this is a user message, clear derived state and record timestamp
653653
if (incomingMessage.role === "user") {
654+
// Clear derived state (todos, agentStatus) for new conversation turn
655+
// This ensures consistent behavior whether loading from history or processing live events
656+
// since stream-start/stream-end events are not persisted in chat.jsonl
657+
this.currentTodos = [];
658+
this.agentStatus = undefined;
659+
654660
this.setPendingStreamStartTime(Date.now());
655661
}
656662
}

0 commit comments

Comments
 (0)