|
4 | 4 | **Author:** Claude Opus 4.6 + Dimitri Saridakis |
5 | 5 | **Created:** 2026-07-02 |
6 | 6 | **Telos:** 97361f814c5f54df |
7 | | -**Incorporates and replaces:** `session-state-machine.md` (Status: Proposed, never implemented), `streaming-input-session-control.md`. Crash recovery from `session-state-machine.md` Phase 4 is pulled forward to Phase 0 here. |
| 7 | +**Incorporates and replaces:** `session-state-machine.md` (never implemented) and supersedes `streaming-input-session-control.md` (implemented in PR #33, but scope expanded here). Crash recovery from `session-state-machine.md` Phase 4 is pulled forward to Phase 0 here. |
8 | 8 | **Related PRs:** #396 (never merged), #401 (merged, reverted via #403), #407 |
9 | 9 |
|
10 | 10 | ## Problem Statement |
@@ -152,6 +152,8 @@ type SessionStateEvent = { |
152 | 152 |
|
153 | 153 | The client binds UI to these events. No deriving, no guessing, no `SET_RUNNING` scattered across handlers. |
154 | 154 |
|
| 155 | +**DETACHED/SUSPENDED buffering:** When the server transitions to DETACHED or SUSPENDED (transport-level states invisible to the client), it must preserve the last emitted client-facing state and suppress emission. On recovery (DETACHED -> ACTIVE, SUSPENDED -> ACTIVE), the server re-emits the current client-facing state. Implementation: a `lastEmittedClientState` field per session, updated on each emission, checked before emitting on state transitions. |
| 156 | + |
155 | 157 | **Why 3 states, not 7:** The 7-state machine (CREATED/STARTING/ACTIVE/DETACHED/SUSPENDED/CLOSING/ENDED) is the server's internal lifecycle. The client doesn't need to know about DETACHED vs SUSPENDED -- those are transport concerns, not UI concerns. The client needs exactly: "am I waiting?" / "is it thinking?" / "does it need my input?". This mirrors Anthropic's SDK (`idle | running | requires_action`). |
156 | 158 |
|
157 | 159 | #### 3. Unify Server-Side State |
@@ -302,7 +304,7 @@ This is the same pattern used by every multi-device chat system. The EventStore |
302 | 304 |
|
303 | 305 | ### Phase 2: Always-send (remove client routing) |
304 | 306 |
|
305 | | -**Prerequisite:** Verify `clientMsgId` idempotency guard from PR #386 is still present and keyed correctly in `chat-rest-handler.ts`. If refactored or removed, re-implement before enabling retries. |
| 307 | +**Prerequisite:** Verify `clientMsgId` idempotency guard from PR #386 is still present and keyed correctly. The dedup logic lives in `server/chat.ts` (`storeAndEchoIfNew()` which calls `eventStore.hasUserMessage()`), not in `chat-rest-handler.ts` (which only passes `clientMsgId` through). If refactored or removed, re-implement before enabling retries. |
306 | 308 |
|
307 | 309 | 1. Remove `wasRunning` branch from `sendMessage()` |
308 | 310 | 2. Remove `pendingSend[]`, `PENDING_SEND_TIMEOUT_MS`, drain logic, timer, callbacks |
|
0 commit comments