* fix(cron): reject sub-millisecond durations
* fix(skill-workshop): preserve proposal terminal newline
Preserve proposal_content exactly at the agent tool boundary and make
renderProposalMarkdown defensively emit a terminal newline.
Add focused regressions for the tool write path and markdown renderer.
* fix(skill-workshop): reject blank raw proposal content
* fix: treat empty-string optional integer tool params as unset
Optional positive-integer tool params (e.g. Telegram replyTo/threadId)
threw ToolInputError when a tool-calling model populated them with an
empty-string or whitespace-only default. Those defaults carry no value,
so readPositiveIntegerParam/readNonNegativeIntegerParam now treat a
blank string as unset (undefined) instead of throwing, while still
rejecting genuinely invalid present values (0, "42.5", "-3"). This
prevents silent message-delivery failures when models emit empty
routing-param defaults. Adds unit tests covering blank vs invalid.
* fix(gateway): log start session persistence failures
The gateway session-lifecycle "start" event persistence
(persistGatewaySessionLifecycleEvent, which surfaces write failures via
requireWriteSuccess) was fired as void ...catch(() => undefined), swallowing
the rejection with zero logging. A failed start-marker write silently dropped
the run's start record from restart-recovery accounting, with no
operator-visible trace.
The sibling terminal-phase catch already logs this since #97839; the start
path was the unfixed sibling. Mirror that fix: log the swallowed start-phase
persistence failure with the same redacted message shape via formatForLog,
keeping the fire-and-forget semantics unchanged. Adds a focused regression
test asserting the log fires on start-persist rejection.
* fix(memory): report close-time pending work failures
* fix(shared): return "" from sliceUtf16Safe when end <= start, matching native .slice
sliceUtf16Safe silently swapped reversed bounds (to < from) instead of
returning "" like String.prototype.slice, creating a subtle footgun for
callers with dynamic start/end pairs.
Caller scan across src/, extensions/, and packages/ confirmed no production
code relies on the old swap behavior — all callers use (text, 0, N) or
(text, -N) forms only.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(plugins): require plugin manifest in npm verifier
* fix(android): propagate CancellationException in CameraHandler catch blocks
Catch (err: Throwable) swallows kotlinx.coroutines.CancellationException,
breaking structured concurrency when the coroutine scope is cancelled
during camera operations (handleList/handleSnap/handleClip).
Add CancellationException rethrow before each Throwable catch to match
the existing pattern used in GatewaySession and TalkModeManager.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(android): propagate CancellationException past GatewaySession invoke boundary
* chore: sync native i18n inventory after gateway session line shift
* fix(agents): prevent native hook relay bridge race condition on renew and registration
Remove synchronous bridge record write after server.listen() that races
before the TCP server binds, and guard renew handler with server.listening
check to prevent stale relay registrations.
Closes#98650
* fix: harden small reliability fixes
Co-authored-by: cxbAsDev <cxbAsDev@users.noreply.github.com>
* docs(agents): explain buffered LSP spawn failures
* docs(agents): clarify LSP spawn timeout invariant
---------
Co-authored-by: qingminlong <qing.minlong@xydigit.com>
Co-authored-by: anyech <anyech@gmail.com>
Co-authored-by: snotty <snotty@users.noreply.github.com>
Co-authored-by: masatohoshino <g515hoshino@gmail.com>
Co-authored-by: lin-hongkuan <lin-hongkuan@users.noreply.github.com>
Co-authored-by: simon-w <weng.qimeng@xydigit.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: 宇宙熊Yzx <53250620+849261680@users.noreply.github.com>
Co-authored-by: xialonglee <li.xialong@xydigit.com>
Co-authored-by: nankingjing <1079826437@qq.com>
Co-authored-by: cxbAsDev <cxbAsDev@users.noreply.github.com>
* ci: guard gateway protocol event coverage for iOS/Android clients (#100198)
Adds scripts/check-protocol-event-coverage.mjs, which derives the
server->client event catalog from GATEWAY_EVENTS in
src/gateway/server-methods-list.ts, extracts the events each mobile app
handles from Swift/Kotlin dispatch sites, and fails on gateway events no
client handles unless allowlisted with a reason in
scripts/protocol-event-coverage.allowlist.json. Wired as
pnpm check:protocol-coverage in the CI guards shard.
* fix(ci): scope Kotlin event extraction to handle*Event dispatch functions (#100198)
Bare event == "..." literals in predicate helpers outside the dispatch
path (gatewayEventInvalidatesNodesDevices in NodeRuntime.kt, which has no
production caller) counted as Android coverage, silently masking that
node.pair.requested/resolved have no live handler. Kotlin extraction now
only reads when(event) labels and event comparisons inside fun
handle*Event(...) bodies; node.pair.* moved to the Android allowlist with
a truthful reason. Swift extraction stays tree-wide because consumption
there always reads .event off a received EventFrame.
* fix(android): remove dead node pairing event helper
* fix(ci): preserve protocol allowlist parse errors
* test(ci): align tooling import plan
Ports the openclaw.ai hero mascot animation (body float 4s, eye blink 3s,
antenna wiggle 2s, staggered claw snaps 4s) to every product surface that
shows the logo:
- web: ui/public/favicon.svg now carries SMIL animations, so every <img>
usage (login gate, sidebar brand, agent avatar fallback) animates with
no markup changes; CSS animations do not run in <img>-loaded SVGs.
- iOS: new OpenClawMascotView renders the canonical 120x120 vector via
Canvas + TimelineView (30fps, honors Reduce Motion); OpenClawProMark
uses it in onboarding, sidebar, and command center. Static PNG imageset
deleted (watch app keeps its own copy).
- Android: new OpenClawMascot composable (PathParser + infinite
transitions, honors the OS animator scale) replaces the static vector
drawable in onboarding and the chat/shell/voice headers, with tint
support for the monochrome header silhouettes. Drawable deleted.
* fix: clean up Android camera clips on cancellation
* fix(android): own camera clip cleanup lifecycle
---------
Co-authored-by: NianJiuZst <180004567+users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(android): strip ws scheme prefix from manual gateway host input
Reject bare ws:// in the Host field and normalize pasted ws:// LAN URLs
so manual onboarding resolves the real hostname instead of host ws.
Fixes#87216.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(android): preserve complete manual gateway endpoints
* fix(android): preserve gateway i18n inventory
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(android): ignore chat events with missing assistant role in voice text extraction
Voice chat event parsing now rejects messages without an explicit assistant role,
matching ChatController fail-closed behavior and preventing accidental TTS playback.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(android): require canonical assistant role for voice events
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(android): distinguish gateway auth-recovery reasons instead of one generic label
gatewaySummary()/gatewayStatusLabel() collapsed every auth failure (expired
setup QR, missing/invalid token, missing/invalid password, stale device
identity) into "Authentication needed", discarding the already-computed
MainViewModel.gatewayConnectionProblem signal consumed elsewhere in the app
(OnboardingFlow's recoveryGatewayAuthDetail). Thread it into both status
labels via a shared gatewayAuthNeededSummary() helper so the Overview and
Settings screens tell the user which recovery action applies.
Fixes#98046
* test(android): lock gateway auth-needed labels to the real unauthorized message format
Verified against src/gateway/server/ws-connection/auth-messages.ts:
formatGatewayAuthFailureMessage always returns strings starting with
"unauthorized", which contains the "auth" substring the status-text gate
checks for. Add tests using the exact real-world message text (not just
synthetic "auth failed" strings) so this stays regression-locked.
* fix(android): widen the auth-needed status gate to cover device-identity failures
CONTROL_UI_DEVICE_IDENTITY_REQUIRED/DEVICE_IDENTITY_REQUIRED real gateway
messages ("device identity required", "control ui requires device
identity...") don't contain "auth", so the status.contains("auth") gate
never reached their specific label. Add "device identity" as an additional
substring the gate checks for those two codes, with regression tests using
the exact real message text (message-handler.ts).
Verified via GitHub-style codex review round 2 (round 1's "unauthorized
doesn't contain auth" claim was checked against source and rejected as
false — "unauthorized" does contain "auth" — but this second, narrower
finding about device-identity codes held up under the same source check).
* fix(android): centralize gateway auth status labels
* fix(android): correlate gateway connection diagnostics
* chore(android): align native i18n inventory after rebase
* fix(android): preserve retryable pairing guidance
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix: release Android camera snap use case
* fix(android): release one-shot camera capture promptly
---------
Co-authored-by: NianJiuZst <180004567+users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>