The run-scoped plan snapshot already fed the sticky composer bar; it now
also renders as an always-expanded card inside the active run's work
group, so plan progress is visible in scroll context. The bar and card
share one extracted renderer (variant: bar | card) instead of duplicating
the template, and the card clears with the run through existing plan
lifecycle.
* fix(proxy-capture): guard body-less arrayBuffer reads against oversized responses
* fix(proxy-capture): exercise body-less fallback in bounded read tests
New tests use mock clones with body: null plus arrayBuffer spies to
prove the content-length precheck guards the !body path. A real
Response clone exposes body.getReader in Node 24, so the prior test
only exercised the streaming branch and would stay green even if
the precheck were deleted.
* chore: retrigger CI
* fix(proxy-capture): reject non-safe content-length before arrayBuffer (#101268)
ClawSweeper P2: the body-less fallback used Number(content-length), so a
huge digit-only Content-Length value could overflow to Infinity, bypass the
Number.isFinite guard, and still call arrayBuffer() — leaving an OOM path in
the hardening PR.
Add declaredContentLengthExceedsCap, which accepts only plain digit strings,
treats any value longer than Number.MAX_SAFE_INTEGER as oversized, and
compares safe-integer parsed values against the cap. Non-numeric or malformed
values fall through to the post-read length check.
Adds a regression test for a 100-digit Content-Length that would previously
have bypassed the guard.
* fix(proxy-capture): normalize zero-padded Content-Length before digit-count guard
* fix(proxy-capture): fail closed without response streams
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
The live-test helper is exported from openclaw/plugin-sdk/test-live (as every
other extension live test imports it), not test-env. The wrong subpath in
#109335 broke check-test-types repo-wide.
Ports the deterministic mascot animator to Android: full pose channels
(face, gaze, droop, hard hat, effects), seeded mood loops including the
hard-hat working hammer cycle, entrance gestures, and static poses when
system animations are off. Onboarding heroes now feel the flow — working
while the gateway connects, celebrating once paired, thinking during
node approval, curious on permissions, sad on visible errors. Tinted
silhouette marks keep their ambient idle loop and stay silent for
TalkBack outside the welcome hero.
* fix(sandbox): use Buffer.byteLength for env var value size limit
validateEnvVarValue checked value.length (UTF-16 code units) against
the 32768-byte limit, so multi-byte CJK values like "值".repeat(11000)
passed the check despite exceeding 33 KB in UTF-8. Switch to
Buffer.byteLength(value, "utf8") so the limit matches the actual byte
count the OS and child processes see.
* test(sandbox): simplify env byte-limit coverage
Co-authored-by: 唐梓夷0668001293 <tang.ziyi@xydigit.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
isLoopbackProxyHostname used split(".", 1)[0] === "127" to detect
127.0.0.0/8 addresses. While isIP() already rejects leading-zero octets
before this branch is reached, startsWith("127.") is more direct and
clearly expresses the intent of matching the entire 127.0.0.0/8 range.
* test: fix environment-dependent failures on built and partial-clone checkouts
Canvas A2UI fixtures now pin the resolved root through a new canvas
test-api hook: dev checkouts with a locally built a2ui.bundle.js won
candidate resolution and shadowed the fixture root. Speech-core prefs
tests resolve the canonical tmpdir (macOS /tmp symlink breaks fs-safe
store roots), and the bundled plugin install test asks discovery for
the canonical bundled path instead of hardcoding the source tree.
* fix(release): keep evidence verifier blob reads local-deterministic
git show against a forged or missing SHA lazily fetched from the
promisor remote on partial clones, hanging a security check on network
state (266s observed). GIT_NO_LAZY_FETCH pins verification to local
objects; missing blobs fail closed as before.
* perf(test): pay the command-handler barrel once and merge subagent command tests
commands-name and commands-login imported the full handler barrel
(~1,700 modules) for registration assertions; those now live in a
dedicated registration test, dropping the two files from 12.6s/4.6s to
1.4s/1.0s locally. Five subagent command test files merged into one so
the auto-reply module graph evaluates once instead of five times.
* perf(ci): stripe node test bins by measured file cost
Round-robin striping packed the whale files together, leaving
auto-reply-commands-1 at ~220s while sibling stripes idled at ~30s.
Greedy LPT over advisory per-file seconds hints balances the commands,
cli-runner, and tooling stripes deterministically; group hints for the
restriped bins are equalized at the old totals.
Code mode previously suspended to the model with a waiting result for every
awaited tools.search/describe/call, forcing one wait tool turn per await
(turns = tool calls + 1). Bridge calls now resolve inline within the exec
deadline and resume the QuickJS snapshot host-side, matching the namespace
auto-drain path. Restart-safe runs keep their existing no-drain semantics.
Live proof across OpenAI/Anthropic/Google: ~45% fewer turns and ~45% fewer
input tokens at equal success rate.
Also landed with the same contract:
- one shared wall-clock deadline per exec/wait call (initial worker, inline
drain rounds, and resumed guest interrupt budget all share it)
- park the snapshot instead of resuming when the remaining budget could not
survive VM restore, so near-deadline settles stay recoverable
- abort now terminates the guest worker, skips resume, and drops suspended
runs instead of pinning process-global slots until TTL, reported with the
existing aborted failure code
- exec description omits API.list/MCP/namespace guidance when the run catalog
has no MCP tools or namespaces, so models stop probing an empty surface
- API.list tolerates a trailing-slash prefix like API.list("mcp/")
The standalone chat window stacked a visible title bar (traffic lights +
"OpenClaw" title) above the SwiftUI toolbar row, wasting a full band of
vertical space. Apply the Dashboard window's merged chrome: hidden title,
transparent titlebar, no separator, movable-by-background — the unified
toolbar (sidebar toggle, search, pickers, session actions) now shares its row
with the traffic lights. A window subclass pins titleVisibility to hidden
because SwiftUI's toolbar bridge can restore visible title chrome, and scene
bridging is reset to toolbars-only after controller attachment (attaching an
NSHostingController resets it to .all).
The companion now rings the doorbell for gateway approvals: it polls pending
node and device pairing requests over the existing CLI seam on the connected
watchdog cadence, fires one native notification per new request when the
window is unfocused, and shows a pending count in the tray status line.
Approval itself stays in the dashboard/CLI (owner boundary).
Adds a CmdOrCtrl+Shift+O summon shortcut with a tray toggle whose opt-out
persists as a marker file (no config schema). The X11-only global-hotkey
backend is gated off on native Wayland sessions — the feature is omitted
rather than half-registered through XWayland; a portal implementation can
follow.
* fix(infra): bound three warning dedupe caches with createDedupeCache
Replace unbounded Set<string> warning dedupe caches with the shared
createDedupeCache helper (ttlMs=0, maxSize=4096) to prevent unbounded
memory growth:
- safeBinTrustedDirWarningCache in invoke-system-run.ts
- chmodWarnedTargets in openclaw-state-db.ts
- clobberCapWarnedPaths in io.clobber-snapshot.ts
All three follow the same anti-pattern: .has()/.add() only, no eviction,
no size cap. Matches the fix pattern from #101696 and #101738.
Co-Authored-By: Claude <noreply@anthropic.com>
* chore: refresh PR body evidence for ClawSweeper re-review
* test(io.clobber-snapshot): add clobber-cap warning deduplication test
Exercises warnClobberCapReached through the production API
(persistBoundedClobberedConfigSnapshot) to prove that
clobberCapWarnedPaths.check() suppresses duplicate warnings
on the same config path.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(infra): tighten warning cache coverage
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(kill-tree): verify process group leader before group kill to prevent gateway SIGTERM (#76259)
- Add isProcessGroupLeader() to killProcessTree/signalProcessTree: ps -p <pid> -o pgid= primary check with /proc/<pid>/stat fallback on Linux. Group kill only when the PID is its own process group leader; non-leaders fall back to single-pid kill, preventing accidental gateway SIGTERM when a non-detached child shares the gateway's process group.
- Propagate detached: true to all detached-spawn cleanup callers (exec-termination, agent-bundle LSP, mcp-stdio, bash, supervisor pty, agent-core nodejs) so detached group cleanup survives leader exit.
- Gateway/daemon cleanup paths (schtasks, restart-health) keep the leader-checked default (detached omitted).
Closes#76259
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor(process): tighten process-group ownership checks
* refactor(daemon): split restart diagnostics
* refactor(daemon): isolate restart health types
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>