* fix(config): use truncateUtf16Safe for allowed-values hint text
Replace naive .slice(0, N) with truncateUtf16Safe() to prevent
surrogate pair splitting in config validation hint messages
shown to users.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(config): prove UTF-16 allowed-value hint boundary
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(mcp-runtime): use truncateUtf16Safe for MCP metadata text truncation
Replace naive .slice(0, N) with truncateUtf16Safe() to prevent
surrogate pair splitting in MCP tool metadata descriptions shown
to users through the bundle MCP runtime catalog.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(mcp-runtime): cover UTF-16 metadata boundary
* style(mcp-runtime): keep imports grouped
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(msteams): lowercase content type so attachment classification is case-insensitive
normalizeContentType only trimmed its input. MIME types are case-insensitive
(RFC 2045), and MS Teams relay payloads (SharePoint, OneDrive, Bot Framework CDN)
routinely emit mixed-case values such as "Image/PNG" or
"Application/Vnd.Microsoft.Teams.File.Download.Info". Every downstream
comparison in the attachments module assumes a lowercased value
(startsWith("image/"), === "application/vnd.microsoft.teams.file.download.info",
startsWith("text/html")), so a mixed-case attachment was silently misclassified:
images became documents, HTML bodies were skipped, and file-download candidates
were not resolved.
Lowercase in normalizeContentType so all six call sites match. The sibling
inferPlaceholder in the same file already lowercases via
normalizeLowercaseStringOrEmpty, so this aligns the two paths.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(msteams): route HTML text extraction through normalizeContentType
extractTextFromHtmlAttachments guarded its loop with an exact
`attachment.contentType !== "text/html"` check, which is the same
case-sensitivity gap the rest of the PR closes via normalizeContentType.
A mixed-case "TEXT/HTML" attachment (common from Teams relays; MIME types are
case-insensitive per RFC 2045) was skipped, so a message whose only body text
lived in such an HTML attachment entered the agent path with empty text.
Route the guard through normalizeContentType so mixed-case HTML attachments
reach the body extractor. Export the function and add regression coverage for
mixed-case / whitespace-padded / object-content / non-HTML cases.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(msteams): preserve MIME parameter case
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
The Control UI sessions roster now leads with operator telemetry: overview
tiles (sessions / live / unread / tokens), per-kind row avatars with a live-run
dot, compact token values with context-usage meters (ok/warn/danger at
65%/85%), initial-load skeleton rows, and icon-led empty states. Stale token
snapshots render as ~approximations with a neutral meter and never drive
warning tones, matching the chat composer convention; the overview token sum
counts only known snapshots (~ when partial, n/a when none). Label chips
ellipsize instead of overflowing sticky columns; <=500px drops the avatar to
preserve the key column budget.
Closes#102517
* fix(agents): keep provider error detail truncation UTF-16 safe
Replace the raw detail.slice(0, limit - 1) in truncateErrorDetail with
truncateUtf16Safe so provider error previews do not emit lone surrogates
when an emoji falls on the truncation boundary.
Adds a regression test that places an emoji at the default truncation
boundary and asserts the formatted detail contains no lone surrogates.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(agents): prove provider error UTF-16 boundary
---------
Co-authored-by: chengzhichao-xydt <chengzhichao-xydt@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(talk): use truncateUtf16Safe for snippet text truncation
fast-context-runtime.ts normalizeSnippet and heartbeat-events-filter.ts
buildExecEventPrompt used raw .slice(0, N), which can produce lone
surrogates when truncation boundaries cross emoji surrogate pairs.
* test(talk): cover UTF-16 snippet boundary
---------
Co-authored-by: hailory <hailory@xydigit.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Peter Steinberger <peter@steipete.me>
The sidebar brand row (logo + wordmark) returned in 8295a12a83 but the
macOS-only titlebar mark from #100648 stayed, so the Mac app showed the
claw icon twice. Delete the sidebar-native-brand element and its
CSS gate; the sidebar brand row is the single brand surface and the
native titlebar strip holds only the traffic lights.
* fix(gateway): keep chat history display text truncation surrogate-safe
truncateChatHistoryText uses slice(0, N) to truncate chat history text
for the Control UI. When the truncation boundary falls inside a UTF-16
surrogate pair (emoji, CJK extended), the resulting string contains a
dangling surrogate that browsers render as U+FFFD (�).
Replace slice(0, maxChars) with truncateUtf16Safe(text, maxChars) so
the truncation point always falls on a complete code-point boundary.
* test(gateway): cover chat display UTF-16 boundary
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(tool-policy-audit): use truncateUtf16Safe for audit field truncation
Replace naive .slice(0, MAX) with truncateUtf16Safe() to prevent
surrogate pair splitting in tool policy audit log output.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(agents): cover audit UTF-16 boundary
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(agents): keep tool-result context guard truncation UTF-16 safe
Replace the raw text.slice(0, cutPoint) in truncateTextToBudget with
truncateUtf16Safe so oversized tool results do not emit lone surrogates
when an emoji falls on the truncation boundary.
Adds a regression test that places an emoji exactly at the legacy cut
point and asserts the truncated output contains no lone surrogates.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(agents): report surrogate-safe omitted count
---------
Co-authored-by: chengzhichao-xydt <chengzhichao-xydt@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(matrix): truncate inbound preview on UTF-16 code-point boundary
The matrix inbound verbose preview used bodyText.slice(0, 200) before logging.
When a supplementary-plane character (emoji, extended CJK) straddles the 200th
code unit, the slice splits its surrogate pair and emits a lone surrogate into
the verbose log line. Lone surrogates corrupt JSON log serialization, break
terminal rendering, and crash UTF-8-validating log shippers.
Use the shared truncateUtf16Safe helper (already used by the mattermost sibling
monitor after #101630) to keep complete surrogate pairs intact. No behavior
change for ASCII input; the preview is still capped at 200 code units.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(matrix): fix inbound-preview test for lint and lib target
- Avoid String.prototype.isWellFormed (ES2024 lib): use the encodeURIComponent
well-formedness probe instead, which works on the project's TS lib target.
- Replace literal string concatenation with template strings to satisfy the
no-useless-concat lint rule. No behavior change in the assertions.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(channels): keep inbound log previews UTF-16 safe
* test(line): type verbose preview fixtures
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>