Reset reconnect attempts only after a provider-ready connection stays stable, and cover both flap exhaustion and stable recovery.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(plugins): terminate git clone args so git: specs cannot inject options
A git: install spec whose base ends in .git or is scp-form was passed to
git clone as a bare positional. A spec beginning with a dash, such as
git:--upload-pack=/path/pwn.git, was parsed by git as an option rather than
a repository, reaching a command-execution primitive. The sibling
git switch --detach -- <ref> in the same files was already hardened; the
clone sites were missed.
Add a -- option terminator before the URL at both clone call sites
(installPluginFromGitSpec and installSkillFromSource) and reject a
leading-dash URL in isGitUrl as defense in depth.
* test(plugins): prove git option rejection boundary
Co-authored-by: yetval <yetvald@gmail.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(backup): write backup archive with owner-only 0o600 permissions
The published openclaw backup .tar.gz was created via createWriteStream
with no mode, so it landed at 0644 and world-readable. The archive
bundles auth.json OAuth access and refresh tokens, openclaw.json provider
API keys, and the state database, exposing all of them to any local user.
The intermediate SQLite snapshot is already chmod 0o600, but that
mitigation never reached the final artifact that actually matters. Create
the archive stream with mode 0o600 so the published backup is owner-only.
publishTempArchive preserves the source mode: fs.link shares the inode and
fs.copyFile copies the source mode, so the single write-site fix reaches
both publish paths.
* fix(backup): create archives owner-only
Co-authored-by: yetval <yetvald@gmail.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(browser): preserve strict CDP discovery policy on target-list lookup
findPageByTargetIdViaTargetList fetched the CDP /json/list endpoint without
passing an ssrfPolicy argument, so the caller's discovery policy was dropped at
the fetch layer while the sibling tryTerminateExecutionViaCdp scoped it. Build
the scoped control policy with scopeCdpPolicyToConfiguredEndpoint and pass it to
fetchJson, matching the sibling lookup path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(browser): tighten target-list CDP policy test comment
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(browser): consolidate target-list SSRF coverage
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* 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>