* test: fix changed-run routing, dedupe gateway package lanes, repair force-rerun triggers
- route extensions/active-memory changed runs to their dedicated lane; add a
generic guard that every catch-all-excluded extension root resolves to a
dedicated config so future split lanes cannot silently skip changed tests
- exclude packages/gateway-client and packages/gateway-protocol from the unit
lane; explicit targets now route to the gateway-client lane (was double-run)
- generate forceRerunTriggers from the test/vitest directory and resolve all
entries absolute: Vitest matches triggers against absolute changed-file
paths, so the old hand-maintained relative list never matched at all
- replace deprecated envFile:false with envDir:false (Vitest 4.1.9 warning)
- give scripts/test-live.mjs stall detection teeth: kill the process group and
exit non-zero when OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS elapses quietly
- consolidate the live-docker shell target list into changed-lanes.mjs so the
lane regex and check targets cannot drift; cover subagent-announce edits
- delete orphans: vitest.extension-clickclack.config.ts,
vitest.full-core-unit.config.ts, scripts/test-docker-all.sh,
scripts/test-live-acp-spawn-defaults-docker.sh
Closes#104231
* test: keep envDir literal so sharedVitestConfig satisfies Vite UserConfig
Vite 8 types envDir as string | false; the bare object literal widened false to
boolean and broke check:test-types at the defineConfig call sites.
* fix(discord): stop retrying non-idempotent sends on post-connect-ambiguous errors
Discord's outbound retry runner treated every transient transport error as
retryable, including ECONNRESET, ETIMEDOUT, AbortError, and the undici
headers/body/socket timeouts. Those errors can fire after Discord has already
received and created the message but before the client reads the response, so
replaying the non-idempotent createChannelMessage POST delivers the same
message twice while reporting success.
Add a pre-connect-only classifier (isRetryableDiscordPreConnectError) covering
errors that provably never reached Discord (DNS/connect refused/connect
timeout) plus rate-limit rejections, mirroring the Telegram send-path
carve-out. The retry runner now accepts a per-call nonIdempotent option; the
message-create and thread-create call sites opt in so ambiguous post-connect
errors surface to the caller instead of double-sending. Idempotent REST calls
(reactions, edits, DM-channel lookup, uploads) keep the broader transient set.
* fix(discord): retry nonce-enforced message creates
* fix(discord): scope create retries by endpoint
* fix(discord): stabilize sticker and poll create nonce across retries
* fix(discord): tighten create retry safety
* fix(discord): preserve nonce-safe retries
* fix(discord): scope nonce fields to message creates
* test(discord): cover nonce-safe direct sends
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat: sidebar update card (web + macOS) with app-first mac update flow and Sparkle beta track
Squashed from claude/update-notification-display-c6cfb9 after semantic merge
with #104178 (channel-aware CLI installs). See PR #104171 body for details.
* chore(i18n): resync generated inventories after rebase
* chore(i18n): resync locale metadata after rebase
Source-checkout (non-release) gateways now report their git branch in the
Control UI bootstrap config; the sidebar footer renders it in red next to
the connection dot. Package installs, main/master, and detached HEAD never
show it.
* fix(ui): move floating chat rail toggles into a single top row inside a titlebar band
The workspace and background-tasks openers stacked vertically over the
chat thread and covered the first message. They now sit side by side in
one 28px row, the thread reserves a ~44-52px top inset that keeps them
clear of bubbles, and empty band background starts a native window drag
in the macOS app (same beginNativeWindowDrag contract as pane headers).
Split panes keep their compact inset since the pane header already owns
the top row.
* test(ui): own the titlebar-band drag in native-window-drag and cover it with tests
Moves the top-inset drag logic from an inline chat-thread closure to
beginNativeWindowDragFromTopInset next to its sibling handler, and adds
jsdom regression tests for in-band background presses, below-band
presses, and presses on scrolled-under content.
* fix(google-meet): force English UI when reusing existing Chrome tab
* fix(google-meet): narrow reused tab before forcing English UI
* test(google-meet): assert /navigate target id is adopted in reused tabs
* fix(google-meet): preserve active call tabs and normalize English before recovery
* fix(google-meet): preserve localized active tabs
* test(google-meet): avoid shadowing path import
* test(google-meet): align mocks with English-pinned tab reuse
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(ci): register local-audio-acceleration doctor contribution and wrap overlong MLX log line
* fix(ci): keep MLX log line under 120 cols and resync native i18n inventory
* chore: nudge PR head sync
* fix(mac): use throwing safe read in MLX transport readability handler
Adds a git-backed session diff panel to the Control UI, complementing the existing PR status chips.
New sessions.diff gateway method (operator.read): resolves the session checkout and returns structured per-file diffs (status, renames, +/- counts, capped unified patch) of branch + uncommitted + untracked work against the default-branch merge base. Hardened against git textconv execution and hardlinked out-of-tree content leaks; handles repos before their first commit via the empty-tree base.
Control UI renders a "Changes" panel in the chat detail sidebar with collapsible per-file diffs, hunk-gap markers, stat chips, and untracked/binary badges, gated on gateway method advertisement. Schemas additive (Swift models regenerated), 20 locales translated.
Closes#104182
* fix(install): harden stdin consumers to prevent pipe corruption in curl | bash
Redirect stdin from /dev/null for non-interactive subprocesses (npm install,
openclaw daemon restart, openclaw plugins update, openclaw dashboard) and
from /dev/tty for interactive ones (openclaw onboard in bootstrap). Also
protect the fallback paths in run_with_spinner and run_quiet_step.
This prevents subprocesses from consuming the script stream when the
installer is piped via curl | bash, which causes truncated function names
and hangs (reported in #73814).
Unlike the global pipe guard in #82918 (closed as too broad), this approach
has no detection heuristics and no re-execution. Each subprocess simply gets
the correct stdin for its purpose.
Fixes#73814
Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
* chore: retrigger proof evaluation
Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
* fix: redirect stdin in run_with_spinner raw-mode fallback
The success-path raw-mode fallback in run_with_spinner invoked the
command with inherited stdin. In a curl | bash scenario, this allowed
the child process to consume bytes from the script stream, causing
truncation. Add < /dev/null to match the other two fallback paths.
* retrigger proof check
* fix(test): update gum fallback assertion for stdin redirect
* fix: redirect gum-wrapped stdin and preserve TTY for interactive commands
Address ClawSweeper P1 and P2 findings:
- P1: Redirect stdin from /dev/null on the normal gum spin path so child
commands cannot consume the piped script stream (gum v0.17.0 passes
os.Stdin to wrapped commands).
- P2: Use is_non_interactive_shell to conditionally redirect stdin in
fallback paths. When running interactively (bash install.sh), commands
that need user input (e.g. Homebrew prompts) keep terminal stdin.
When piped (curl | bash), stdin is redirected from /dev/null.
- P2: Revert labeler.yml changes to keep the PR focused on installer
stdin safety. Size-label best-effort handling belongs in a separate PR.
Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
* chore: retrigger proof evaluation
Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
* retrigger proof check
* fix: base stdin isolation on stdin TTY check, not stdout
Replace is_non_interactive_shell with needs_stdin_isolation for stdin
redirection decisions. The new function checks stdin directly (! -t 0)
and NO_PROMPT, without checking stdout (-t 1). This ensures that
stdout redirection (e.g. install.sh > log.txt) does not suppress
interactive prompts when stdin is a terminal.
* test(install): add stdin isolation and NO_PROMPT coverage
Three focused tests for the needs_stdin_isolation function:
1. Verify needs_stdin_isolation returns true when stdin is piped
(the core curl|bash scenario).
2. Verify needs_stdin_isolation returns true when NO_PROMPT=1 is set
(explicit non-interactive override).
3. Verify run_quiet_step redirects subprocess stdin to /dev/null
when running in a piped context, preventing script consumption.
* test(install): strengthen stdin isolation test with sentinel data
Replace the weak TTY check (which passes even without the fix since
the test pipe is also non-TTY) with a sentinel-based test: pipe
SENTINEL_DATA on stdin and verify the child process reads nothing,
proving run_quiet_step actually redirects stdin to /dev/null.
* test(install): add counterproof and cat-based stdin isolation tests
Add two new tests to prove the stdin isolation fix is necessary and
works correctly:
- counterproof: demonstrates that pipe data DOES leak to the child
when stdin is not redirected through run_quiet_step, proving the
/dev/null redirect is the isolation barrier
- cat-based test: uses cat (reads all of stdin) instead of read -t 1
(timeout-based) for a deterministic assertion that run_quiet_step
produces empty stdin
* fix: gate gum spin stdin redirect on needs_stdin_isolation
The gum spin command unconditionally redirected stdin from /dev/null,
which also killed interactive prompts for direct installs since gum
v0.17 passes os.Stdin to the wrapped command. Now only redirect
stdin when needs_stdin_isolation returns true (piped install context).
Adds focused tests verifying both paths: piped installs get /dev/null
redirect, direct interactive installs preserve terminal stdin.
* test: assert gum stdin is not /dev/null for direct interactive installs
The direct gum runtime test now reads the child command's stdin-source
log file and asserts stdin was NOT /dev/null, using device:inode
comparison (stat -f on macOS, stat -c on Linux) for reliable detection
across both platforms.
* retrigger proof check
* retrigger proof check with real installer evidence
Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
* fix(install): preserve interactive post-install stdin
* fix(install): route piped prompts through controlling tty
* fix(install): keep quiet piped steps noninteractive
* fix(install): avoid hidden prompts with redirected output
* fix(install): preserve visible prompt output state
---------
Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
Co-authored-by: Vincent Koc <25068+vincentkoc@users.noreply.github.com>
* fix(ui): resolve global-alias session kinds for chat avatar mode
* fix(ui): borrow only the session kind from alias-matched global rows
* fix(ui): gate the global-row kind fallback on configured global scope
* fix(ui): classify global-scope aliases without requiring a listed row
Hovering a model row in the chat model picker showed a tooltip that just
echoed the row's own label. The shared openclaw-tooltip now skips opens
whose content is already fully visible inside the trigger, while keeping
the full-text tooltip for clipped/ellipsized labels.