* fix(chat): stop duplicating interim commentary before tool calls (issue #842) Interim commentary the agent narrates before each tool call rendered TWICE. Two mechanisms, both now covered: 1. The old tool.start orphan seal + message.complete's full text: when the complete payload repeats the commentary, the final bubble duplicated it. onMessageComplete now strips the sealed-orphan prefix (tracked in StreamingState) from the final text. 2. On-device capture + DB forensics (2026-08-08) exposed the real repeat offender: the app seals the RAW streamed text — which carries leading blank lines the model emits before its narration — while the backend persists a CLEANED copy (leading whitespace stripped). A transcript reload ~10s later merged the REST page, exact-content matching failed, and the cleaned REST copy rendered on top of the live bubble. sameLogicalMessage now compares trimmed content, so the sealed live bubble is covered by the REST row. Also: - session.interrupt seals the in-flight streaming message before clearing streaming state, so interrupting a commentary turn keeps the partial text - regression tests cover both turn shapes (commentary in the final row → stripped; absent → kept) and the whitespace-drift reload case * fix(chat): match tool rows by gateway call id, drop reasoning-echo dupes (#842) REST transcript rows carry tool_call_id; live WS bubbles keep tool_id from tool.start. Match tool rows on that 1:1 identity so MCP/web rows (stored as raw <untrusted_tool_result> text with no JSON key) dedupe during the post-turn transcript merge instead of ghosting after the final message. reasoning.available echo (narration text == message body, sent ~40ms before each tool.start) no longer overwrites the real reasoning trace, so narration bubbles don't render their own text twice (body + Reasoning card). Room v5 migration adds tool_call_id; regression tests use captured on-device payload shapes. * fix(chat): flush throttled tokens before orphan seal + prefix-safe dedupe (#842) The tool.start seal raced the 33ms throttled token flush: deltas landing just before the transition stayed in the buffer, so the sealed narration orphan came out truncated (on-device capture: orphan ended '...chickpea', backend persisted '...chickpea goodness:') and failed the logical-match against the clean REST row — the commentary ghosted on the next transcript sync. - ChatStreamingController.flushPendingTokens(): force-flushes the throttled TOKEN buffer on MessageStart/MessageComplete/MessageDone/ToolStart so the reducer seals the COMPLETE narration. - sameLogicalMessage: accept prefix-covering (both sides >=40 chars) so a residual seal drift can never re-add a substantial narration. - Regression tests: controller flush + truncated-orphan prefix match. --------- Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
1 parent
ab42e19b7f
commit
adde03a4de
14 files changed
+780
-38
No files matched your search
@@ -19,6 +19,7 @@ data class SessionMessage(
|
||||
val type: String? = null,
|
||||
val reasoning: JsonElement? = null,
|
||||
val reasoning_text: JsonElement? = null,
|
||||
val tool_call_id: String? = null,
|
||||
) {
|
||||
val timestampText: String?
|
||||
get() = (timestamp as? JsonPrimitive)?.content
|
||||
@@ -38,4 +39,7 @@ data class SessionMessage(
|
||||
null -> ""
|
||||
else -> r.toString()
|
||||
}
|
||||
|
||||
val toolCallId: String
|
||||
get() = tool_call_id.orEmpty()
|
||||
}
|
||||
Reference in new issue
Block a user