* fix(codex): use truncateUtf16Safe for tool transcript output truncation
Replace naive .slice(0, N) with truncateUtf16Safe() in event-projector
tool transcript output text to prevent surrogate pair splitting.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(codex): keep streamed output utf16-safe
* refactor(codex): use plugin sdk text runtime
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(oc-path): use truncateUtf16Safe for error message path truncation
Replace naive .slice(0, 80) with truncateUtf16Safe() at three
error-reporting call sites to prevent surrogate pair splitting
in oc:// path validation error messages.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(oc-path): cover utf16 error truncation
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(discord): use truncateUtf16Safe for deploy error body truncation
* test(discord): cover utf16 deploy errors
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(compaction): use truncateUtf16Safe for post-compaction context text
Replace naive .slice(0, maxContextChars) with truncateUtf16Safe()
to prevent surrogate pair splitting in post-compaction context
summaries sent to the model.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(compaction): prove context UTF-16 boundary
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* 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>