Synthetic API errors were carrying only English display text, so desktop chat could not distinguish our compatibility guidance from raw upstream failures and recovery logic depended on those English strings.
Add stable business error codes for media, PDF, request-size, prompt-length, and auto-mode errors. Desktop chat now resolves those codes through locale keys and suppresses the stale English fallback for known business errors, while normalizeMessagesForAPI still supports legacy text matching for old transcripts.
Constraint: Raw upstream provider details must remain available for diagnostics but should not drive localized business UX.
Rejected: Localize src/services/api/errors.ts strings directly | that would make persisted transcript text and media recovery depend on the active UI language.
Confidence: high
Scope-risk: moderate
Directive: Add new user-facing synthetic API errors with businessErrorCode values before localizing their display text.
Tested: bun test src/services/api/errors.test.ts tests/mediaRecoveryAndEstimation.test.ts src/server/__tests__/ws-memory-events.test.ts
Tested: cd desktop && bun run test -- run src/stores/chatStore.test.ts src/components/chat/MessageList.test.tsx
Tested: bun run check:server
Tested: bun run check:desktop
Not-tested: Manual desktop screenshot smoke for the rendered Chinese error card
Unsupported image rejections from text-only compatible providers should not
poison the session, and low-trust multimodal usage spikes should not make
context indicators report a full window.
Constraint: Third-party Anthropic-compatible providers may report encoded media bytes as usage tokens.
Rejected: Trust all provider usage uniformly | third-party media responses can pin context to 100% incorrectly.
Confidence: high
Scope-risk: moderate
Directive: Do not remove the media-aware fallback without checking text-only provider recovery and desktop context indicators.
Tested: bun run check:server
Tested: focused media/context regression suite
When the model streams `tool_use ... text ... tool_use` interleaved blocks
within one turn (e.g. parallel TaskCreate calls plus a mid-stream
explanation), the previous merge logic preserved the literal stream order
and persisted it to history. On the next request, Bedrock's stricter
history validator inspects the trailing run of `tool_use` blocks: text
between tool_uses makes the earlier tool_uses no longer count as
"trailing", so they are reported as missing tool_result and the API 400s
even though all tool_results were actually returned. See
CLIENT_TOOL_USE_BUG_REPORT.md from the worldRouter proxy team for the
captured request body and Bedrock error.
This fix:
- Adds `reorderAssistantToolUseBlocks` that moves non-tool_use blocks out
of the tool_use cluster (preserving tool_use id order, thinking/
redacted_thinking positions, and dropping no blocks). No-op when zero
or one tool_use, or when tool_uses are already contiguous.
- Applies it in `mergeAssistantMessages` so live stream-merge produces
clean history.
- Applies it as a pass in `normalizeMessagesForAPI` so sessions resumed
from disk that were persisted before this fix also get cleaned up.
- Adds 9 bun:test cases covering the no-op, hoist, head/tail
preservation, idempotence, and merge cases (including the exact
pattern from the upstream bug report).
This captures the pending worktree fixes before applying them to the
current local main. The changes tighten IM adapter path and credential
handling, preserve retry behavior for failed desktop notifications, and
make Azure/OpenAI provider auth and stop reasons reflect actual runtime
state.
Constraint: Worktree was detached from an older local main with pending uncommitted fixes
Rejected: Merge the detached HEAD directly | would also replay unrelated stale history
Rejected: Leave notification dedupe as fire-and-forget | failed sends consumed retry keys
Confidence: high
Scope-risk: broad
Directive: Keep adapter absolute-path matching constrained to configured work roots
Tested: git diff --check
Not-tested: full quality gate before local main integration