* fix(plugins): fall back to os.homedir() when HOME/OPENCLAW_HOME is empty
manifest-metadata-scan resolved the home directory with
`env.OPENCLAW_HOME ?? env.HOME ?? env.USERPROFILE ?? os.homedir()`. The nullish
coalescing operator only catches null/undefined, so an empty or whitespace home
variable (e.g. HOME="" in a stripped-down container/sandbox env) was kept as ""
and path.join("", ".openclaw") resolved to a RELATIVE ".openclaw" under the cwd.
Plugin discovery then read global extensions from the wrong directory.
Normalize each candidate with the already-imported normalizeOptionalString (which
returns undefined for empty/whitespace) so an empty value falls through to
os.homedir(), matching the canonical home resolver in src/infra/home-dir.ts.
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor(plugins): reuse canonical manifest paths
Co-authored-by: liyuanbin <li.yuanbin1@xydigit.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* [AI] fix(agents): emit model.failover diagnostic on normal model fallback transitions
Restructure observeFailedCandidate to emit the existing model.failover
diagnostic event for candidate run failures when an actual next fallback
candidate exists.
Previously emitFailoverEvent was only called in the cooldown lane-suspension
branch. Normal fallback transitions only reached the trajectory recorder
(onFallbackStep) and were invisible in OTel traces.
Key design decisions:
- Emit inside observeFailedCandidate so both LiveSessionModelSwitchError
and generic error paths are covered automatically
- Guard emission on nextCandidate to avoid false telemetry for single-model
runs or terminal candidate exhaustion (the cooldown suspension branch
already handles that case with suspended=true)
- Extract error reason via existing describeFailoverError helper
Related to #102015
Co-Authored-By: claude-4.6 <noreply@anthropic.com>
* [AI] test(agents): add regression tests for model.failover diagnostic emission
Covers three scenarios:
- Primary fails, fallback succeeds: verifies model.failover event payload
(from/to provider/model, reason, cascadeDepth, suspended)
- Single-model run: verifies no false event emission
- Exhausted chain: verifies only real transition emits, terminal does not
Related to #102015
Co-Authored-By: claude-4.6 <noreply@anthropic.com>
* test(agents): consolidate failover diagnostics coverage
* test(agents): satisfy fallback diagnostic gates
---------
Co-authored-by: claude-4.6 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(ui): align split-view toolbar segments with the pane edges they label
* fix(ui): track pane gutter and horizontal overflow in the split toolbar
* fix(ui): reset split toolbar scroll offset on mode flips and reserve docked terminal space
* fix(ui): scroll clipped split panes into view when their toolbar segment gains focus
* 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>
Corner radius is owned by themes; drop the browser-local multiplier that
scaled all --radius-* variables (added in #49436). Stale borderRadius
localStorage keys are ignored and dropped on next settings write.
* 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(ui): keep workboard capture text truncation UTF-16 safe
Replace raw .slice(0, N - 3) with truncateUtf16Safe in
clampSessionCaptureText and clampSessionCaptureTitle to prevent
lone surrogates when emoji cross the truncation boundary.
Completes the UTF-16 hardening started in #101685, which added the
import but only covered buildCardSessionLabel.
* test(ui): cover workboard UTF-16 boundaries
* docs(changelog): note Workboard UTF-16 fix
* docs(changelog): leave release notes to release flow
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(mac): unified Liquid Glass pairing approval panel
Replace the serial node/device pairing NSAlerts with one floating
approval panel. Both prompters feed request cards into a shared
PairingApprovalCenter; each card shows a hardware icon, platform and
model, app/core version, source IP, a copyable short id, request age,
a trust line (first connection / already-paired-id caution / repair
token rotation), and a friendly access summary with an elevated
warning for system.run-class commands. Requests resolved elsewhere
disappear live; Not Now snoozes without resolving (gateway TTL
applies) and the menu-bar pending line reopens the panel. Liquid
Glass surface on macOS 26+, material fallback on macOS 15. Deletes
the NSAlert + invisible-host-window machinery, dead node isRepair
handling, and text-only describe() body.
Closes#102535
* fix(mac): satisfy swiftformat conditionalAssignment and resync native i18n inventory
Session-scoped tool-result projection freeze (shrink-only, stable
fallback identities for ambiguous tool ids) and a sent-watermark so
late-resolving inbound media appends a new user turn instead of
rewriting an already-sent slot. Stops Anthropic prompt-cache tail
invalidation on busy/media-heavy embedded sessions.