* fix(ollama): use truncateUtf16Safe for malformed NDJSON log warning
The parseNdjsonStream function uses naive .slice(0, 120) on malformed
NDJSON data in log warnings which can split surrogate pairs. Replace
with truncateUtf16Safe().
* test(ollama): cover UTF-16-safe NDJSON warnings
* test(ollama): satisfy lint in NDJSON coverage
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(parallel): use truncateUtf16Safe for MCP error JSON truncation
The extractMcpToolPayload function and error messages use naive
.slice(0, 500) on JSON-serialized errors and results which can split
surrogate pairs. Replace with truncateUtf16Safe().
* test(parallel): cover UTF-16-safe MCP errors
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(telegram): use truncateUtf16Safe for raw update log truncation
* refactor(telegram): own raw log bounds in formatter
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(active-memory): use truncateUtf16Safe for log value truncation
* fix(active-memory): use truncateUtf16Safe for log value truncation
* test(active-memory): cover UTF-16-safe log limits
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(tts-local-cli): handle stdout/stderr stream errors in speech provider
CLI TTS speech provider spawns a child process to generate audio and
registers stdout/stderr data listeners but omits stream error handlers.
stdout carries synthesized audio data. A pipe error mid-generation must
reject the promise so the caller does not silently receive truncated
audio when the child later exits zero. stderr carries diagnostic logs
only — errors there are benign and should not crash the provider.
Apply separate strategies matching vincentkoc's requirement:
- stdout (audio): error → reject(Promise) — surface the failure
- stderr (diagnostic): error → ignore — does not affect audio output
* fix lint: remove unused signal param from mock kill()
* fix(tts-local-cli): contain child stream failures
Co-authored-by: 赵旺0668001248 <zhao.wang1@xydigit.com>
* chore(tts-local-cli): keep release notes in PR body
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(diagnostics-prometheus): use truncateUtf16Safe for error message truncation
The safeErrorMessage function uses naive .slice(0, 500) on error messages
which can split surrogate pairs. Replace with truncateUtf16Safe().
* test(diagnostics-prometheus): cover UTF-16-safe errors
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(codex-supervisor): use truncateUtf16Safe for stderr tail truncation
StdioCodexJsonRpcConnection includes stderr output in the transport-closed
error message with a naive .slice(0, 1200) which can split surrogate pairs.
Replace with truncateUtf16Safe().
* test(codex-supervisor): cover UTF-16-safe stderr tails
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* 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(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>
* 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>
* feat(stepfun): add step-3.7-flash model and make it default
* fix(stepfun): set step-3.7-flash cacheRead cost to 0.04
* fix(stepfun): raise step-3.7-flash maxTokens to context limit
Output was artificially capped at 65536; StepFun docs set max_tokens
default to INF, bounded only by the 256K context window. Raise
step-3.7-flash maxTokens to 262144 on both stepfun and stepfun-plan,
matching contextWindow. Per models.dev anomalyco/models.dev#1903.
step-3.5-flash entries unchanged.
* fix(stepfun): zero step-plan step-3.7-flash cost for plan billing
Step Plan endpoint bills per subscription plan, not per token, matching
the sibling stepfun-plan/step-3.5-flash and step-3.5-flash-2603 entries
which already carry zero cost. Standard stepfun/step-3.7-flash keeps its
per-token rate. Per models.dev anomalyco/models.dev#1903, whose step-plan
providers omit cost entirely.
* docs(changelog): note StepFun 3.7 support
* style(stepfun): format provider table
* fix(stepfun): forward reasoning effort
* fix(stepfun): scope reasoning compat to 3.7
* test(stepfun): keep 3.5 reasoning compat unchanged
* fix(stepfun): carry off fallback through agent turns
* fix(stepfun): use documented token limit field
* fix(stepfun): match chat completions contract
* chore(stepfun): keep changelog release-owned
---------
Co-authored-by: Tianning Li <litianning@stepfun.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* [AI] fix(memory): use truncateUtf16Safe for dreaming snippet truncation
Replace .slice(0, N) with truncateUtf16Safe() at 5 call sites in
dreaming-phases.ts so complex emoji and surrogate pairs near the
truncation boundary are not split into lone surrogates.
truncateUtf16Safe is the standard SDK helper, already used in
session-cost-usage, cron, exec-approval, and node-host modules
for the same purpose.
* [AI] fix(memory): use SDK facade import for truncateUtf16Safe, add surrogate-proof test
Replace direct @openclaw/normalization-core/utf16-slice import with
openclaw/plugin-sdk/memory-core-host-engine-foundation SDK facade,
matching manager-search.ts in the same extension.
Add surrogate-proof.test.ts that runs a full dreaming ingestion sweep
with emoji at the 280-char boundary and verifies zero lone surrogates.
* [AI] fix(memory-proof): use relative imports in surrogate-proof test
* [AI] fix(memory-proof): place emoji at post-prefix 280-char boundary, assert session corpus file
* [AI] fix(memory-proof): avoid unnecessary String callback in oxlint
* [AI] fix(memory-proof): add curly braces per eslint curly rule
* [AI] feat(proof): add standalone dreaming surrogate truncation proof script
* [AI] chore(proof): remove unused SQLite-dependent proof script
* [AI] fix(proof): correct emoji boundary placement for all 5 call sites
* [AI] fix(proof): add curly braces per eslint curly rule in proof script
* [AI] fix(proof): session emoji at actual corpus 280-char boundary
* test: streamline memory dreaming UTF-16 coverage
---------
Co-authored-by: hailory <hailory@xydigit.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>