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
* feat(ios): read-only offline cache for chat sessions and transcripts
Cache-first cold open for the iOS chat tab: the last known transcript and
session list render immediately from a local SQLite cache, then live gateway
history replaces them wholesale through the existing reconciliation path.
When the gateway is unreachable, recent sessions and transcripts stay
browsable read-only; sending remains gated by the existing connection state.
- New OpenClawChatTranscriptCache protocol seam plus SQLite-backed store in
OpenClawChatUI (raw SQLite3, no new dependencies), scoped per gateway
stableID so transcripts never leak across paired gateways.
- Bounds: 50 sessions, 50 transcripts, 200 messages per session; text rows
only (attachment/binary payloads and tool arguments are stripped).
- Disposable cache: schema versioned via user_version; any open/schema/decode
mismatch drops and rebuilds silently. File protection
completeUntilFirstUserAuthentication on iOS.
- View model pre-paints from cache only until a live response applies
(hasAppliedLiveHistory/hasAppliedLiveSessions guards); write-through is
chained and off the render path.
- iOS wiring keys the chat view model identity on transport mode plus cache
gateway ID so switching paired gateways rebuilds the view model.
Part of #100194
* fix(ios): harden offline transcript cache
* fix(ios): expose offline cached sessions
* fix(ios): isolate transcript caches by gateway
* chore(i18n): sync native inventory
* fix(ios): allow cache extension to replace messages
* fix(diffs): share SSR preloads and repair language-pack hydration
Render viewer and file documents from a single @pierre/diffs SSR preload
per file (mode=both previously ran the full diff+highlight pipeline
twice; 651ms -> 303ms on an 8-file patch), apply the file-mode font bump
as a document-level override, and keep hydration payloads
variant-faithful.
Fix the language-pack runtime downgrading pack-only languages to plain
text at hydration by defining a per-target build flag and forwarding it
to payload normalization.
Also: case-insensitive language hints, identical before/after
short-circuit with details.changed, patch input failures classified as
tool input errors, canonical config values now win over deprecated
aliases, hash-pinned viewer runtime served immutable, truthful
browser-vs-render errors, timing-safe artifact token compare, unref
idle browser timer.
* docs(changelog): link diffs rendering entry to PR
* test(diffs): narrow manifest validation results before value access
* test(tooling): allowlist diffs viewer-client define suppression
* fix(slack): normalize react emoji glyphs and clarify member-info userId param
Slack's reactions.add/remove only accept shortcode names, never a raw
Unicode glyph, but the react action's emoji param had no description
steering models away from passing one, so calls like
emoji="✅" failed with invalid_name. Consolidates the glyph-to-shortcode
map that already existed privately in the ack-reaction dispatch path into
the shared normalizeSlackEmojiName export in actions.ts, the layer that
owns the actual Slack API calls, so the message-tool react action gets the
same normalization.
Also tightens the generic userId param description so models stop trying
target on member-info, which has no target mode and requires userId
directly.
* fix(slack): preserve emoji reaction semantics
* fix(slack): default reactions to inbound message
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(chat-ui): redesign composer bottom bar with split Talk control and reasoning-effort chip
Removes the toolbar divider so the composer frame is the only chrome, merges
the Talk toggle and Talk settings gear into a mic+caret split control, shows
the effective reasoning level as a subtle pill next to the model name
(accent-washed only on session override), right-aligns the model/settings
cluster next to Send, and softens active/selected highlight states across the
bar. The combined model menu and reasoning fly-out now anchor right so they
stay on screen.
* fix(chat-ui): keep mobile composer controls usable
* fix(chat-ui): keep reasoning controls in mobile viewport
* fix(chat-ui): simplify composer placeholder
* chore(i18n): refresh composer locale metadata
* feat(logbook): automatic work journal plugin with a plugin-contributed Control UI tab
Squash of PR #99930 work for rebase onto the Control UI route refactor:
- extensions/logbook: Dayflow-style capture -> observations -> timeline cards
pipeline with SQLite store, node capture commands, standup/ask, retention
- plugin SDK/gateway seam: surface "tab" Control UI descriptors projected
into hello-ok controlUiTabs (scope-filtered, deterministic order)
- Control UI: dynamic plugin tabs with bundled Logbook view
- docs, tests, labeler wiring
* feat(ui): port plugin tabs and Logbook to the route-owned Control UI architecture
- shared /plugin route carries the tab id in the query (?id=<tab>), matching
the router's exact-path contract
- openclaw-plugin-page renders bundled views (Logbook), sandboxed plugin
frames (descriptor path), or the unavailable card
- sidebar renders hello controlUiTabs after each group's static routes
- Logbook view/controller live under ui/src/pages/plugin/
* fix(ui): namespace plugin tabs by pluginId to prevent cross-plugin tab id collisions
* fix(logbook): prefer app capture nodes and rotate off failing nodes
* fix(plugins): reject protocol-relative Control UI tab paths
* fix(logbook): harden automatic journal
* docs(changelog): remove maintainer self-credit
* chore(ui): refresh locale metadata after rebase
* fix(logbook): preserve analysis window boundaries
* fix(logbook): align status privacy and timezone
* fix(ui): stop hidden plugin tab polling
Wires replyOptions.onModelSelected so the resolved provider/model and
thinking level for each turn (including after fallback) are sent as
author_model / author_thinking fields on activity rows and the final
reply. Servers without these columns ignore the unknown JSON fields, so
the wire shape is backward compatible; servers that persist them get
per-message attribution.
- docs: document agentActivity option, the non-inherited agent_activity:write
scope, default-off and best-effort degradation behavior
- activity: type the enqueue catch parameter as unknown (lint lane)
- activity: treat toolCallId as opaque; normalize only itemId lane prefixes
- activity: skip identical/stale-shorter commentary snapshots so no
redundant PATCHes queue
- http-client: fail fast when createActivityMessage has no channel or
conversation target; narrow mocked request bodies before JSON.parse
* feat(control-ui): session grouping with drag & drop and channel categorization
* fix(control-ui): restrict session drag&drop to private session-key payloads
* chore(i18n): translate session grouping strings across control ui locales
* chore(protocol): regenerate swift gateway models for session category
* chore(protocol): regenerate swift gateway models for session category