fix(sync): settle completed turns and finished messages promptly
A lost or delayed turn-ending `session.idle` left the busy spinner up until the watchdog poll caught it (5-10s). An assistant `message.updated` carrying `time.completed` now schedules one status check for that session, and `streaming.ts` stops treating a completed trailing message as streaming. The check is deferred by 750ms and re-reads the session status when the timer fires, so the overwhelmingly common case — the turn's own `session.idle` arriving right behind the completed message — settles on its own and costs zero extra requests; only a session the store still believes busy spends a fetch. The poll shares the watchdog's in-flight directory guard, so the deferred check and the periodic poll cannot overlap on one directory. Status authority is unchanged: the monotonic pass never lowers status, and an authoritative resync runs only when the snapshot disagrees.
This commit is contained in:
@@ -16,13 +16,15 @@ import {
|
||||
const message = (id: string, role: "user" | "assistant"): Message => ({
|
||||
id,
|
||||
role,
|
||||
time: { created: 1 },
|
||||
} as unknown as Message)
|
||||
|
||||
const completedAssistantMessage = (id: string): Message => ({
|
||||
id,
|
||||
role: "assistant",
|
||||
time: { created: 1, completed: 100 },
|
||||
} as unknown as Message)
|
||||
const completedAssistantMessage = (id: string): Message => {
|
||||
const base = message(id, "assistant")
|
||||
// SAFETY: test fixture — the streaming reducers read only `id`, `role`, and
|
||||
// `time.completed`, which this literal provides.
|
||||
return { ...base, time: { created: 1, completed: 100 } } as Message
|
||||
}
|
||||
|
||||
const stateWithMessages = (messages: Message[], status: SessionStatus = { type: "busy" } as SessionStatus): State => ({
|
||||
...INITIAL_STATE,
|
||||
|
||||
Reference in New Issue
Block a user