* fix(gateway): stop device pairing approval alert floods from retrying devices
A device retrying with a broken token minted a new pending requestId (and
approval alert broadcast) every 5-minute TTL window because refresh preserved
ts and expiry keyed on ts. Pending requests now stay alive via an internal
refreshedAtMs keepalive (ts still owns ordering/--latest), superseded device
requests broadcast device.pair.resolved like node pairing already did, and the
Mac app keeps one alert per device, closes superseded visible alerts, and
resyncs its queue when approving a stale request.
Closes#100974
* chore: satisfy swiftformat and refresh native i18n inventory for pairing prompter changes
* feat(crestodian): AI-only conversation with model-judged approvals
Every chat message now routes to the AI (agent loop, then planner); the
typed grammar is a single anchored command language used only as the
no-model deterministic fallback and for rescue/one-shot, so natural
language can no longer misparse into operations. Approval of pending
mutations is judged from the user's own words by a host-run classifier
(closed-list fast path, single-shot model judgment, fail-closed), never
by the conversation model. New connect_channel/open_agent ring-zero tool
directives let the AI start the hosted channel wizard and agent handoff.
Chat turns are serialized, stale host proposals are cleared once the
agent loop owns the conversation, sensitive config-set values are
redacted from AI-visible history, and Gemini CLI joins the overview,
models output, prompts, and local backend ladder.
Fixes#100604
* test(crestodian): cover AI-only turns, approval intents, directives, and anchored grammar
* docs(crestodian): AI-only conversation, natural approvals, Gemini fallback
* test(crestodian): classify decline via text classifier in agent-loop seam
* fix(crestodian): correct textResult arity and directive handoff type
* fix(crestodian): keep exact sensitive config-set commands off every model path
* feat(ui): declutter the sidebar — pinned/chats groups, stable ordering, palette session search, titlebar brand in macOS app
- Sessions split into Pinned and Chats groups; pins are exempt from the
nine-row recency cap and the open session highlights in place instead of
hoisting to the top, so clicking a row no longer reshuffles the list.
- The command palette (Cmd+K) is the single search: it queries sessions
across agents server-side and lists matching chats next to nav commands;
the sidebar session-picker popover and its magnifier are removed, and the
topbar search pill shrinks to an icon button.
- The sidebar brand row is gone: web keeps the breadcrumb wordmark, and the
macOS app shows a compact brand mark in the native titlebar strip next to
the traffic lights (CSS-gated on the injected openclaw-native-macos class).
- The agent filter dropdown becomes a compact scope chip in the Chats group
header; the welcome badge drops its duplicate claw logo.
- Footer collapses to one icon row (status dot, settings, docs, pairing,
mobile theme switch); default pinned nav routes are now Overview-only and
the Recent-collapse preference is retired.
* test(ui): keep sidebar hover-reveal proof before the stability click; refresh locale metadata
Clicking the row leaves :focus-within on it, which keeps the management
actions visible; the hidden-actions assertion must run first.
* test(ui): archive an idle session in the sidebar e2e; assert running rows stay archive-disabled
* fix(ui): drop stale and hidden rows from palette session search
Invalidate the in-flight search and clear results on every query change so a
late-resolving previous-query request cannot leave stale rows selectable, and
run results through getVisibleSessionRows so archived/global/unknown/cron/
subagent rows stay hidden like the sidebar list. Codex review findings.
* fix(ui): over-fetch palette session search so hidden rows cannot starve visible matches
Exclude global/unknown rows server-side and fetch a full 50-row page before
applying the sidebar's hidden-row filter, keeping the 10-row display cap.
Codex review finding.
* chore(i18n): refresh sidebar locale metadata after rebase
* fix(ui): bound command palette session search
* docs: defer release notes to release generation
Second sweep: 13 cards from the openclaw.ai X showcase and Discord
maintainer channel — car negotiation, flight check-in, insurance
claim, Idealista skill, tokenomics cost tracker, Excalidraw
diagrams, GA4 skill, X post pipeline, lab results to Notion,
Pebble Ring voice, creator media studio, HomePod skill, holo cube,
macOS menu bar app — plus a Related link to the full X showcase.
oxfmt 0.57.0 --check passed; hook bypassed (no node_modules in
worktree).
Adds 12 community cards sourced from Discord (#skills, #general,
shell-society) and the web: Dropage deploy, anti-scam URL checker,
product-design skill trio, miab-broker, lite-mode, ClawEval,
Music Craft, gardening back office, Obsidian second brain,
family history bot, Action Button walkie-talkie, and Clinch.
All links verified live; oxfmt 0.57.0 --check passed (worktree
lacks node_modules, hook bypassed after proof).
Gateway (additive, no protocol version bump): SessionEntry gains
lastReadAt/markedUnreadAt/lastActivityAt; session rows expose a derived
unread flag (explicit mark, or last read before latest activity; never-read
sessions stay read so upgrades do not light up). lastActivityAt is stamped
in the canonical post-run store update - user, channel, and cron runs count
as activity; heartbeat, internal-event, and preserved-state runs do not.
sessions.patch gains unread; sessions.create gains fork (transcript fork
from parentSessionKey under the parent lifecycle lock, refusing active,
concurrently-changed, and oversized parents, cross-agent aware).
Web sidebar: Pinned/custom-group/Ungrouped sections, unread dots, kebab and
right-click context menu (pin, mark unread/read, rename, fork, move to group,
archive, delete guarded for agent main sessions and active runs), mark-read
on view with loop-safe re-acknowledgement and failure retry; sessions page
gets unread + fork actions and shared custom-group helpers.
iOS Command Center: grouped sections, unread/pin indicators, Show Archived
gated on per-entry state, full context menu with rename/new-group alerts and
delete confirmation, current-session preview guarantee, read-episode
re-acknowledgement; new patch/delete/fork transport calls; Swift protocol
models regenerated.
Android SessionsScreen: grouped headers, unread/pin indicators, Archived
filter gated on per-entry state, long-press menu with the full control set,
agent-scoped forks, explicit label/category clears from session events,
main-session fallback when archiving/deleting the open chat, read-episode
re-acknowledgement with failure retry.
Closes#100739
* feat: correlate native search outcomes in audit history
Metadata-only audit ledger for agent runs and tool actions in the shared
state DB: stable event identity, closed action/status/error vocabularies,
one-way-hashed tool-call ids, never-inferred terminal outcomes for native
web-search (explicit completed/failed only; otherwise unknown), bounded
retention, audit.list gateway RPC and openclaw audit CLI. Squashed from
the 82-commit audit stack for replay onto current main.
* feat(audit): add audit.enabled config gate (default on)
The metadata-only audit ledger records by default: an audit trail enabled
only after an incident cannot explain the incident, and the rows are
strictly less sensitive than the transcripts every install already
stores. audit.enabled=false stops new writes at the gateway subscription
seam; audit.list and openclaw audit keep serving existing records until
they expire. Documented in the configuration reference, protocol page,
and CLI reference.
* fix: repair full-matrix CI findings after rebase
- break the dynamic-tools/dynamic-tool-execution import cycle by
extracting resolveCodexToolAbortTerminalReason into a leaf module
- restore main's session-worktree protocol exports lost in the
index.ts auto-merge
- register the audit event writer worker as a knip entry point
- docs table formatting; subagent wait-cancellation test scoped to its
audit intent (outcome + timing) and advanced past main's new
lifecycle-timeout retry grace
Adds an additive worktree flag to sessions.create so any new chat can run in an
isolated managed worktree of the agent's git workspace, with the branch checked
out and .worktreeinclude provisioning applied. The session's spawnedCwd points at
the matching subdirectory inside the worktree so chat runs, CLI, and the file
browser execute there. agents.list gains workspaceGit (workspace or an ancestor
is a git checkout) to gate the affordance; web sidebar, iOS, and Android expose a
New-Chat-in-worktree action. Uses the method's operator.write scope, but the
.openclaw/worktree-setup.sh step runs only for operator.admin callers since it
executes repo code. Deleting the session, or leaving via a plain New Chat, clears
the cwd and lossless-removes the worktree; idle GC treats recent session activity
as worktree activity so an active session's checkout is never swept.
Live-verified end-to-end on a real gateway; follow-up to #100535 (issue #100534).
* feat(browser): restore driver "extension" via loopback Chrome extension relay
Reintroduces browser profile driver "extension" (removed in 2026.3.22) as a
loopback relay that drives the user's signed-in Chrome through an MV3 extension
instead of the remote-debugging port. This avoids Chrome's blocking "Allow
remote debugging?" prompt, which cannot be clicked when the operator drives
OpenClaw from a phone. Automated tabs live in an "OpenClaw" tab group (the
consent boundary), mirroring the Codex/Claude-in-Chrome model.
- relay bridge synthesizes the CDP browser target surface for Playwright
connectOverCDP and forwards session-scoped commands to chrome.debugger
- relay server binds loopback only; both sides authenticate with a token
derived (HMAC-SHA256) from gateway auth, so the raw credential never reaches
Chrome; extension origin + loopback Host checks guard the upgrade
- built-in "chrome" profile; distinct relay ports per extension profile;
relay reconciles on auth rotation / cdpPort change and prunes removed profiles
- doctor + status surface the extension transport; doctor keeps repairing the
retired relay endpoint URL on legacy "extension" profiles
Refs #53599
* feat(browser): bundle the OpenClaw MV3 Chrome extension
Thin MV3 extension (chrome-extension/): a WebSocket client to the loopback
relay plus chrome.debugger forwarding and OpenClaw tab-group management. All
CDP target synthesis lives server-side in the relay bridge, so the extension
stays a dumb transport (the removed 2026.3 extension put that logic in a
1000-line untestable service worker). Popup handles pairing and per-tab share
toggle; `openclaw browser extension path|pair` load and pair it. A build copy
hook stages it into dist so the load path is stable.
Refs #53599
* docs(browser): document the Chrome extension profile
Adds docs/tools/chrome-extension for the restored extension driver (install,
pair, tab-group consent model, security posture) and wires it into the browser
docs profile section and nav.
Refs #53599
* feat(browser): make the extension relay work on remote browser nodes
Derives the relay auth token from a host-local secret in the credentials dir
(created on first use) instead of gateway auth. Each machine that runs a
browser — the gateway host and every browser node host — owns its own token, so
the extension pairs with whichever machine hosts its Chrome and no gateway
credential travels to a node. The node host already runs the shared browser
control bootstrap, so this is all that was missing for cross-machine control.
Also removes the "relay needs gateway auth before it can start" failure mode:
startup and `openclaw browser extension pair` ensure the secret exists.
Refs #53599
* fix(browser): harden relay secret creation and satisfy CI lint/typecheck
- Make the host-local relay secret creation atomic (O_CREAT|O_EXCL + adopt the
winner on EEXIST) so the gateway service and `extension pair` CLI cannot mint
divergent tokens on a fresh host (would 401 until restart); credentials dir
created mode 0700. (adversarial review finding)
- Resolve type-aware oxlint findings across the relay + extension: unknown catch
vars, addEventListener over ws.on* in the MV3 worker, void async listeners,
drop useless returns/spreads, Object.assign over map-spread, safe ws frame
decode (Buffer[]/ArrayBuffer), toSorted.
- Add extensionRelayDefaultPort/extensionRelayPorts to remaining test config
literals; type the extension relay-core module (.d.ts, excluded from dist);
regenerate docs_map.
* fix(browser): satisfy OpenGrep security policy on the relay
- Hash both operands before timingSafeEqual so token comparison has no
length short-circuit (GHSA-JJ6Q-RRRF-H66H).
- Bound the relay WebSocketServer with maxPayload (64 MiB, headroom for CDP
screenshots/bodies) against oversized frames (GHSA-VW3H-Q6XQ-JJM5).
- Rewrite the config test env helper to avoid the skill-env-host-injection
shape (GHSA-82G8-464F-2MV7); it is a test-only env swap.
Reactive corrections ("that's not what I asked", "stop doing X") now
count as durable signals: expanded extraction patterns, vocabulary
routing to existing workspace skills, per-skill grouping. Both capture
modes share one invariant: a bounded signal-fingerprint ring on the
session entry prevents replaying applied/rejected corrections, pending
autocapture-owned proposals are revised instead of skipped, /learn-style
turns suppress duplicate agent-end capture, and extraction runs before
any skill discovery. Autonomy off keeps the suggest-tier offer; autonomy
on files/revises proposals directly.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* improve(cron): show consecutive failure count and last error in cron list/show
* improve(cron): show consecutive failure count and last error in cron CLI output
* fix(clownfish): address review for live-pr-inventory-20260706T074223-002 (1)
---------
Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com>
* fix(hooks): flag hook event names that no trigger site emits
* docs(hooks): clarify bare family subscriptions in unknown-event note
* fix(hooks): word unknown-event diagnostics around core-emitted keys
* fix(hooks): apply unknown-event advisory to legacy config handlers too
Adds the middle tier between capture-off and autonomous capture: when
autonomy is disabled, detected durable-instruction signals record a
one-shot pendingSkillSuggestion on the session entry (signal-hash
fingerprint prevents transcript-history replay), and the next
non-heartbeat turn atomically consumes it and injects one bounded
user-role line offering to save the skill. The agent offers, the user
decides; skill_workshop approval flow unchanged; no new config.
Gateways that crash-loop under systemd/launchd previously flapped forever
with no persisted state and no supervisor signal. The gateway now records
every boot outcome in the shared state DB (gateway_boot_lifecycle); three
unclean boots within five minutes trip a breaker that boots the gateway in
safe mode: full control plane available, channel/provider auto-start
suppressed at the channel-manager seam (startup, config hot-reload,
secrets.reload) with manual channels.start override, one stability bundle
per trip. The breaker re-evaluates each boot and logs recovery when the
window drains. Slow shutdowns record forced_stop and never count as
crashes; /readyz stays ready and reports suppressed channels; the health
monitor treats suppressed accounts as expected-stopped. Fatal invalid-config
errors now exit 78 (EX_CONFIG) on both the startup and unhandled-rejection
paths, engaging the systemd unit's pre-existing RestartPreventExitStatus=78
so supervisors stop relaunching until the config is fixed.
* feat(skills): diagnose skill_workshop hidden by tool policy (#87570)
Workshop can be enabled and auto-capturing while tools.profile hides the
skill_workshop tool; every inspection surface looked healthy. plugins
inspect and openclaw doctor now name the excluding policy layer (global/
agent/provider profile, allowlist, denylist) and the exact alsoAllow
grant to add, via a shared resolveSkillWorkshopToolPolicyAvailability
helper that /learn's guard now reuses instead of composing policy
itself. Diagnosis only; no policy behavior change.
* ci: retrigger
* feat(macos): adopt shared read-only chat transcript cache
Cache-first cold open for the macOS chat window/panel: the last known
transcript and session list paint immediately from the shared SQLite
transcript cache, then live gateway history replaces them wholesale.
Recent chats stay browsable read-only while the gateway is unreachable;
sending remains gated by connection state.
- Wires OpenClawChatSQLiteTranscriptCache into WebChatSwiftUIWindowController;
DB at ~/Library/Application Support/OpenClaw/chat-cache.sqlite.
- Gateway identity (MacChatTranscriptCache.gatewayID), derivable offline:
local keys on the canonical gateway state dir; remote/direct keys on the
full canonical URL (scheme, host, resolved port, percent-encoded
path/query); remote/ssh keys on the SSH target plus the resolved remote
gateway port, mirroring the tunnel port resolution. Unconfigured mode
gets no cache.
- macOS file protection: no per-file Data Protection classes; iOS-only
attribute stays gated behind #if os(iOS) in the shared store, and the
per-user container plus FileVault protect at rest.
- Onboarding chat stays uncached (transient guided setup session).
Part of #100194
* feat(macos): wire the offline command outbox into chat windows
* style(macos): fix orphaned doc comment; regenerate docs map
* style(macos): doc-comment lint fix; regenerate docs map on current main
Summary:
- Merged feat(tencent): add Tencent Hy3 provider (TokenHub and TokenPlan) after ClawSweeper review.
Automerge notes:
- Ran the ClawSweeper repair loop before final review.
- Included post-review commit in the final squash: fix(tencent): preserve TokenHub auth compatibility
- Included post-review commit in the final squash: refactor(tencent): unify TokenPlan env/flag naming with TokenHub
- Included post-review commit in the final squash: docs: refresh Tencent provider docs metadata
- Included post-review commit in the final squash: fix: allow TokenPlan provider config overlays
- Included post-review commit in the final squash: docs: dedupe Tencent provider glossary labels
- Included post-review commit in the final squash: fix(tencent): repair TokenHub model defaults
Validation:
- ClawSweeper review passed for head 30c9fc130f.
- Required merge gates passed before the squash merge.
Prepared head SHA: 30c9fc130f
Review: https://github.com/openclaw/openclaw/pull/99076#issuecomment-4888527271
Co-authored-by: leisang <leisang@tencent.com>
Co-authored-by: Mason Huang <masonxhuang@tencent.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: hxy91819
Centralized managed worktrees under <state-dir>/worktrees/<repo-fingerprint>/<name>
with branch-per-task (openclaw/<name>), .worktreeinclude provisioning, an optional
.openclaw/worktree-setup.sh repo hook, and a SQLite registry in the shared state DB.
Removal always snapshots the tree (untracked included, gitignored excluded) to
refs/openclaw/snapshots/<id>; restore rebuilds the branch at the original commit with
the snapshot content as uncommitted state. Lossless run-end cleanup, 7-day idle GC for
run-owned worktrees (manual exempt), orphan reconciliation, 30-day snapshot retention.
Surfaces: worktrees.* gateway RPC (operator.admin mutations), openclaw worktrees CLI,
Control UI page, plugin-SDK facade + Workboard kind:"worktree" materialization.
E2E-verified on Testbox: full create->work->remove->restore->gc lifecycle.
* feat(ios): durable offline command outbox for chat sends
Text messages sent while the gateway is unreachable queue in a durable
per-gateway outbox (new outbox_commands table in the per-gateway chat
cache SQLite store, schema v2) instead of failing. Queued bubbles render
with visible Queued/Sending/Not sent states and flush strictly in
createdAt order once transport health recovers; each command's client
UUID rides as the send idempotency key, so at-least-once delivery plus
gateway dedupe keeps the transcript exact.
Contract summary:
- Bounds: 50 queued commands per gateway; refused enqueues keep the
draft. Queued rows older than 48h expire to failed("expired") rather
than silently sending stale commands; tap-to-retry refreshes
createdAt so an expired row can resend as new intent.
- Failure taxonomy: transport-level failures keep rows queued without
burning retry attempts (backoff ladder, then health drops so the
reconnect machinery owns pacing); gateway rejections burn attempts
and fail terminally after 3, with context-menu retry/delete.
- Deletes are tombstoned synchronously and rechecked after the claim
await so an active flush can never send a removed command; Delete is
hidden while a bubble is already in flight; offline enqueue is
guarded against double submit during the health probe.
- Post-reconnect live sends route behind draining outbox rows (FIFO),
and a cold open assumes a backlog until restore adopts durable rows.
- Crash safety: 'sending' rows revert to 'queued' at startup; flushed
turns are spliced into the session's cached transcript before their
outbox row is deleted, and stale history snapshots cannot evict a
just-flushed turn until a snapshot confirms it.
- Per-gateway scoping and purge ride the transcript cache: one SQLite
file per gateway; reset/forget drops the queue with the cache.
Fixture/unpaired transports get no outbox.
Part of #46664
* chore(ios): sync native i18n inventory
* style(chat-ui): satisfy strict SwiftFormat lint (doc comment, scope blank line)
* feat(goals): keep active session goals in per-turn context (#100409)
Active goals now inject one bounded user-role context line on every
non-heartbeat turn, refreshed at queued/interrupt admission via
provenance-tracked generated lines so operator stops take effect
immediately. Adds qa/scenarios/goals continuance scenarios and
goals.* coverage IDs.
* docs: regenerate docs map for goal context section
* fix(qa): use requiredChannelDriver instead of execution.channel pin in goal scenarios
Approval wait now fits inside the Codex dynamic-tool watchdog (70s +10s
gateway grace under the 90s kill), approval cards carry proposal id,
skill name, description, file count, and body size (spoof-safe
rendering), and timeouts return a structured pending-not-failed outcome
instead of a bare error. Expired requests cannot execute late; no
auto-apply; generic plugin approvals unchanged.
* feat(commands): add /learn to draft skills from recent work (#100408)
/learn rewrites the turn into a standards-guided Skill Workshop authoring
instruction: the agent gathers named sources (or distills the current
conversation) and files ONE pending skill proposal via skill_workshop.
Approval flow unchanged; sandboxed/tool-restricted agents get a clear
unavailable reply. Extracts the harness OpenClaw-tools predicate into
shared helpers and reserves the command name against plugin shadowing.
* docs: regenerate docs map for /learn section
* feat(gateway): add system.info RPC and Gateway Host card in Settings
Settings quick page now shows where the Gateway runs and how the host
is doing: machine name, LAN address and runtime port, OS, Node/PID,
uptime, CPU count and load, RAM, and free disk on the state-dir volume.
Backed by a new additive operator.read RPC (system.info); the card
polls every 10s while visible and hides for clients without the read
scope or against older gateways.
Refs #100465
* docs: regenerate docs map for Gateway host status section
* fix(gateway): harden system info compatibility
* fix(ui): clear stale gateway host info
* docs(changelog): note gateway host status
* fix(browser): drain download saves and use monotonic cursor for act response
- Add drainPendingDownloadSaves() to wait for in-flight saveAs before sampling
- Use monotonic downloadSeq cursor instead of bounded-list length
- Propagate downloads info in POST /act response for click/batch/evaluate
Fixes#93250
* fix(lint): add braces around single-line if returns
Fixes eslint(curly) failures in drainPendingDownloadSaves and
pickNewDownloads. PR #93307 required CI gate.
Ref: ClawSweeper P1 review finding
* fix(browser): scope act download metadata to action
* fix(browser): broadcast downloads to all active captures to prevent misattribution
When concurrent /act calls overlap on the same page, using
state.actionDownloadCaptures.at(-1) assigned downloads to the wrong
action's capture. Push managed save promises to all active captures
so the triggering action always receives its download metadata.
Also adds regression tests: broadcast-to-all-captures, sequential
capture isolation, and strengthen the dispose test to assert no
re-capture after disposal.
* fix(browser): prevent unhandled rejection when download capture action throws before drain
Move managedSave.catch() before the captures-branch check so the
rejection handler always runs, preventing an unhandled promise
rejection when the action throws before drain() is called. Simplify
the handler by removing the now-dead return + catch at the bottom.
* fix(browser): type downloads in BrowserActResponse, fix lint unused var
- Add optional downloads field to BrowserActResponse type contract so
typed callers of browserAct() can consume the new payload without casts.
- Remove unused afterDispose variable in pw-session tests (lint fix).
* fix(test): correct post-dispose download assertion
capture.promises is not cleared by dispose(), so re-draining a disposed
capture still returns pre-dispose results. This is benign — callers
should drain before disposing. Update the test to assert the actual
behavior (still shows old results, new download not captured).
* test(browser): cover /act download metadata response
* refactor(browser): report action-owned downloads safely
* fix(browser): close action download ownership races
* test(browser): type action download capture mock
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(control-ui): keep the dashboard mounted with a reconnect banner on gateway drops
Once a session is established, a dropped gateway WebSocket no longer
unmounts the dashboard into the login gate. The client's close handler
now reports willRetry (the same fact that drives its reconnect
scheduling), the gateway store derives a `reconnecting` snapshot state
from it (everConnected && willRetry), and the app shell stays mounted
with an amber "Gateway connection lost - reconnecting" banner plus a
Retry now action while the client retries with backoff. The login gate
is reserved for first connects, credential rejections, and manual gate
submissions; event-gap recovery also no longer flashes the gate.
createApplicationGateway moved from bootstrap.ts to gateway-store.ts
with an injectable client factory for direct behavior tests. Adds the
previously unstyled `.callout.warn` variant and a "Connection loss and
reconnect" docs section.
Fixes#100475
* chore(i18n): regenerate control-ui locale bundles for connection banner strings
* chore(control-ui): unbreak CI - add reconnecting to overlays snapshot fixture, regenerate docs map
* chore(i18n): re-sync locale metadata after rebase onto refreshed main locales
* chore(i18n): refresh raw-copy baseline after rebase