diff options
| author | Adam Malczewski <[email protected]> | 2026-06-24 13:43:40 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-24 13:43:40 +0900 |
| commit | b58fb8373a1f7311cead23aa9a4d1fcd6927634f (patch) | |
| tree | 839e0e51a235ed5d9bb8d24ac0f367552c1d61ac /packages/kernel/src | |
| parent | d274567893ff3283878ac0dcafd51a0b127653d7 (diff) | |
| download | dispatch-b58fb8373a1f7311cead23aa9a4d1fcd6927634f.tar.gz dispatch-b58fb8373a1f7311cead23aa9a4d1fcd6927634f.zip | |
fix(broken-chat): read-time self-repair of unrecoverable chats
reconcile() only repaired orphaned tool-calls. Two other broken states made
chats uncontinuable, and load() had no parse-error guard:
- A trailing assistant message whose only chunk is 'error' (a failed-
generation marker) serializes to empty content -> provider rejects/empty
-> chat never continues. 6 of 140 production conversations were stuck.
- A tool-call whose input is a raw malformed-JSON string (model emitted
broken JSON) re-sent as OpenAI arguments -> provider 400s on every
continuation (the 77574596 break).
- load() JSON.parse had no try/catch -> one corrupt row bricked the chat.
Fix = read-time repair (no DB surgery; append-only preserved). reconcile
runs on every load() BEFORE any provider sees messages, so Layer 1
protects ALL providers.
Layer 1 (conversation-store reconcile): strip error chunks from assistant
messages + drop the now-empty error-only messages (safe: never followed by
a tool message); orphaned-tool-call synthesis unchanged; ReconcileReport
+2 additive counts. loadSince (FE reads) intentionally unreconciled so the
user still SEES the error. load() wraps JSON.parse in try/catch (skip
corrupt rows).
Layer 2 (openai-stream): serializeToolArguments ensures tool-call
arguments is always valid JSON (malformed string -> fallback object),
neutralizing already-stored malformed args.
Layer 2 equiv (../claude provider-anthropic): safeJson returns a valid
object fallback on parse failure, not the raw string. (Separate repo.)
Live-verified: reproduced 77574596's real broken tail in the dev DB;
POST /chat continued it cleanly (no 400, model replied) — the provider
accepted the reconciled history.
tsc -b EXIT 0, biome clean, 1453 vitest pass.
Diffstat (limited to 'packages/kernel/src')
0 files changed, 0 insertions, 0 deletions
