Rename "Nodes and devices" to "Nodes and media" and split into
two subgroups for better navigation:
- Media capabilities: media-understanding, images, audio, camera, tts
- Node features: talk, voicewake, location-command
- Move plugins/voice-call from Channels > Messaging platforms to
Tools & Plugins > Plugins (it's a plugin, not a channel integration)
- Add install/clawdock to Install > Containers nav (was orphaned)
Add consistent Related sections to 17 channel pages that had none,
linking to: Channels Overview, Pairing, Groups, Channel Routing, Security.
Add Groups and Security links to 4 channel pages (discord, slack,
telegram, whatsapp) that already had partial Related sections.
New page: docs/automation/index.md — single entry point for all automation
mechanisms (heartbeat, cron, tasks, hooks, standing-orders, webhooks) with
a decision flowchart and comparison table.
Add "Related" sections to 5 high-traffic pages that were dead ends:
- gateway/heartbeat.md → links to tasks, cron-vs-heartbeat, timezone, troubleshooting
- concepts/session.md → links to multi-agent, tasks, channel-routing
- concepts/multi-agent.md → links to channel-routing, subagents, ACP, presence, session
- concepts/agent-loop.md → links to tools, hooks, compaction, exec-approvals, thinking
- concepts/timezone.md → links to heartbeat, cron-jobs, date-time
Add automation/index to Mintlify nav as first item in Automation group.
* fix(agents): dispose bundled MCP runtime after local runs
* fix(agents): scope bundle MCP cleanup to local one-shots
* fix(agents): dispose bundle MCP after local runs
* docs(changelog): note local bundle MCP cleanup fix
Place a sentinel object in the loadedFacadeModules cache before the Jiti
sync load begins. Re-entrant calls (caused by circular facade references
from constant exports evaluated at module-evaluation time) now receive the
sentinel instead of recursing infinitely. Once the real module finishes
loading, Object.assign() back-fills the sentinel so any references
captured during the circular load phase see the final exports.
The Jiti load is wrapped in try/catch: on failure the sentinel is removed
from the cache so that subsequent retry attempts re-execute the load
instead of silently returning an empty object. The function returns the
sentinel (not the raw loaded module) to guarantee a single object identity
for all callers, including those that captured a reference during the
circular load phase.
Also tightens the generic constraint from <T> to <T extends object> so
Object.assign() is type-safe, and propagates the constraint to the
test-utils callers in bundled-plugin-public-surface.ts.
Fixes#57394
Link to /automation/tasks from all pages that mention subagent runs,
ACP runs, or detached background work:
- tools/subagents.md: note that each sub-agent run is tracked as a background task
- tools/acp-agents.md: note that ACP session spawns are tracked as background tasks
- cli/index.md: link tasks section to doc page, add tasks audit subcommand
- concepts/queue.md: note that detached lane runs are tracked as background tasks
- gateway/configuration-reference.md: cron section cross-ref to tasks
- help/faq.md: add tasks link to sub-agent offloading FAQ answer
- Rewrite docs/automation/tasks.md to match repo doc style:
- Add cross-ref blockquote, TL;DR bullets, proper section pacing
- Replace ASCII lifecycle with a mermaid stateDiagram-v2
- Add <Tip> for heartbeat wake behavior
- Split CLI into individual subcommand sections (list/show/cancel/notify/audit)
- Tighten prose to 3-6 lines per section before a break
- Add "Status integration" section for task pressure
- Restructure "How tasks relate" with shorter, punchier subsections
- Fix comparison table link in cron-vs-heartbeat.md
New page: docs/automation/tasks.md — comprehensive reference for the task
system covering lifecycle, delivery, notifications, audit, CLI commands,
storage, maintenance, and how tasks relate to cron/heartbeat/sessions.
- Add to Mintlify navigation (docs.json) under Automation group
- Clean up engineer's earlier scattered additions in cron-jobs.md,
cron-vs-heartbeat.md, and heartbeat.md to be concise and link to the
new canonical tasks page
- Replace verbose inline explanations with cross-reference links
* fix(slack): complete interactive block delivery
Related #12602
Related #49528
* docs(changelog): add Slack interactive delivery note
Related #12602
* fix(slack): add reply-blocks helper and tighten directives
Related #12602
Related #49528
* fix(slack): scope style parsing and recheck merged blocks
Related #12602
Related #49528
* fix(agents): classify Anthropic "unexpected error" api_error as transient (#57010)
Anthropic sometimes returns api_error payloads with message "An unexpected
error occurred while processing the response" during mid-stream failures.
This was not matched by API_ERROR_TRANSIENT_SIGNALS_RE, causing the error
to surface as terminal instead of triggering retry/fallback.
Add "unexpected error" to the transient signal regex. This follows the
same pattern as prior fixes for "Internal server error" (#23193) and
overloaded_error 529 (#34535).
Closes#57010
* chore(changelog): add anthropic failover entry
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(config): prevent AJV schema defaults from leaking into persisted config
Fixes#56772. Ensures that channel and plugin AJV validations respect the applyDefaults option, preventing runtime defaults from being written to openclaw.json during doctor/update flows.
* test: address review feedback on #56772 fix
- Split validation.channel-metadata.test.ts into applyDefaults true/false cases (fixes CI)
- Update io.write-config.test.ts regression test to use a mock plugin registry, ensuring it actually exercises the AJV default injection path
* fix(config): revert applyDefaults passthrough to prevent required-field regression
Codex-connector correctly identified that BlueBubbles channel schema marks
enrichGroupParticipantsFromContacts as both default:true and required.
Passing applyDefaults:false during write validation would cause required
checks to fail, breaking writeConfigFile entirely.
Reverted validation.ts to always use applyDefaults:true for channel/plugin
AJV validation. The protection against default leakage into persisted config
is fully handled by the persistCandidate change in io.ts (cfgToWrite uses
the pre-validation merge-patched value, not validated.config).
Updated validation.channel-metadata.test.ts to reflect this architecture.
* fix(config): apply legacy web-search normalization to persistCandidate
* fix: stabilize config default-leak landing tests (#56834) (thanks @openperf)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* feat(openai): forward text verbosity across responses transports
* fix(openai): remove stale verbosity rebase artifact
* chore(changelog): add openai text verbosity entry
---------
Co-authored-by: Ubuntu <ubuntu@vps-1c82b947.vps.ovh.net>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Problem: When LLM stops responding, the agent hangs for ~5 minutes with no feedback.
Users had to use /stop to recover.
Solution: Add idle timeout detection for LLM streaming responses.
formatTokensCompact() computed cache rate using totalTokens as the
denominator. Legacy rows with undersized totalTokens produced
impossible values like 120%. Use inputTokens + cacheRead + cacheWrite
when prompt-side fields are available, falling back to
max(totalTokens, cacheRead + cacheWrite) for legacy data.
Fixes#26643
- Merge 3 duplicate trust-model sections into one (Scope first + Deployment/host trust)
- Promote "What the audit checks" from h3 to h2 (standalone topic, not child of Shared inbox)
- Add "On this page" navigation links at the top for the 1200+ line page
- Rebuild --auth-choice enum from BuiltInAuthChoice source (remove ollama, minimax-api,
minimax-api-lightning; add 15+ missing choices including deepseek, copilot, volcengine, etc.)
- Fix agent command: --verbose accepts on|off not on|full|off; add missing --agent, --reply-to,
--reply-channel, --reply-account, -m/-t short flags; fix --thinking description
- Add tasks command to command tree and body (list/show/notify/cancel)
- Mark anthropic-cli as deprecated legacy alias
- Remove stale ollama references from custom-base-url/custom-model-id
Replace GitHub-flavor > [!WARNING] with Mintlify <Warning> component.
The old syntax renders as a plain blockquote in Mintlify, hiding the most
safety-critical content on the page.
- Future Events: session:start/session:end are live plugin hooks, clarify they are planned for internal event stream only
- Log example: session-memory listens to command:new AND command:reset, not just command:new
* gateway: fix /v1/responses tool schema to use flat Responses API format
* gateway: fix remaining stale wrapped-format tools in parity tests
* gateway: propagate strict flag through extractClientTools normalization
* fix(gateway): cover responses tool boundary
* Delete docs/internal/vincentkoc/2026-03-30-pr-57166-responses-tool-schema-followup.md
---------
Co-authored-by: Michel Belleau <mbelleau@Michels-MacBook-Pro.local>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix: wire memorySearch.extraPaths to QMD indexing
The 'agents.defaults.memorySearch.extraPaths' config field was documented
to add extra directories to the memory index, but the paths were never
actually passed to the QMD backend. Only 'memory.qmd.paths' worked.
This fix reads extraPaths from the memorySearch config and maps them
to QMD custom path collections, so users can simply configure:
memorySearch:
extraPaths:
- odd-vault
- /Users/odd/workspace
- /Users/odd/docs
And have those directories indexed alongside the default memory files.
Closes#57302
* fix: handle per-agent memorySearch.extraPaths overrides + add tests
- Read per-agent overrides from agents.list[].memorySearch.extraPaths
- Agent-specific overrides take priority over defaults
- Falls back to defaults when agent has no overrides
- Added 3 test cases for the feature
* fix: merge defaults + agent overrides instead of replacing
* fix: remove any types from tests, fix merge behavior assertion
* fix(memory): merge qmd extra path collections
* fix(memory): normalize qmd extra path resolution
* fix(memory): type qmd extra path merge
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
resolveCronPayloadOutcome() collapsed announce delivery to the last
deliverable payload. Replace with pickDeliverablePayloads() that
preserves all successful text payloads. Error-only runs fall back to
the last error payload only.
Extract shared isDeliverablePayload() helper. Keep
deliveryPayloadHasStructuredContent scoped to the last payload
to preserve downstream finalizeTextDelivery safeguards.
Fixes#13812
* fix: QMD 2.0 mcporter compatibility with v1 fallback [AI-assisted]
QMD 2.0 unified all search modes under a single 'query' MCP tool
with typed sub-queries, replacing search/vector_search/deep_search.
- Default to QMD 2.0 'query' tool with {searches: [...]} format
- Auto-detect version on first call and cache for the session
- Fall back to v1 tool names if 'query' is not found
- Backwards compatible: v1 users get one retry then cached
AI-assisted: Built with Claude (Opus 4.6) via OpenClaw. Fully tested
against QMD 2.0 with mcporter 0.7.3 daemon. v1 fallback path not
live-tested (no v1 instance available). Code reviewed and understood.
* test: add QMD v2 tool format and v1 fallback tests
- Verify mcporter bridge uses 'query' tool with {searches: [...]} format (v2)
- Verify fallback to 'deep_search' with {query, limit} format when v2 not found
- Verify v1 fallback logs a warning for visibility
* fix: address review feedback — multi-collection v1 fallback + test cleanup
- Fix multi-collection v1 fallback: resolve effectiveTool at the top
of runQmdSearchViaMcporter so stale 'query' tool names from the
loop are corrected once qmdMcpToolVersion is set to 'v1'
- Assert callCount in v1 fallback test (one v2 attempt + one v1 retry)
- Remove spurious global state reset (qmdMcpToolVersion is per-instance)
* docs: correct version references — breaking change was QMD 1.5, not 2.0
The MCP tool removal (search/vector_search/deep_search → query) happened
in QMD 1.5, not 2.0. QMD 2.0 was the SDK/library refactor.
Updated all comments, test names, and documentation to reflect this.
* fix: respect searchMode when building v2 mcporter queries
When searchMode is 'search' (BM25), only send lex sub-query.
When 'vsearch', only send vec. Default 'query' sends all three
(lex + vec + hyde) for full hybrid search with reranking.
Previously all three sub-queries were always sent regardless of
the configured searchMode, which could trigger unnecessary vector
embedding and HyDE LLM work on setups explicitly requesting
lexical-only search.
Addresses Codex P2 review feedback.
* docs: correct to QMD 1.1.0 — that's the actual version that removed the tools
Per CHANGELOG.md, MCP tools search/vector_search/deep_search were removed
in QMD 1.1.0 (2026-02-20), not 1.5 (which doesn't exist). Versions go
1.0.7 → 1.1.0 → 1.1.1 → 1.1.2 → 1.1.5 → 1.1.6 → 2.0.0.
* fix: remove redundant v1 guard (race condition) + tighten error matching
1. Remove qmdMcpToolVersion !== 'v1' guard from catch block. It's
redundant (effectiveTool === 'query' already prevents infinite retry)
and introduces a race condition: concurrent searches that both probe
with 'query' while version is null would fail after the first sets
version to 'v1'.
2. Tighten isToolNotFoundError regex to require 'Tool' near 'not found'
(within 40 chars, no sentence boundary). Prevents false-positives
when user query text in the mcporter args contains both words.
Addresses Greptile P1 (concurrent-search race) and Codex P2
(overly broad error matching).
* fix: address claude code review — type safety, minScore docs, explicit switch
- Restore type union for tool param instead of bare string
- Add comment explaining minScore omission for v2 (QMD 1.1+ uses
its own reranking pipeline, no minScore parameter)
- Make buildV2Searches switch exhaustive with explicit case 'query'
* fix: resolve CI failures — oxfmt formatting + TypeScript type errors
- Run oxfmt to fix formatting issues
- Add union return type to resolveQmdMcpTool() and
runMcporterAcrossCollections() tool param to satisfy tsc
(string was not assignable to the union type)
* fix(memory): align qmd query collection filters
* fix(memory): narrow qmd missing-tool fallback detection
* fix(memory): ignore qmd timeout text for v1 fallback
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix: inject anthropic service_tier for OAuth auth
Remove the OAuth-token exclusion from createAnthropicFastModeWrapper
so that sk-ant-oat-* requests receive service_tier injection, matching
Claude Code CLI behavior and reducing avoidable 529 overload cascades.
isAnthropicOAuthApiKey remains in use in createAnthropicBetaHeadersWrapper
for beta header selection — it is not dead code after this change.
Fixes#55758
* docs(changelog): note anthropic oauth service tier fix
* Update CHANGELOG.md
---------
Co-authored-by: Cypherm <28184436+Cypherm@users.noreply.github.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
The transcript validator regex rejected namespaced MCP tool names
(e.g., vigil-harbor:memory_status) because colon wasn't in the
allowed character set. Tool call blocks were silently dropped during
session repair, breaking tool-call/result pairing.
Also closes a resource leak: if client.connect() throws after the
transport is instantiated, the transport is now explicitly closed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
MCP tools are now prefixed with their server name (e.g., vigil-harbor:memory_status)
to prevent collisions between tools from different MCP servers and built-in tools.
Adds SSE and StreamableHTTP transport support alongside existing stdio, enabling
connection to remote MCP servers via URL-based config with optional custom headers
and env var substitution. Includes config validation, session lifecycle management,
and 5 new tests for HTTP config edge cases.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Standalone MCP server that exposes OpenClaw plugin-registered tools
(e.g. memory-lancedb's memory_recall, memory_store, memory_forget)
to ACP sessions running Claude Code via acpx's MCP proxy mechanism.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add isTransientSqliteError() covering SQLITE_CANTOPEN, SQLITE_BUSY,
SQLITE_LOCKED, and SQLITE_IOERR via named codes, numeric errcodes
(node:sqlite), and message-string fallback. Combine with existing
network transient check so both families are treated as non-fatal
in the global unhandled rejection handler.
Prevents crash loop under launchd on macOS when SQLite files are
temporarily unavailable.
Fixes#34678
Extends the invalid-URL redaction to also scrub sensitive query parameters
(token, api_key, secret, access_token, etc.) using the same param list as
the valid-URL description path. Adds tests for both query param and
credential redaction in error reasons.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Properly convert Headers instances to plain objects in eventSourceInit.fetch
so SDK-generated headers (e.g. Accept: text/event-stream) are preserved
while user-configured headers still take precedence.
- Redact potential credentials from invalid URLs in error reasons to prevent
secret leakage in log output.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Address Greptile P1/P2 review feedback:
- Fix header spread order so user-configured auth headers take precedence
over SDK-internal headers in SSE eventSourceInit.fetch
- Add password, pass, auth, client_secret, refresh_token to the
sensitive query-param redaction set in describeSseMcpServerLaunchConfig
- Add tests for redaction of all sensitive params and embedded credentials
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wire the shared resolveReactionMessageId helper into the WhatsApp
channel adapter, matching the pattern already used by Telegram, Signal,
and Discord. The model can now react to the current inbound message
without explicitly providing a messageId.
Safety guards:
- Only falls back to context when the source is WhatsApp
- Suppresses fallback when targeting a different chat (normalized comparison)
- Throws ToolInputError (400) instead of plain Error (500) when messageId
is missing, preserving gateway error mapping
* fix: canonicalize session keys at write time to prevent orphaned sessions (#29683)
resolveSessionKey() uses hardcoded DEFAULT_AGENT_ID="main", but all read
paths canonicalize via cfg. When the configured default agent differs
(e.g. "ops" with mainKey "work"), writes produce "agent:main:main" while
reads look up "agent:ops:work", orphaning transcripts on every restart.
Fix all three write-path call sites by wrapping with
canonicalizeMainSessionAlias:
- initSessionState (auto-reply/reply/session.ts)
- runWebHeartbeatOnce (web/auto-reply/heartbeat-runner.ts)
- resolveCronAgentSessionKey (cron/isolated-agent/session-key.ts)
Add startup migration (migrateOrphanedSessionKeys) to rename existing
orphaned keys to canonical form, merging by most-recent updatedAt.
* fix: address review — track agent IDs in migration map, align snapshot key
P1: migrateOrphanedSessionKeys now tracks agentId alongside each store
path in a Map instead of inferring from the filesystem path. This
correctly handles custom session.store templates outside the default
agents/<id>/ layout.
P2: Pass the already-canonicalized sessionKey to getSessionSnapshot so
the heartbeat snapshot reads/restores use the same key as the write path.
* fix: log migration results at all early return points
migrateOrphanedSessionKeys runs before detectLegacyStateMigrations, so
it can canonicalize legacy keys (e.g. "main" → "agent:main:main") before
the legacy detector sees them. This caused the early return path to skip
logging, breaking doctor-state-migrations tests that assert log.info was
called.
Extract logMigrationResults helper and call it at every return point.
* fix: handle shared stores and ~ expansion in migration
P1: When session.store has no {agentId}, all agents resolve to the same
file. Track all agentIds per store path (Map<path, Set<id>>) and run
canonicalization once per agent. Skip cross-agent "agent:main:*"
remapping when "main" is a legitimate configured agent sharing the store,
to avoid merging its data into another agent's namespace.
P2: Use expandHomePrefix (environment-aware ~ resolution) instead of
os.homedir() in resolveStorePathFromTemplate, matching the runtime
resolveStorePath behavior for OPENCLAW_HOME/HOME overrides.
* fix: narrow cross-agent remap to provable orphan aliases only
Only remap agent:main:* keys where the suffix is a main session alias
("main" or the configured mainKey). Other agent:main:* keys — hooks,
subagents, cron sessions, per-sender keys — may be intentional
cross-agent references and must not be silently moved into another
agent's namespace.
* fix: run orphan-key session migration at gateway startup (#29683)
* fix: canonicalize cross-agent legacy main aliases in session keys (#29683)
* fix: guard shared-store migration against cross-agent legacy alias remap (#29683)
* refactor: split session-key migration out of pr 30654
---------
Co-authored-by: Your Name <your_email@example.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(agents): preserve original task prompt on model fallback for new sessions
* fix(agents): use dynamic transcript check for sessionHasHistory on fallback retry
Address Greptile review feedback: replace the static !isNewSession flag
with a dynamic sessionFileHasContent() check that reads the on-disk
transcript before each fallback retry. This correctly handles the edge
case where the primary model completes at least one assistant-response
turn (flushing the user message to disk) before failing - the fallback
now sends the recovery prompt instead of duplicating the original body.
The !isNewSession short-circuit is kept as a fast path so existing
sessions skip the file read entirely.
* fix(agents): address security vulnerabilities in session fallback logic
Fixes three medium-severity security issues identified by Aisle Security Analysis on PR #55632:
- CWE-400: Unbounded session transcript read in sessionFileHasContent()
- CWE-400: Symlink-following in sessionFileHasContent()
- CWE-201: Sensitive prompt replay to a different fallback provider
* fix(agents): use JSONL parsing for session history detection (CWE-703)
Replace bounded byte-prefix substring matching in sessionFileHasContent()
with line-by-line JSONL record parsing. The previous approach could miss
an assistant message when the preceding user content exceeded the 256KB
read limit, causing a false negative that blocks cross-provider fallback
entirely.
* fix(agents): preserve fallback prompt across providers
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(acpx): read ACPX_PINNED_VERSION from package.json instead of hardcoding
The hardcoded ACPX_PINNED_VERSION ("0.1.16") falls out of sync with the bundled acpx version in package.json every release, causing ACP runtime to be marked unavailable due to version mismatch (see #43997).
* Validate and sanitize ACPX version retrieval
Add validation for acpx version from package.json
Remove memory_search and memory_get from SUBAGENT_TOOL_DENY_ALWAYS.
These are read-only tools with no side effects that are essential for
multi-agent setups relying on shared memory for context retrieval.
Rationale:
- Read-only tools (memory_search, memory_get) have no side effects and
cannot modify state, send messages, or affect external systems
- Other read-only tools (read, web_search, web_fetch) are already
available to sub-agents by default
- Multi-agent deployments with shared knowledge depend on memory tools
for context retrieval
- The workaround (tools.subagents.tools.alsoAllow) works but requires
manual configuration that contradicts memorySearch.enabled: true
Fixes#55385
* gateway: prefer transcript model in sessions list
* gateway: keep live subagent model in session rows
* gateway: prefer selected model until runtime refresh
* gateway: simplify session model identity selection
* gateway: avoid transcript model fallback on cost-only reads
loadChannelOutboundAdapter (via createChannelRegistryLoader) was reading
from getActivePluginRegistry() — the unpinned active registry that gets
replaced whenever loadOpenClawPlugins() runs (config schema reads, plugin
status queries, tool listings, etc.).
After replacement, the active registry may omit channel entries or carry
them in setup mode without outbound adapters, causing:
Outbound not configured for channel: telegram
The channel inbound path already uses the pinned registry
(getActivePluginChannelRegistry) which is frozen at gateway startup and
survives all subsequent registry replacements. This commit aligns the
outbound path to use the same pinned surface.
Adds a regression test that pins a registry with a telegram outbound
adapter, replaces the active registry with an empty one, then asserts
loadChannelOutboundAdapter still resolves the adapter.
Fixes#54745Fixes#54013
* fix(imessage): prevent self-chat dedupe false positives (#47830)
Move echo cache remember() to post-send only, add early return when
inbound message ID doesn't match cached IDs (prevents text-based
false positives in self-chat), and reduce text TTL from 5s to 3s.
Three targeted changes to fix silent user message loss in self-chat:
1. deliver.ts: Remove pre-send remember() call — cache only reflects
successfully-delivered content, not pre-send full text.
2. echo-cache.ts: Skip text fallback when inbound has a valid message ID
that doesn't match any cached outbound ID. In self-chat, sender == target
so scopes collide; a user message with a fresh ID but matching text was
incorrectly dropped as an echo.
3. echo-cache.ts: Reduce text TTL from 5000ms to 3000ms — agent echoes
arrive within 1-2s, 5s was too wide.
Adds self-chat-dedupe.test.ts (7 tests) + updates deliver.test.ts.
BlueBubbles uses a different cache pattern — no changes needed there.
Closes#47830
* review(imessage): strip debug logs, bump echo TTL to 4s (#47830)
Bruce Phase 4 review changes:
- Remove all [IMSG-DEBUG] console.error calls from inbound-processing.ts
and monitor-provider.ts (23 lines, left over from Phase 2 debug deploy)
- Bump SENT_MESSAGE_TEXT_TTL_MS from 3s to 4s in echo-cache.ts to give
~2s margin above the observed 2.2s echo arrival time under load
- Update TTL tests to reflect 4s TTL (expired at 5s, live at 3s)
* fix(imessage): add dedupe comments and canary/compat/TTL tests
* fix(imessage): address review feedback on echo cache, shadowing, and test IDs
* refactor(imessage): hoist inboundMessageId to eliminate duplicate computation (#47830)
* fix(imessage): unify self-chat echo matching
* fix: use inbound guid for self-chat echo matching (#55359) (thanks @rmarr)
---------
Co-authored-by: Rohan Marr <rmarr@users.noreply.github.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(memory): build FTS index when no embedding provider is available
* fix(memory): trigger full reindex on provider→FTS-only transition
* fix(memory): return FTS-only keyword hits at default threshold
* fix: keep FTS-only memory hits at default threshold (#56473) (thanks @opriz)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(telegram): prevent polling watchdog from aborting in-flight message delivery
The polling-stall watchdog only tracked getUpdates timestamps to detect
network stalls. When the agent takes >90s to process a message (common
with local/large models), getUpdates naturally pauses, and the watchdog
misidentifies this as a stall. It then calls fetchAbortController.abort(),
which cancels all in-flight Telegram API requests — including the
sendMessage call delivering the agent's reply. The message is silently
lost with no retry.
Track a separate lastApiActivityAt timestamp that is updated whenever
any Telegram API call (sendMessage, sendChatAction, etc.) completes
successfully. The watchdog now only triggers when both getUpdates AND
all other API activity have been silent beyond the threshold, proving
the network is genuinely stalled rather than just busy processing.
Update existing stall test to account for the new timestamp, and add a
regression test verifying that recent sendMessage activity suppresses
the watchdog.
Fixes#56065
Related: #53374, #54708
* fix(telegram): guard watchdog against in-flight API calls
* fix(telegram): bound watchdog API liveness
* fix: track newest watchdog API activity (#56343) (thanks @openperf)
* fix: note Telegram watchdog delivery fix (#56343) (thanks @openperf)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
When approvals.exec.targets routes to a Telegram DM, the recipient
receives inline approval buttons but may not have explicit
channels.telegram.execApprovals configured. This adds a fallback
isTelegramExecApprovalTargetRecipient check so those DM recipients
can act on the buttons they were sent.
Includes accountId scoping for multi-bot deployments and 9 new tests.
* feat(ui): wire /steer slash command to sessions.steer RPC
* feat(ui): wire /steer (soft inject) and /redirect (hard restart) slash commands
* test: use generic subagent names in steer/redirect tests
* fix(ui): exempt steer/redirect from busy-queue and guard sessions.list failures
* fix(ui): skip 'all' wildcard in steer/redirect target resolution
* test: register slash-command-executor test in vitest config
* fix(ui): restrict steer target to subagent keys and active sessions
Address two review issues in resolveSteerTarget:
P2: Replace resolveKillTargets with a dedicated resolveSteerSubagent
that matches only on subagent key suffix or label, not agent id.
This prevents false-positive targeting when the first word collides
with an agent id (e.g. "/steer main refine plan").
P1: Filter out ended sessions (endedAt set) so stale subagents with
reused names are not targeted.
* fix(ui): use shared generateUUID for steer idempotency key
* fix: restore telegram test to upstream state (merge artifact)
* fix(ui): track redirected run so Abort works and concurrent sends are blocked
* fix(ui): skip run tracking when /redirect targets a subagent session
* fix(ui): block idle steer runs
* fix(ui): dedupe steer slash command
* fix(ui): show pending steer state
* fix: wire control-ui steer and redirect (#54625) (thanks @fuller-stack-dev)
* fix: tighten steer target resolution (#54625) (thanks @fuller-stack-dev)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(plugins): reuse active registry for sub-agent tool resolution
* test(plugins): harden resolveRuntimePluginRegistry with per-field, caller-shape, and cold-start tests
Add 11 regression tests covering:
- R1: Per-field isolation (coreGatewayHandlers, includeSetupOnlyChannelPlugins,
preferSetupRuntimeForChannelPlugins each independently prevent fallback;
empty onlyPluginIds[] treated as non-gateway-scoped)
- R2: Caller-shape regression (tools.ts, memory-runtime.ts,
channel-resolution.ts shapes fall back; web-search-providers.runtime.ts
with onlyPluginIds does not)
- R3: Cold-start path (null active registry falls through to loadOpenClawPlugins)
Add debug logging to resolveRuntimePluginRegistry recording which exit path
was taken (no-options, cache-key-match, non-gateway-scoped fallback, fresh load).
* refactor: simplify plugin registry resolution tests and trim happy-path debug logs
* fix(plugins): address review comments on registry fallback
- Fix cold-start test assertion: loadOpenClawPlugins always activates
the registry (shouldActivate defaults to true), so getActivePluginRegistry()
is not null after the call. Updated assertion to match actual behavior.
- Add safety comment documenting why the non-gateway-scoped fallback is
safe despite cache-key mismatch: single-gateway-per-process model means
sub-agents share workspaceDir, config, and env with the gateway.
* test(plugins): restructure per-field isolation tests to avoid load timeouts
Test isGatewayScopedLoad directly instead of going through the full
resolveRuntimePluginRegistry path which triggers expensive plugin
discovery. This fixes the includeSetupOnlyChannelPlugins test timing
out in CI while providing more precise coverage of the predicate.
* fix(plugins): expand safety comment to address startup-scoped registry concern
* fix(plugins): scope subagent registry reuse to tool loading
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Fixes#46185.
Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm test -- extensions/line/src/markdown-to-line.test.ts src/tts/prepare-text.test.ts
Note: `pnpm check` currently fails on unchanged `extensions/microsoft/speech-provider.test.ts` lines 108 and 139 on the rebased base, outside this PR diff.
Verified:
- pnpm test -- extensions/microsoft/speech-provider.test.ts extensions/microsoft/tts.test.ts
Notes:
- Rebases and refactor-port completed onto current main.
- No required GitHub checks were reported for this branch at merge time.
Co-authored-by: Extra Small <littleshuai.bot@gmail.com>
When re-splitting CJK-heavy segments at chunking.tokens, check whether the
slice boundary falls on a high surrogate (0xD800–0xDBFF) and if so extend
by one code unit to keep the pair intact. Prevents producing broken
surrogate halves for CJK Extension B+ characters (U+20000+).
Add test verifying no lone surrogates appear when splitting lines of
surrogate-pair characters with an odd token budget.
Addresses third-round Codex P2 review comment.
- Two-pass line splitting: first slice at maxChars (unchanged for Latin),
then re-split only CJK-heavy segments at chunking.tokens. This preserves
the original ~800-char segments for ASCII lines while keeping CJK chunks
within the token budget.
- Narrow surrogate-pair adjustment to CJK Extension B+ range (D840–D87E)
only, so emoji surrogate pairs are not affected. Mixed CJK+emoji text
is now handled consistently regardless of composition.
- Add tests: emoji handling (2), Latin backward-compat long-line (1).
Addresses Codex P1 (oversized CJK segments) and P2s (Latin over-splitting,
emoji surrogate inconsistency).
- Use code-point length instead of UTF-16 length in estimateStringChars()
so that CJK Extension B+ surrogate pairs (U+20000+) are counted as 1
character, not 2 (fixes ~25% overestimate for rare characters).
- Change long-line split step from maxChars to chunking.tokens so that
CJK lines are sliced into token-budget-sized segments instead of
char-budget-sized segments that produce ~4x oversized chunks.
- Add tests for both fixes: surrogate-pair handling and long CJK line
splitting.
Addresses review feedback from Greptile and Codex bots.
The QMD memory system uses a fixed 4:1 chars-to-tokens ratio for chunk
sizing, which severely underestimates CJK (Chinese/Japanese/Korean) text
where each character is roughly 1 token. This causes oversized chunks for
CJK users, degrading vector search quality and wasting context window space.
Changes:
- Add shared src/utils/cjk-chars.ts module with CJK-aware character
counting (estimateStringChars) and token estimation helpers
- Update chunkMarkdown() in src/memory/internal.ts to use weighted
character lengths for chunk boundary decisions and overlap calculation
- Replace hardcoded estimateTokensFromChars in the context report
command with the shared utility
- Add 13 unit tests for the CJK estimation module and 5 new tests for
CJK-aware memory chunking behavior
Backward compatible: pure ASCII/Latin text behavior is unchanged.
Closes#39965
Related: #40216
Webhook channels (LINE, Zalo, Nextcloud Talk, BlueBubbles) are
incorrectly flagged as stale-socket during quiet periods because
snapshot.mode is always undefined, making the mode !== "webhook"
guard in evaluateChannelHealth dead code.
Add mode: "webhook" to each webhook plugin's describeAccount and
propagate described.mode in getRuntimeSnapshot.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
collectStatusIssues previously checked account.channelAccessToken directly,
but this field is stripped by projectSafeChannelAccountSnapshotFields for
security. This caused 'openclaw status' to always report WARN even when the
token is valid and the LINE provider starts successfully.
Use account.configured instead, which is already computed by
buildChannelAccountSnapshot and correctly reflects whether credentials
are present. This is consistent with how other channels (e.g. Telegram)
implement their status checks.
Fixes#45693
Pad both buffers to equal length before constant-time comparison.
Key fix: call timingSafeEqual unconditionally and store the result
before the && length check, ensuring the constant-time comparison
always runs regardless of buffer lengths. This avoids JavaScript's
&& short-circuit evaluation which would skip timingSafeEqual on
length mismatches, preserving the timing side-channel.
Changes:
- Pad hash and signature buffers to maxLen before comparison
- Store timingSafeEqual result before combining with length check
- Add explanatory comment about the short-circuit avoidance
Build a topic-qualified routing target (telegram:<chatId>:topic:<threadId>)
for native commands in forum groups so /new and /reset stay scoped to
the active topic instead of falling back to General.
General topic (threadId=1) correctly falls through to the base chat
target since Telegram rejects message_thread_id=1 on sends.
Add regression tests for topic routing and General topic edge case.
Fixes#35963
Wrap the embedded agent stream to catch 'Unhandled stop reason: ...'
errors from the provider adapter and convert them into structured
assistant error messages instead of crashing the agent run.
Covers all unknown stop reasons so future provider additions don't
crash the runner. The wrapper becomes a harmless no-op once the
upstream dependency handles them natively.
Fixes#43607
Filter whitespace-only text chunks at the bot delivery fan-in before
they reach sendTelegramText(). Covers normal text replies, follow-up
text, and voice fallback text paths.
Media-only replies are unaffected. message_sent hook still fires with
success: false for suppressed empty replies.
Fixes#37278
Add shared isSilentReplyPayloadText() detector that catches both bare
NO_REPLY tokens and JSON {"action":"NO_REPLY"} envelopes. Apply at the
reply directive parser, reply normalizer, and embedded agent payload
builder so the control payload is stripped before any channel sees it.
Preserves media when text is only a silent control envelope.
Fixes#37727
Replace proportional text estimate with binary search for the largest
text prefix whose rendered Telegram HTML fits the character limit, then
split at the last whitespace boundary within that verified prefix.
Single words longer than the limit still hard-split (unavoidable).
Markdown formatting stays balanced across split points.
Fixes#36644
Add shared normalizeTelegramReplyToMessageId() that rejects non-numeric,
NaN, and mixed-content strings before they reach the Telegram Bot API.
Apply at all four API sinks: direct send, bot delivery, draft stream,
and bot helpers.
Prevents GrammyError 400 when non-numeric values from session metadata
slip through typed boundaries.
Fixes#37222
Sanitize message text at the debounce enqueue boundary and add an
independent guard in combineDebounceEntries(). Prevents TypeError when
a queued entry has null text that reaches .trim() during flush.
Add regression test: enqueue null-text entry alongside valid message,
verify flush completes without error and valid message is delivered.
Fixes#35777
The forward-compat resolver hardcoded 'google' as the provider ID for
template lookup, so alias providers (google-vertex, google-gemini-cli)
could not find matching templates. Pass the actual provider ID from the
runtime context and add a templateProviderId fallback for cross-provider
template resolution.
Also fix flash-lite prefix ordering — check 'gemini-3.1-flash-lite'
before 'gemini-3.1-flash' to prevent misclassification.
Add regression tests for pro, flash, and flash-lite across provider
aliases.
Fixes#36111
The generated zsh completion script called compdef at source time,
which fails with 'command not found: compdef' when loaded before
compinit. Replace with a deferred registration that tries immediately,
and if compdef is not yet available, queues a self-removing precmd hook
that retries on first prompt.
Handles repeated sourcing (deduped hook entry) and shells that never
run compinit (completion simply never registers, matching zsh model).
Add real zsh integration test verifying no compdef error on source and
successful registration after compinit.
Fixes#14289
hasWebSearchKey() was hardcoded to only check Brave and Perplexity
credentials. Replace with provider-aware check using
resolveBundledPluginWebSearchProviders() so Gemini, Grok/XAI, Kimi,
Moonshot, and OpenRouter credentials are recognized by the audit.
Add focused regression tests for each provider.
Fixes#34509
Remove circular self-link in English FAQ and dead anchor reference in
zh-CN FAQ. Both FAQ sections already contain the full workaround inline,
so the cross-references added no value and were never backed by a valid
target in troubleshooting.md.
Fixes#36970
* fix: display model name instead of ID in Telegram model selector (#56165)
* fix(telegram): scope model display names by provider
Signed-off-by: sallyom <somalley@redhat.com>
---------
Signed-off-by: sallyom <somalley@redhat.com>
Co-authored-by: sallyom <somalley@redhat.com>
* feat: add support for extra headers in Tavily API requests
* test(tavily-client): add unit tests for X-Client-Source header in API calls
* fix(tavily): add client source attribution (#55335) (thanks @lakshyaag-tavily)
---------
Co-authored-by: Nimrod Gutman <nimrod.gutman@gmail.com>
* [feat]Multiple nodes session context isolated from each other
* feat(android): Multiple nodes session context isolated from each other
* feat(android): Multiple nodes session context isolated from each other
* feat(android): Multiple nodes session context isolated from each other
* fix(android): isolate device chat defaults
---------
Co-authored-by: lixuankai <lixuankai@oppo.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
- Add blockStreaming and blockStreamingCoalesceDefaults to MSTeams channel plugin (was the only channel missing it)
- Wire disableBlockStreaming flag in reply dispatcher from config
- Flush pending messages immediately during generation when blockStreaming is enabled
- Add comprehensive tests for schema validation and progressive flush behavior
Refs #56041
* fix: preserve indentation when stripping reply directives
* fix: preserve word boundaries when stripping reply directives
* fix: drop separator space after leading reply directives
* fix: preserve indentation when stripping reply directives (#55960) (thanks @Nanako0129)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
The tokenize() function only matched [a-z0-9_]+ patterns, returning an
empty set for CJK-only text. This made Jaccard similarity always 0 (or
always 1 for two empty sets) for CJK content, effectively disabling MMR
diversity detection.
Add support for:
- CJK Unified Ideographs (U+4E00–U+9FFF, U+3400–U+4DBF)
- Hiragana (U+3040–U+309F) and Katakana (U+30A0–U+30FF)
- Hangul Syllables (U+AC00–U+D7AF) and Jamo (U+1100–U+11FF)
Characters are extracted as unigrams, and bigrams are generated only
from characters that are adjacent in the original text (no spurious
bigrams across ASCII boundaries).
Fixes#28000
* fix(msteams): reset stream state after preparePayload suppresses delivery
When an agent uses tools mid-response (text → tool calls → more text),
the stream controller's preparePayload would suppress fallback delivery
for ALL text segments because streamReceivedTokens stayed true. This
caused the second text segment to be silently lost or duplicated.
Fix: after preparePayload suppresses delivery for a streamed segment,
finalize the stream and reset streamReceivedTokens so subsequent
segments use fallback delivery.
Fixesopenclaw/openclaw#56040
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(msteams): guard preparePayload against finalized stream re-suppression
When onPartialReply fires after the stream is finalized (post-tool
partial tokens), streamReceivedTokens gets set back to true but the
stream can't deliver. Add stream.isFinalized check so a finalized
stream never suppresses fallback delivery.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(msteams): await pending finalize in controller to prevent race
Store the fire-and-forget finalize promise from preparePayload and
await it in the controller's finalize() method. This ensures
markDispatchIdle waits for the in-flight stream finalization to
complete before context cleanup.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(msteams): add edge case tests for multi-round and media payloads
Add tests for 3+ tool call rounds (text → tool → text → tool → text)
and media+text payloads after stream finalization, covering the full
contract of preparePayload across all input types and cycle counts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
`openclaw --version` outputs "OpenClaw 2026.x.y-z" but
readGatewayVersion() passed the full string to Semver.parse(),
which failed on the "OpenClaw " prefix. This caused the app to
fall back to reading package.json from a local source checkout
(~/Projects/openclaw), reporting a false version mismatch.
Strip the product name prefix before parsing so the installed
CLI version is correctly recognized.
* Plugins: clean up channel config on uninstall
`openclaw plugins uninstall` only removed `plugins.*` entries but left
`channels.<id>` config behind, causing errors when the gateway
referenced a channel whose plugin no longer existed.
Now `removePluginFromConfig` also deletes the matching
`channels.<pluginId>` entry (exact match only), and the CLI
previews/reports the removal. Shared config keys like `defaults`
and `modelByChannel` are guarded from accidental removal.
* Plugins: sync uninstall preview with channel cleanup
* fix: clean up channel config on uninstall (#35915) (thanks @wbxl2000)
---------
Co-authored-by: George Zhang <georgezhangtj97@gmail.com>
Ollama thinking-capable models default to think=true when the parameter
is absent. When OpenClaw has thinking set to off, the request never
included think=false, so models continued generating thinking tokens
that were then discarded by the response parser, producing empty
responses.
Wire onPayload into the Ollama stream path so payload wrappers can
mutate the request body, and add an Ollama-specific wrapper that sets
top-level think=false when thinkingLevel is off.
Fixes#46680, #50702, #50712
Co-Authored-By: SnowSky1 <126348592+snowsky1@users.noreply.github.com>
* Discord: gate text approvals by approver policy
* Discord: require approvers for plugin text approvals
* Discord: preserve legacy text approval fallback
* docs(agent-loop): correct default timeoutSeconds from 600s to 172800s (48h)
The default was raised to 48 hours in PR #51874 (merged 2026-03-21) to
avoid cutting off long-running ACP sessions, but the docs were not
updated at the time. Closes#55380.
* docs: remove 'Use 0 to disable' per aisle security review
The Ollama stream function requested `stream: true` from the API but
accumulated all content chunks internally, emitting only a single `done`
event at the end. This prevented downstream consumers (block streaming
pipeline, typing indicators, draft stream) from receiving incremental
text updates during generation.
Emit the full `start → text_start → text_delta* → text_end → done`
event sequence matching the AssistantMessageEvent contract used by
Anthropic, OpenAI, and Google providers. Each `text_delta` carries both
the incremental `delta` and an accumulated `partial` snapshot.
Tool-call-only responses (no text content) continue to emit only the
`done` event, preserving backward compatibility.
---------
Signed-off-by: Jakub Rusz <jrusz@proton.me>
Co-authored-by: Claude <claude-opus-4-6> <noreply@anthropic.com>
Co-authored-by: Bruce MacDonald <brucewmacdonald@gmail.com>
* Plugins: add native ask dialog for before_tool_call hooks
Extend the before_tool_call plugin hook with a requireApproval return field
that pauses agent execution and waits for real user approval via channels
(Telegram, Discord, /approve command) instead of relying on the agent to
cooperate with a soft block.
- Add requireApproval field to PluginHookBeforeToolCallResult with id, title,
description, severity, timeout, and timeoutBehavior options
- Extend runModifyingHook merge callback to receive hook registration so
mergers can stamp pluginId; always invoke merger even for the first result
- Make ExecApprovalManager generic so it can be reused for plugin approvals
- Add plugin.approval.request/waitDecision/resolve gateway methods with
schemas, scope guards, and broadcast events
- Handle requireApproval in pi-tools via two-phase gateway RPC with fallback
to soft block when the gateway is unavailable
- Extend the exec approval forwarder with plugin approval message builders
and forwarding methods
- Update /approve command to fall back to plugin.approval.resolve when exec
approval lookup fails
- Document before_tool_call requireApproval in hooks docs and unified
/approve behavior in exec-approvals docs
* Plugins: simplify plugin approval code
- Extract mergeParamsWithApprovalOverrides helper to deduplicate param
merge logic in before_tool_call hook handling
- Use idiomatic conditional spread syntax in toolContext construction
- Extract callApprovalMethod helper in /approve command to eliminate
duplicated callGateway calls
- Simplify plugin approval schema by removing unnecessary Type.Union
with Type.Null on optional fields
- Extract normalizeTrimmedString helper for turn source field trimming
* Tests: add plugin approval wiring and /approve fallback coverage
Fix 3 broken assertions expecting old "Exec approval" message text.
Add tests for the /approve command's exec→plugin fallback path,
plugin approval method registration and scope authorization, and
handler factory key verification.
* UI: wire plugin approval events into the exec approval overlay
Handle plugin.approval.requested and plugin.approval.resolved gateway
events by extending the existing exec approval queue with a kind
discriminator. Plugin approvals reuse the same overlay, queue management,
and expiry timer, with branched rendering for plugin-specific content
(title, description, severity). The decision handler routes resolve calls
to the correct gateway method based on kind.
* fix: read plugin approval fields from nested request payload
The gateway broadcasts plugin approval payloads with title, description,
severity, pluginId, agentId, and sessionKey nested inside the request
object (PluginApprovalRequestPayload), not at the top level. Fix the
parser to read from the correct location so the overlay actually appears.
* feat: invoke plugin onResolution callback after approval decision
Adds onResolution to the requireApproval type and invokes it after
the user resolves the approval dialog, enabling plugins to react to
allow-always vs allow-once decisions.
* docs: add onResolution callback to requireApproval hook documentation
* test: fix /approve assertion for unified approval response text
* docs: regenerate plugin SDK API baseline
* docs: add changelog entry for plugin approval hooks
* fix: harden plugin approval hook reliability
- Add APPROVAL_NOT_FOUND error code so /approve fallback uses structured
matching instead of fragile string comparison
- Check block before requireApproval so higher-priority plugin blocks
cannot be overridden by a lower-priority approval
- Race waitDecision against abort signal so users are not stuck waiting
for the full approval timeout after cancelling a run
- Use null consistently for missing pluginDescription instead of
converting to undefined
- Add comments explaining the +10s timeout buffer on gateway RPCs
* docs: document block > requireApproval precedence in hooks
* fix: address Phase 1 critical correctness issues for plugin approval hooks
- Fix timeout-allow param bug: return merged hook params instead of
original params when timeoutBehavior is "allow", preventing security
plugins from having their parameter rewrites silently discarded.
- Host-generate approval IDs: remove plugin-provided id field from the
requireApproval type, gateway request, and protocol schema. Server
always generates IDs via randomUUID() to prevent forged/predictable
ID attacks.
- Define onResolution semantics: add PluginApprovalResolutions constants
and PluginApprovalResolution type. onResolution callback now fires on
every exit path (allow, deny, timeout, abort, gateway error, no-ID).
Decision branching uses constants instead of hard-coded strings.
- Fix pre-existing test infrastructure issues: bypass CJS mock cache for
getGlobalHookRunner global singleton, reset gateway mock between tests,
fix hook merger priority ordering in block+requireApproval test.
* fix: tighten plugin approval schema and add kind-prefixed IDs
Harden the plugin approval request schema: restrict severity to
enum (info|warning|critical), cap timeoutMs at 600s, limit title
to 80 chars and description to 256 chars. Prefix plugin approval
IDs with `plugin:` so /approve routing can distinguish them from
exec approvals deterministically instead of relying on fallback.
* fix: address remaining PR feedback (Phases 1-3 source changes)
* chore: regenerate baselines and protocol artifacts
* fix: exclude requesting connection from approval-client availability check
hasExecApprovalClients() counted the backend connection that issued
the plugin.approval.request RPC as an approval client, preventing
the no-approval-route fast path from firing in headless setups and
causing 120s stalls. Pass the caller's connId so it is skipped.
Applied to both plugin and exec approval handlers.
* Approvals: complete Discord parity and compatibility fallback
* Hooks: make plugin approval onResolution non-blocking
* Hooks: freeze params after approval owner is selected
* Gateway: harden plugin approval request/decision flow
* Discord/Telegram: fix plugin approval delivery parity
* Approvals: fix Telegram plugin approval edge cases
* Auto-reply: enforce Telegram plugin approval approvers
* Approvals: harden Telegram and plugin resolve policies
* Agents: static-import gateway approval call and fix e2e mock loading
* Auto-reply: restore /approve Telegram import boundary
* Approvals: fail closed on no-route and neutralize Discord mentions
* docs: refresh generated config and plugin API baselines
---------
Co-authored-by: Václav Belák <vaclav.belak@gendigital.com>
* fix(plugins): apply bundled allowlist compat in plugin status report
`buildPluginStatusReport` (used by `openclaw plugins list` and
`openclaw doctor`) was calling `loadOpenClawPlugins` without applying
`withBundledPluginAllowlistCompat`. When `plugins.allow` is set, the
allowlist check in `resolveEffectiveEnableState` runs before the
bundled-default-enable check, causing all bundled plugins not explicitly
in the allowlist to be reported as "disabled".
The gateway runtime already applies this compat via
`providers.runtime.ts`, so the actual loaded state differs from what
CLI diagnostics report.
Apply the same `withBundledPluginAllowlistCompat` transform so the
status report matches gateway runtime behavior.
* add regression test for bundled allowlist compat wiring
Address review feedback: the previous mocks were identity stubs that
did not exercise the compat wiring. Now the mocks are spies, and a new
test verifies that:
1. loadPluginManifestRegistry is called to discover bundled plugin IDs
2. withBundledPluginAllowlistCompat receives only bundled IDs (not workspace)
3. loadOpenClawPlugins receives the compat-adjusted config
* scope compat to bundled providers only (address codex review)
Use resolveBundledProviderCompatPluginIds instead of injecting all
bundled plugin IDs. This matches the runtime compat surface in
providers.runtime.ts — non-provider bundled plugins (device-pair,
phone-control, etc.) are not auto-added to the allowlist, keeping
the status report consistent with gateway startup behavior.
* Replace killProcessTree references to shell-utils with process/kill-tree
* Address grace timeout comment
* Align with existing process kill behavior
* bash: fail stop without pid
* bash: lazy-load kill tree on stop
---------
Co-authored-by: Jacob Tomlinson <jtomlinson@nvidia.com>
* feishu: fall back from synthetic tool accounts
* feishu: validate implicit tool accounts by config id
* fix: fall back from synthetic tool accounts (#55627) (thanks @MonkeyLeeT)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
- compaction.ts: drop removed 'headers' param from generateSummary call
- compaction.retry.test.ts: align test call with new generateSummary signature
- compaction-safeguard.ts: replace getApiKeyAndHeaders with getApiKey (upstream removed)
- Migrate all Skill sourceInfo.source → flat source field across agents, cli, security
- Update 6 test files to match new Skill shape
HTTP 500 (Internal Server Error) was not triggering model fallback,
causing agents to fail outright instead of trying the next candidate.
This is inconsistent with TRANSIENT_HTTP_ERROR_CODES which already
includes 500. Aligns the direct status check with that constant.
Co-authored-by: Craig McWilliams <craigamcw@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Claude CLI backend uses `--output-format json`, which produces no
stdout until the entire request completes. When session context is large
(100K+ tokens) or API response is slow, the no-output watchdog timer
(max 180s for resume sessions) kills the process before it finishes,
resulting in "CLI produced no output for 180s and was terminated" errors.
Switch to `--output-format stream-json --verbose` so Claude CLI emits
NDJSON events throughout processing (init, assistant, rate_limit, result).
Each event resets the watchdog timer, which is the intended behavior —
the watchdog detects truly stuck processes, not slow-but-progressing ones.
Changes:
- cli-backends.ts: `json` → `stream-json --verbose`, `output: "jsonl"`
- helpers.ts: teach parseCliJsonl to extract text from Claude's
`{"type":"result","result":"..."}` NDJSON line
Note: `--verbose` is required for stream-json in `-p` (print) mode.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the CLI backend output mode is "text", sessionId was hardcoded to
undefined. This caused the fallback chain to store the OpenClaw internal
UUID as the CLI session ID. On resume, --resume was called with the
wrong UUID, resulting in "No conversation found with session ID".
Return resolvedSessionId instead of undefined so the correct CLI session
ID is persisted and resume works correctly.
codex-cli lacks systemPromptArg, so the system prompt is never
serialized into argv — making the not-toContain assertion pass
vacuously even on pre-fix code. Switch to claude-cli which defines
systemPromptArg ("--append-system-prompt") and add a positive
assertion that the user-supplied prompt IS present in argv.
Co-Authored-By: Dhiman's Agentic Suite <dhiman.seal@hotmail.com>
`runCliAgent()` unconditionally appended "Tools are disabled in this
session. Do not call tools." to `extraSystemPrompt` for every CLI
backend session. The intent was to prevent the LLM from calling
OpenClaw's embedded API tools (since CLI backends manage their own
tools natively). However, CLI agents like Claude Code interpret this
text as a blanket prohibition on ALL tools, including their own native
Bash, Read, and Write tools.
This caused silent failures across cron jobs, group chats, and DM
sessions when using any CLI backend: the agent would see the injected
text in the system prompt and refuse to execute tools, returning text
responses instead. Cron jobs reported `lastStatus: "ok"` despite the
agent failing to run scripts.
The fix removes the hardcoded string entirely. CLI backends already
receive `tools: []` (no OpenClaw embedded tools in the API call), so
the text was redundant at best.
Closes#44135
Co-Authored-By: Dhiman's Agentic Suite <dhiman.seal@hotmail.com>
* test: improve test runner help text
* test: print extension help to stdout
* test: leave extension help passthrough alone
* test: parse timing update flags in one pass
* fix(agents): enforce visibility guard after sessionId resolution in session_status
When a sessionId (rather than an explicit agent key) is passed to the
session_status tool, the sessionId resolution block rewrites
requestedKeyRaw to an explicit "agent:..." key. The subsequent
visibility guard check at line 375 tested
`!requestedKeyRaw.startsWith("agent:")`, which was now always false
after resolution — skipping the visibility check entirely.
This meant a sandboxed agent could bypass visibility restrictions by
providing a sessionId instead of an explicit session key.
Fix: use the original `isExplicitAgentKey` flag (captured before
resolution) instead of re-checking the dynamic requestedKeyRaw.
This ensures the visibility guard runs for sessionId inputs while
still skipping the redundant check for inputs that were already
validated at the earlier explicit-key check (lines 281-286).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: cover session status sessionId guard
* test: align parent sessionId guard coverage
---------
Co-authored-by: Kevin Sheng <shenghuikevin@github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(extensions): route fetch calls through fetchWithSsrFGuard
Replace raw fetch() with fetchWithSsrFGuard in BlueBubbles, Mattermost,
Nextcloud Talk, and Thread Ownership extensions so outbound requests go
through the shared DNS-pinning and network-policy layer.
BlueBubbles: thread allowPrivateNetwork from account config through all
fetch call sites (send, chat, reactions, history, probe, attachments,
multipart). Add _setFetchGuardForTesting hook for test overrides.
Mattermost: add guardedFetchImpl wrapper in createMattermostClient that
buffers the response body before releasing the dispatcher. Handle
null-body status codes (204/304).
Nextcloud Talk: wrap both sendMessage and sendReaction with
fetchWithSsrFGuard and try/finally release.
Thread Ownership: add fetchWithSsrFGuard and ssrfPolicyFromAllowPrivateNetwork
to the plugin SDK surface; use allowPrivateNetwork:true for the
Docker-internal forwarder.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(extensions): improve null-body handling and test harness cleanup
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(bluebubbles): default to strict SSRF policy when allowPrivateNetwork is unset
Callers that omit allowPrivateNetwork previously got undefined policy,
which caused blueBubblesFetchWithTimeout to fall through to raw fetch
and bypass the SSRF guard entirely.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(bluebubbles): thread allowPrivateNetwork through action and monitor call sites
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(mattermost,nextcloud-talk): add allowPrivateNetwork config for self-hosted/LAN deployments
* fix: regenerate config docs baseline for new allowPrivateNetwork fields
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Remove Qwen OAuth integration (qwen-portal-auth)
Qwen OAuth via portal.qwen.ai is being deprecated by the Qwen team due
to traffic impact on their primary Qwen Code user base. Users should
migrate to the officially supported Model Studio (Alibaba Cloud Coding
Plan) provider instead.
Ref: https://github.com/openclaw/openclaw/issues/49557
- Delete extensions/qwen-portal-auth/ plugin entirely
- Remove qwen-portal from onboarding auth choices, provider aliases,
auto-enable list, bundled plugin defaults, and pricing cache
- Remove Qwen CLI credential sync (external-cli-sync, cli-credentials)
- Remove QWEN_OAUTH_MARKER from model auth markers
- Update docs/providers/qwen.md to redirect to Model Studio
- Update model-providers docs (EN + zh-CN) to remove Qwen OAuth section
- Regenerate config and plugin-sdk baselines
- Update all affected tests
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* Clean up residual qwen-portal references after OAuth removal
* Add migration hint for deprecated qwen-portal OAuth provider
* fix: finish qwen oauth removal follow-up
---------
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Frank Yang <frank.ekn@gmail.com>
* Docs: rename modelstudio.md to qwen_modelstudio.md, add Standard API endpoints
* refine docs
* Docs: fix broken link in providers/index.md after modelstudio rename
* Docs: add redirect from /providers/modelstudio to /providers/qwen_modelstudio
* Docs: adjust the order in index.md
* docs: rename modelstudio to qwen_modelstudio, add Standard API endpoints (#54407) (thanks @wenmengzhou)
---------
Co-authored-by: George Zhang <georgezhangtj97@gmail.com>
* Microsoft Foundry: add native provider
* Microsoft Foundry: tighten review fixes
* Microsoft Foundry: enable by default
* Microsoft Foundry: stabilize API routing
* msteams: add pin/unpin, list-pins, and read message actions
Wire up Graph API endpoints for message read, pin, unpin, and list-pins
in the MS Teams extension, following the same patterns as edit/delete.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* msteams: address PR review comments for pin/unpin/read actions
- Handle 204 No Content in postGraphJson (Graph mutations may return empty body)
- Strip conversation:/user: prefixes in resolveConversationPath to avoid Graph 404s
- Remove dead variable in channel pin branch
- Rename unpin param from messageId to pinnedMessageId for semantic clarity
- Accept both pinnedMessageId and messageId in unpin action handler for compat
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* msteams: resolve user targets + add User-Agent to Graph helpers
- Resolve user:<aadId> targets to actual conversation IDs via conversation
store before Graph API calls (fixes 404 for DM-context actions)
- Add User-Agent header to postGraphJson/deleteGraphRequest for consistency
with fetchGraphJson after rebase onto main
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* msteams: resolve DM targets to Graph chat IDs + expose pin IDs
- Prefer cached graphChatId over Bot Framework conversation IDs for user
targets; throw descriptive error when no Graph-compatible ID is available
- Add `id` field to list-pins rows so default formatters surface the pinned
resource ID needed for the unpin flow
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* msteams: add react and reactions (list) message actions
* msteams: add search message action via Graph API
* msteams: fix search query injection, add ConsistencyLevel header, use manual query string
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* msteams: fetch thread history via Graph API for channel replies
* msteams: address PR #51643 review feedback
- Wrap resolveTeamGroupId Graph call in try/catch, fall back to raw
conversationTeamId when Team.ReadBasic.All permission is missing
- Remove dead fetchChatMessages function (exported but never called)
- Add JSDoc documenting oldest-50-replies Graph API limitation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* msteams: address thread history PR review comments
* msteams: only cache team group IDs on successful Graph lookup
Avoid caching raw conversationTeamId as a Graph team GUID when the
/teams/{id} lookup fails — the raw ID may be a Bot Framework conversation
key, not a valid GUID, causing silent thread-history failures for the
entire cache TTL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: pass agentId in CLI message command to enable session transcript writes
The CLI `openclaw message send` command was not passing `agentId` to
`runMessageAction()`, causing the outbound session route resolution to
be skipped (it's gated on `agentId && !dryRun`). Without a route, the
`mirror` object is never constructed, and `appendAssistantMessageToSessionTranscript()`
is never called.
This fix resolves the agent ID from the config (defaulting to "main")
and passes it through, enabling transcript mirroring for all channels
when using the CLI.
Closes#54186
* fix: format message.ts with oxfmt
* fix: use resolveDefaultAgentId instead of cfg.agent
* fix: restore CLI message transcript mirroring (#54187) (thanks @KevInTheCloud5617)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* daemon: tighten systemd duplicate gateway detection (#15849)
* fix three issues from PR review
* fix windows unit tests due to posix/windows path differences
* ensure line continuations are handled in systemd units
* fix misleading test name
* attempt fix windows test due to fs path separator
* fix system_dir separator, fix platform side-effect
* change approach for mocking systemd filesystem test
* normalize systemd paths to linux style
* revert to vers that didnt impact win32 tests
* back out all systemd inspect tests
* change test approach to avoid other tests issues
* fix: tighten systemd duplicate gateway detection (#45328) (thanks @gregretkowski)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* docs: add WeChat channel via official Tencent iLink Bot plugin
Add WeChat to the README channel lists and setup section.
Uses the official Tencent-published plugin @tencent-weixin/openclaw-weixin
which connects via the iLink Bot API (QR code login, long-poll).
Requires WeChat 8.0.70+ with the ClawBot plugin enabled; the plugin
is being rolled out gradually by Tencent.
Covers: setup steps, capabilities (DM-only, media up to 100 MB,
multi-account, pairing authorization, typing indicators, config path),
and the context token restart caveat.
* docs: update WeChat plugin install for v2.0 compatibility
- Add version compatibility note (v2.x requires OpenClaw >= 2026.3.22,
@legacy tag for older hosts)
- Add plugins.allow step (required since plugins.allow was introduced)
* docs: drop manual plugins.allow/enable steps (handled by plugins install)
* docs: fix multi-account instruction to require explicit --account id
* docs: trim WeChat section to match neighboring channels, fix pairing link
* docs: sync WeChat channel docs
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(auto-reply): deliver verbose tool summaries in Telegram forum topics
Forum topics have ChatType 'group' but are threaded conversations where
verbose tool output should be delivered (same as DMs). The
shouldSendToolSummaries gate now checks IsForum to allow tool summaries
in forum topic sessions.
Fixes#43206
* test: add sendToolResult count assertion per review feedback
* fix: add changelog for forum topic verbose tool summaries (#43236) (thanks @frankbuild)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* feat: add video generation core infrastructure and extend image generation parameters
Add full video generation capability to OpenClaw core:
- New `video_generate` agent tool with support for prompt, duration, aspect ratio,
resolution, seed, watermark, I2V (first/last frame), camerafixed, and draft mode
- New `VideoGenerationProvider` plugin SDK type and `registerVideoGenerationProvider` API
- New `src/video-generation/` module (types, runtime with fallback, provider registry)
- New `openclaw/plugin-sdk/video-generation` export for external plugins
- 200MB max file size for generated videos (vs default 5MB for images)
Extend image generation with additional parameters:
- `seed`, `watermark`, `guidanceScale`, `optimizePrompt`, `providerOptions`
- New `readBooleanParam()` helper in tool common utilities
Update plugin registry, contracts, and all test mocks to include
`videoGenerationProviders` and `videoGenerationProviderIds`.
Made-with: Cursor
* fix: validate aspect ratio against target provider when model override is set
* cleanup: remove redundant ?? undefined from video/image generate tools
* chore: regenerate plugin SDK API baseline after video generation additions
---------
Co-authored-by: yongliang.xie <yongliang.xie@bytedance.com>
* fix(talk-voice): enforce operator.admin scope on /voice set config writes
* fix(talk-voice): align scope guard with phone-control pattern
Use optional chaining (?.) instead of Array.isArray so webchat callers
with undefined scopes are rejected, matching the established pattern in
phone-control. Add test for webchat-with-no-scopes case.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* plugin-runtime: expose runHeartbeatOnce in system API
Plugins that enqueue system events and need the agent to deliver
responses to the originating channel currently have no way to
override the default `heartbeat.target: "none"` behaviour.
Expose `runHeartbeatOnce` in the plugin runtime `system` namespace
so plugins can trigger a single heartbeat cycle with an explicit
`heartbeat: { target: "last" }` override — the same pattern the
cron service already uses (see #28508).
Changes:
- Add `RunHeartbeatOnceOptions` type and `runHeartbeatOnce` to
`PluginRuntimeCore.system` (types-core.ts)
- Wire the function through a thin wrapper in runtime-system.ts
- Update the test-utils plugin-runtime mock
Made-with: Cursor
* feat(plugins): expose runHeartbeatOnce in system API (#40299) (thanks @loveyana)
---------
Co-authored-by: George Zhang <georgezhangtj97@gmail.com>
* feat(minimax): add image generation and TTS providers, trim TUI model list
Register MiniMax image-01 and speech-2.8 models as plugin providers for
the image_generate and TTS tools. Both resolve CN/global base URLs from
the configured model endpoint origin.
- Image generation: base64 response, aspect-ratio support, image-to-image
via subject_reference, registered for minimax and minimax-portal
- TTS: speech-2.8-turbo (default) and speech-2.8-hd, hex-encoded audio,
voice listing via get_voice API, telephony PCM support
- Add MiniMax to TTS auto-detection cascade (after ElevenLabs, before
Microsoft) and TTS config section
- Remove MiniMax-VL-01, M2, M2.1, M2.5 and variants from TUI picker;
keep M2.7 and M2.7-highspeed only (backend routing unchanged)
* feat(minimax): trim legacy model catalog to M2.7 only
Cherry-picked from temp/feat/minimax-trim-legacy-models (949ed28).
Removes MiniMax-VL-01, M2, M2.1, M2.5 and variants from the model
catalog, model order, modern model matchers, OAuth config, docs, and
tests. Keeps only M2.7 and M2.7-highspeed.
Conflicts resolved:
- provider-catalog.ts: removed MINIMAX_TUI_MODELS filter (no longer
needed since source array is now M2.7-only)
- index.ts: kept image generation + speech provider registrations
(added by this branch), moved media understanding registrations
earlier (as intended by the cherry-picked commit)
* fix(minimax): update discovery contract test to reflect M2.7-only catalog
Cherry-picked from temp/feat/minimax-trim-legacy-models (2c750cb).
* feat(minimax): add web search provider and register in plugin entry
* fix(minimax): resolve OAuth credentials for TTS speech provider
* MiniMax: remove web search and TTS providers
* fix(minimax): throw on empty images array after generation failure
* feat(minimax): add image generation provider and trim catalog to M2.7 (#54487) (thanks @liyuan97)
---------
Co-authored-by: tars90percent <tars@minimaxi.com>
Co-authored-by: George Zhang <georgezhangtj97@gmail.com>
* fix(plugins): resolve sdk alias from import.meta.url for external plugins
When a plugin is installed outside the openclaw package (e.g.
~/.openclaw/extensions/), resolveLoaderPluginSdkPackageRoot() fails to
locate the openclaw root via cwd or argv1 hints, resulting in an empty
alias map. Jiti then cannot resolve openclaw/plugin-sdk/* imports and
the plugin fails to load with "Cannot find module".
Since sdk-alias.ts is always compiled into the openclaw package itself,
import.meta.url reliably points inside the installation directory. Add it
as an unconditional fallback in resolveLoaderPluginSdkPackageRoot() so
external plugins can always resolve the plugin SDK.
Fixes: Error: Cannot find module 'openclaw/plugin-sdk/plugin-entry'
* fix(plugins): pass loader moduleUrl to resolve sdk alias for external plugins
The previous approach of adding import.meta.url as an unconditional
fallback inside resolveLoaderPluginSdkPackageRoot() broke test isolation:
tests that expected null from untrusted fixtures started finding the real
openclaw root. Revert that and instead thread an optional moduleUrl through
buildPluginLoaderAliasMap → resolvePluginSdkScopedAliasMap →
listPluginSdkExportedSubpaths → resolveLoaderPluginSdkPackageRoot.
loader.ts passes its own import.meta.url as the hint, which is always
inside the openclaw installation. This guarantees the sdk alias map is
built correctly even when argv1 does not resolve to the openclaw root
(e.g. single-binary distributions, custom launchers, or Docker images
where the binary wrapper is not a standard npm symlink).
Tests that call sdk-alias helpers directly without moduleUrl are
unaffected and continue to enforce the existing isolation semantics.
A new test covers the moduleUrl resolution path explicitly.
* fix(plugins): use existing fixture file for moduleUrl hint in test
The previous test pointed loaderModuleUrl to dist/plugins/loader.js
which is not created by createPluginSdkAliasFixture, causing resolution
to fall back to the real openclaw root instead of the fixture root.
Use fixture.root/openclaw.mjs (created by the bin+marker fixture) so
the moduleUrl hint reliably resolves to the fixture package root.
* fix(test): use fixture.root as cwd in external plugin alias test
When process.cwd() is mocked to the external plugin dir, the
findNearestPluginSdkPackageRoot(process.cwd()) fallback resolves to
the real openclaw repo root in the CI test runner, making the test
resolve the wrong aliases. Using fixture.root as cwd ensures all
resolution paths consistently point to the fixture.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(release): add plugin-sdk:check-exports to release:check
plugin-sdk subpath exports (e.g. openclaw/plugin-sdk/plugin-entry,
openclaw/plugin-sdk/provider-auth) were missing from the published
package.json, causing external plugins to fail at load time with
'Cannot find module openclaw/plugin-sdk/plugin-entry'.
Root cause: sync-plugin-sdk-exports.mjs syncs plugin-sdk-entrypoints.json
into package.json exports, but this sync was never validated in the
release:check pipeline. As a result, any drift between
plugin-sdk-entrypoints.json and the published package.json goes
undetected until users hit the runtime error.
Fix: add plugin-sdk:check-exports to release:check so the CI gate
fails loudly if the exports are out of sync before publishing.
* fix(test): isolate moduleUrl hint test from process.cwd() fallback
Use externalPluginRoot as cwd instead of fixture.root, so only the
moduleUrl hint can resolve the openclaw package root. Previously,
withCwd(fixture.root) allowed the process.cwd() fallback to also
resolve the fixture root, making the moduleUrl path untested.
Spotted by greptile-apps review on #54283.
* fix(test): use empty string to disable argv1 in moduleUrl hint test
Passing undefined for argv1 in buildPluginLoaderAliasMap triggers the
STARTUP_ARGV1 default (process.argv[1], the vitest runner binary inside
the openclaw repo). resolveTrustedOpenClawRootFromArgvHint then resolves
to the real openclaw root before the moduleUrl hint is checked, making
the test resolve wrong aliases.
Pass "" instead: falsy so the hint is skipped, but does not trigger the
default parameter value. Only the moduleUrl can bridge the gap.
Made-with: Cursor
* fix(plugins): thread moduleUrl through SDK alias resolution for external plugins (#54283) Thanks @xieyongliang
---------
Co-authored-by: bojsun <bojie.sun@bytedance.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Jerry <jerry@JerrydeMacBook-Air-2.local>
Co-authored-by: yongliang.xie <yongliang.xie@bytedance.com>
Co-authored-by: George Zhang <georgezhangtj97@gmail.com>
* fix(feishu): use message create_time instead of Date.now() for Timestamp field
When a message is sent offline and later retried by the Feishu client
upon reconnection, Date.now() captures the *delivery* time rather than
the *authoring* time. This causes downstream consumers to see a
timestamp that can be minutes or hours after the user actually composed
the message, leading to incorrect temporal semantics — for example, a
"delete this" command may target the wrong resource because the agent
believes the instruction was issued much later than it actually was.
Replace every Date.now() used for message timestamps with the original
create_time from the Feishu event payload (millisecond-epoch string),
falling back to Date.now() only when the field is absent. The
definition is also hoisted to the top of handleFeishuMessage so that
both the pending-history path and the main inbound-payload path share
the same authoritative value.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(feishu): verify Timestamp uses message create_time
Add two test cases:
1. When create_time is present, Timestamp must equal the parsed value
2. When create_time is absent, Timestamp falls back to Date.now()
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: revert unrelated formatting change to lifecycle.test.ts
This file was inadvertently formatted in a prior commit. Reverting to
match main and keep the PR scoped to the Feishu timestamp fix only.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(feishu): use message create_time for inbound timestamps (#52809) (thanks @schumilin)
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: George Zhang <georgezhangtj97@gmail.com>
* fix(feishu): close WebSocket connections on monitor stop/abort
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(feishu): add WebSocket cleanup tests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(feishu): close WebSocket connections on monitor stop (#52844) (thanks @schumilin)
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: George Zhang <georgezhangtj97@gmail.com>
* gateway: make session:patch hook typed and non-blocking
* gateway(test): add session:patch hook coverage
* docs(gateway): clarify session:patch security note
* fix: address review feedback on session:patch hook
Remove unused createInternalHookEvent import and fix doc example
to use inline event.type check matching existing hook examples.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: isolate hook payload to prevent mutation leaking into response
Shallow-copy sessionEntry and patch in the session:patch hook event
so fire-and-forget handlers cannot mutate objects used by the
response path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: isolate session:patch hook payload (#53880) (thanks @graciegould)
---------
Co-authored-by: “graciegould” <“graciegould5@gmail.com”>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(process): auto-detect PTY cursor key mode for send-keys
When a PTY session sends smkx (\x1b[?1h) or rmkx (\x1b[?1l) to switch
cursor key mode, send-keys now detects this and encodes cursor keys
accordingly.
- smkx/rmkx detection in handleStdout before sanitizeBinaryOutput
- cursorKeyMode stored in ProcessSession
- encodeKeySequence accepts cursorKeyMode parameter
- DECCKM_SS3_KEYS for application mode (arrows + home/end)
- CSI sequences for normal mode
- Modified keys (including alt) always use xterm modifier scheme
- Extract detectCursorKeyMode for unit testing
- Use lastIndexOf to find last toggle in chunk (later one wins)
Fixes#51488
* fix: fail loud when PTY cursor mode is unknown (#51490) (thanks @liuy)
* style: format process send-keys guard (#51490) (thanks @liuy)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
macOS registers Edge as 'com.microsoft.edgemac' in LaunchServices, which
differs from the CFBundleIdentifier 'com.microsoft.Edge' in the app's own
Info.plist. Without recognising the LaunchServices IDs, Edge users who set
Edge as their default browser are not detected as having a Chromium browser.
Add the four com.microsoft.edgemac* variants to CHROMIUM_BUNDLE_IDS and a
corresponding test that mocks the LaunchServices → osascript resolution
path for Edge.
* fix(cron): track and log bestEffort delivery failures, mark not delivered on partial failure
* fix(cron): cache successful results on partial failure to preserve replay idempotency
When a best-effort send partially fails, we now still cache the successful delivery results via rememberCompletedDirectCronDelivery. This prevents duplicate sends on same-process replay while still correctly marking the job as not fully delivered.
* fix(cron): preserve partial-failure state on replay (#27069)
* fix(cron): restore test infrastructure and fix formatting
* fix: clarify cron best-effort partial delivery status (#42535) (thanks @MoerAI)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(telegram): validate photo dimensions before sendPhoto
Prevents PHOTO_INVALID_DIMENSIONS errors by checking image dimensions
against Telegram Bot API requirements before calling sendPhoto.
If dimensions exceed limits (width + height > 10,000px), automatically
falls back to sending as document instead of crashing with 400 error.
Tested in production (openclaw 2026.3.13) where this error occurred:
[telegram] tool reply failed: GrammyError: Call to 'sendPhoto' failed!
(400: Bad Request: PHOTO_INVALID_DIMENSIONS)
Uses existing sharp dependency to read image metadata. Gracefully
degrades if sharp fails (lets Telegram handle validation, backward
compatible behavior).
Closes: #XXXXX (will reference OpenClaw issue if one exists)
* fix(telegram): validate photo aspect ratio
* refactor: use shared telegram image metadata
* fix: fail closed on telegram image metadata
* fix: preflight invalid telegram photos (#52545) (thanks @hnshah)
---------
Co-authored-by: Bob Shah <bobshah@Macs-Mac-Studio.local>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Groups configured with groupPolicy: open are expected to respond to all
messages. Previously, requireMention defaulted to true regardless of
groupPolicy, causing image (and other non-text) messages to be silently
dropped because they cannot carry @-mentions.
Fix: when groupPolicy is 'open' and requireMention is not explicitly
configured, resolve it to false instead of true. Users who want
mention-required behaviour in open groups can still set requireMention: true
explicitly.
Adds three regression tests covering the new default, explicit override, and
the unchanged allowlist-policy behaviour.
Closes#52553
Address Codex P1 + Greptile P2:
- Move config validation before the restart attempt so invalid config
is caught in the stop→start path (not just the already-loaded path)
- Derive service.loaded from actual isLoaded() after restart instead
of hardcoded true
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: HCL <chenglunhu@gmail.com>
After `gateway stop` (which runs `launchctl bootout`), `gateway start`
checks `isLoaded` → false → prints "not loaded" hints and exits.
The service is never re-bootstrapped, so `start` cannot recover from
`stop` — only `gateway install` works.
Root cause: src/cli/daemon-cli/lifecycle-core.ts:208-217 — runServiceStart
calls handleServiceNotLoaded which only prints hints, never attempts
service.restart() (which already handles bootstrap via
bootstrapLaunchAgentOrThrow at launchd.ts:598).
Fix: when service is not loaded, attempt service.restart() first (which
handles re-bootstrapping on all platforms). If restart fails (e.g. plist
was deleted, not just booted out), fall back to the existing hints.
The restart path is already proven: restartLaunchAgent (launchd.ts:556)
handles "not loaded" via bootstrapLaunchAgentOrThrow. This fix routes
the start command through the same recovery path.
Closes#53878
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: HCL <chenglunhu@gmail.com>
Move cleanup() after disconnect() in waitForDiscordGatewayStop so the
error listener is still active during disconnect. Add a safety error
listener in the lifecycle finally block to suppress late errors emitted
by Carbon during teardown.
Fixes the "Max reconnect attempts (0) reached after code 1006" uncaught
exception that kills the entire gateway process when a Discord WebSocket
drops and reconnection fails.
* fix(telegram): improve error messages for 403 bot not member errors
- Detect 403 'bot is not a member' errors specifically
- Provide actionable guidance for users to fix the issue
- Fixes#48273 where outbound sendMessage fails with 403
Root cause:
When a Telegram bot tries to send a message to a channel/group it's not
a member of, the API returns 403 'bot is not a member of the channel chat'.
The error message was not clear about how to fix this.
Fix:
1. Detect 403 errors in wrapTelegramChatNotFoundError
2. Provide clear error message explaining the issue
3. Suggest adding the bot to the channel/group
* fix(telegram): fix regex precedence for 403 error detection
- Group alternatives correctly: /403.*(bot.*not.*member|bot was blocked)/i
- Require 403 for both alternatives (previously bot.*blocked matched any error)
- Update error message to cover both scenarios
- Fixes Greptile review feedback
* fix(telegram): correct regex alternation precedence for 403 errors
- Fix: /403.*(bot.*not.*member|bot was blocked)/ → /403.*(bot.*not.*member|bot.*blocked)/
- Ensures 403 requirement applies to both alternatives
- Fixes Greptile review comment on PR #48650
* fix(telegram): add 'bot was kicked' to 403 error regex and message
* fix(telegram): preserve membership delivery errors
* fix: improve Telegram 403 membership delivery errors (#53635) (thanks @w-sss)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
- Fixes#47924
- Prevents SVG icon from expanding and covering entire chat window
- Adds explicit 24x24px dimensions to context-notice__icon SVG
Root cause:
The SVG element lacked explicit width and height attributes,
causing it to expand to fill the parent container when the context
usage warning appears (at ~85% token limit).
* fix: correct ClawHub URL in system prompt and use streaming download in marketplace
- Fix#54154: Change clawhub.com to clawhub.ai in system prompt
- Fix#54156: Replace arrayBuffer() with streaming pipeline for marketplace
plugin downloads to avoid OOM on memory-constrained devices
* fix: guard marketplace archive stream body
* fix: note marketplace streaming and ClawHub URL (#54160) (thanks @QuinnH496)
---------
Co-authored-by: Li Enying <li.enying@openclaw.ai>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(gateway): isolate channel startup failures to prevent cascade
When one channel (e.g., WhatsApp) fails to start due to missing runtime
modules, it should not block other channels (e.g., Discord) from starting.
Changes:
- Use Promise.allSettled to start channels concurrently
- Catch individual channel startup errors without affecting others
- Add startup summary logging for observability
Before: Sequential await startChannel() - if one throws, subsequent
channels never start.
After: Concurrent startup with per-channel error handling - all channels
attempt to start, failures are logged but don't cascade.
Fixes: P0 - WhatsApp runtime exception no longer blocks Discord startup
* fix(gateway): keep channel startup isolation sequential
* fix: isolate channel startup failures (#54215) (thanks @JonathanJing)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Add loginctl enable-linger and XDG_RUNTIME_DIR recovery hints to the
generic (non-WSL) systemd unavailable error path, helping users on
SSH/headless servers diagnose and fix the issue without a desktop
session.
Fixes#11805
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When preferSetupRuntimeForChannelPlugins is active, gateway boot performs
two plugin loads: a setup-runtime pass and a full reload after listen.
The initial pin captured the setup-entry snapshot. The deferred reload now
re-pins so getChannelPlugin() resolves against the full implementations.
Channel plugin resolution fails with 'Channel is unavailable: <channel>'
after the active plugin registry is replaced at runtime. The root cause is
that getChannelPlugin() resolves against the live registry snapshot, which
is replaced when non-primary registry loads (e.g., config-schema reads)
call loadOpenClawPlugins(). If the replacement registry does not carry the
same channel entries, outbound message delivery and subagent announce
silently break.
This mirrors the existing pinActivePluginHttpRouteRegistry pattern: the
channel registry is pinned at gateway startup and released on shutdown.
Subsequent setActivePluginRegistry calls no longer evict the channel
snapshot, so getChannelPlugin() always resolves against the registry that
was active when the gateway booted.
- Add hasAgentReasoningDefault to reasoningExplicitlySet check
This prevents model default from overriding agent's explicit "off"
- Restore !thinkingActive guard for model default fallback
Prevents redundant Reasoning: output alongside internal thinking
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The reasoningDefault was incorrectly skipped when thinking was active.
Thinking controls reasoning depth while reasoning controls visibility -
they should be independent settings.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test(memory): lock qmd status counts regression
* feat: make /tools show what the agent can use right now
* fix: sync web ui slash commands with the shared registry
* feat: add profile and unavailable counts to /tools
* refine: keep /tools focused on available tools
* fix: resolve /tools review regressions
* fix: honor model compat in /tools inventory
* fix: sync generated protocol models for /tools
* fix: restore canonical slash command names
* fix: avoid ci lint drift in google helper exports
* perf: stop computing unused /tools unavailable counts
* docs: clarify /tools runtime behavior
When OpenClaw restarts under load, the Feishu bot-info probe
(`/open-apis/bot/v3/info`) can exceed the 10-second timeout due to
event-loop contention during channel initialization. This leaves
`botOpenId` empty, causing `checkBotMentioned()` to return `false`
for every group message — silently dropping them all while DMs
continue to work fine.
Two fixes:
1. **Increase startup probe timeout from 10s to 30s** and make it
configurable via `OPENCLAW_FEISHU_STARTUP_PROBE_TIMEOUT_MS` env var.
The previous 10s budget was too tight when multiple channels
(Slack, Discord, Feishu) initialize concurrently.
2. **Graceful degradation in `checkBotMentioned()`**: when `botOpenId`
is unknown, return `true` (assume mentioned) instead of `false`.
This prevents group messages from being silently discarded when the
probe fails for any reason. The trade-off is that the bot may
respond to non-@-mentioned messages temporarily until the next
successful probe, which is far preferable to total silence.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The `createMessageToolCardSchema()` helper returned a bare `Type.Object()`
which TypeBox treats as required when merged into the parent tool schema via
`Type.Object({ card: ... })`. This caused schema validation to reject
media-only sends on Feishu and MSTeams with "must have required property
card", even though the implementation correctly treats card as optional.
Wrap the return value in `Type.Optional()` so the card field is excluded
from the JSON Schema `required` array. Fixes the catch-22 where omitting
card fails validation and including an empty card triggers the runtime
"does not support card with media" guard.
Closes#53697
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Custom providers using `api: "google-generative-ai"` (e.g. a paid
Google tier) resolved in the model picker but failed at runtime with
HTTP 404 because the base URL lacked the required `/v1beta` path
segment and provider normalization was gated on the provider key
being exactly `"google"`.
Two targeted fixes, both keyed on the semantic `api` field rather
than provider name strings:
1. `models-config.providers.ts` — change the normalization gate from
`normalizedKey === "google"` to
`normalizedProvider?.api === "google-generative-ai"` and add
`normalizeGoogleBaseUrl()` to ensure the canonical `/v1beta` suffix.
2. `pi-embedded-runner/model.ts` — apply
`normalizeGoogleGenerativeAiBaseUrl()` in three resolution paths
(`applyConfiguredProviderOverrides`, `buildInlineProviderModels`,
fallback model construction) so the base URL is corrected at
runtime regardless of how the model was discovered.
No changes to name-only call sites (`model-selection`,
`live-model-filter`, `model-forward-compat`); those paths are not
required for custom provider resolution and broadening their provider
checks would incorrectly capture unrelated providers like
`google-antigravity`.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Check routedCounts.final to detect prior delivery
- Skip fallback for ttsMode='all' to avoid duplicate TTS processing
- Use delivery.deliver for proper routing in cross-provider turns
- Fixes#46814 where ACP child run results were not delivered
- invalidate cached Codex CLI credentials when auth.json changes within the TTL window
- skip external CLI sync when the stored Codex OAuth credential is newer
- cover both behaviors with focused regression tests
Refs #53466
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Regeneration-Prompt: |
Current origin/main fails src/cli/program/preaction.test.ts because the
test asserts on process.title directly inside Vitest, where that runtime
interaction is not stable enough to observe the write reliably. Keep the
production preaction behavior unchanged. Make the test verify that the
hook assigns the expected title by wrapping process.title with a local
getter/setter during each test and restoring the original descriptor
afterward so other tests keep the real process object behavior.
The legacy nano-banana-pro skill migration moves the Gemini API key to
models.providers.google.apiKey but does not populate the required baseUrl
and models fields on the provider entry. When the google provider object
is freshly created (no pre-existing config), the resulting config fails
Zod validation on write:
Config validation failed: models.providers.google.baseUrl:
Invalid input: expected string, received undefined
Fix: default baseUrl to 'https://generativelanguage.googleapis.com' and
models to [] when they are not already set, matching the defaults used
elsewhere in the codebase (embeddings-gemini, pdf-native-providers).
Fixes the 'doctor --fix' crash for users who only have a legacy
nano-banana-pro skill entry and no existing models.providers.google.
* add missing autoArchiveDuration to DiscordGuildChannelConfig type
The autoArchiveDuration field is present in the Zod schema
(DiscordGuildChannelSchema) and actively used at runtime in
threading.ts and allow-list.ts, but was missing from the
canonical TypeScript type definition.
Add autoArchiveDuration to DiscordGuildChannelConfig to align
the type with the schema and runtime usage.
* Discord: add changelog for config type fix (#43427) (thanks @davidguttman)
---------
Co-authored-by: Onur Solmaz <2453968+osolmaz@users.noreply.github.com>
* feat(discord): add autoThreadName 'generated' strategy
Adds async thread title generation for auto-created threads:
- autoThread: boolean - enables/disables auto-threading
- autoThreadName: 'message' | 'generated' - naming strategy
- 'generated' uses LLM to create concise 3-6 word titles
- Includes channel name/description context for better titles
- 10s timeout with graceful fallback
* Discord: support non-key auth for generated thread titles
* Discord: skip fallback auto-thread rename
* Discord: normalize generated thread title first content line
* Discord: split thread title generation helpers
* Discord: tidy thread title generation constants and order
* Discord: use runtime fallback model resolution for thread titles
* Discord: resolve thread-title model aliases
* Discord: fallback thread-title model selection to runtime defaults
* Agents: centralize simple completion runtime
* fix(discord): pass apiKey to complete() for thread title generation
The setRuntimeApiKey approach only works for full agent runs that use
authStorage.getApiKey(). The pi-ai complete() function expects apiKey
directly in options or falls back to env vars — it doesn't read from
authStorage.runtimeOverrides.
Fixes thread title generation for Claude/Anthropic users.
* fix(agents): return exchanged Copilot token from prepareSimpleCompletionModel
The recent thread-title fix (3346ba6) passes prepared.auth.apiKey to
complete(). For github-copilot, this was still the raw GitHub token
rather than the exchanged runtime token, causing auth failures.
Now setRuntimeApiKeyForCompletion returns the resolved token and
prepareSimpleCompletionModel includes it in auth.apiKey, so both the
authStorage path and direct apiKey pass-through work correctly.
* fix(agents): catch auth lookup exceptions in completion model prep
getApiKeyForModel can throw for credential issues (missing profile, etc).
Wrap in try/catch to return { error } for fail-soft handling rather than
propagating rejected promises to callers like thread title generation.
* Discord: strip markdown wrappers from generated thread titles
* Discord/agents: align thread-title model and local no-auth completion headers
* Tests: import fresh modules for mocked thread-title/simple-completion suites
* Agents: apply exchanged Copilot baseUrl in simple completions
* Discord: route thread runtime imports through plugin SDK
* Lockfile: add Discord pi-ai runtime dependency
* Lockfile: regenerate Discord pi-ai runtime dependency entries
* Agents: use published Copilot token runtime module
* Discord: refresh config baseline and lockfile
* Tests: split extension runs by isolation
* Discord: add changelog for generated thread titles (#43366) (thanks @davidguttman)
---------
Co-authored-by: Onur Solmaz <onur@textcortex.com>
Co-authored-by: Onur Solmaz <2453968+osolmaz@users.noreply.github.com>
Root cause: Telegram channel monitor captures config at startup before secrets
are resolved and passes it as configOverride into the reply pipeline. Since
getReplyFromConfig() uses configOverride directly (skipping loadConfig() which
reads the resolved runtime snapshot), the unresolved SecretRef objects propagate
into FollowupRun.run.config and crash runEmbeddedPiAgent().
Fix (defense in depth):
- get-reply.ts: detect unresolved SecretRefs in configOverride and fall back to
loadConfig() which returns the resolved runtime snapshot
- message-tool.ts: try-catch around schema/description building at tool creation
time so channel discovery errors don't crash the agent
- message-tool.ts: detect unresolved SecretRefs in pre-bound config at tool
execution time and fall back to gateway secret resolution
Fixes: https://github.com/openclaw/openclaw/issues/45838
When a local run ends with an empty final event while another run is active,
skip history reload to prevent clearing the user's pending message from the
chat log. This fixes the 'message disappears' issue with slow models like Ollama.
1. Narrow loadConfigForInstall() to catch only INVALID_CONFIG errors,
letting real failures (fs permission, OOM) propagate.
2. Assert allow array is properly cleaned in stale-cleanup test.
3. Add comment clarifying version-resolution is already addressed via
the shared VERSION constant.
4. Run cleanStaleMatrixPluginConfig() during install so
persistPluginInstall() → writeConfigFile() does not fail validation
on stale Matrix load paths.
Migrates the Teams extension from @microsoft/agents-hosting to the official Teams SDK (@microsoft/teams.apps + @microsoft/teams.api) and implements Microsoft's AI UX best practices for Teams agents.
- AI-generated label on all bot messages (Teams native badge + thumbs up/down)
- Streaming responses in 1:1 chats via Teams streaminfo protocol
- Welcome card with configurable prompt starters on bot install
- Feedback with reflective learning (negative feedback triggers background reflection)
- Typing indicators for personal + group chats (disabled for channels)
- Informative status updates (progress bar while LLM processes)
- JWT validation via Teams SDK createServiceTokenValidator
- User-Agent: teams.ts[apps]/<sdk-version> OpenClaw/<version> on outbound requests
- Fix copy-pasted image downloads (smba.trafficmanager.net auth allowlist)
- Pre-parse auth gate (reject unauthenticated requests before body parsing)
- Reflection dispatcher lifecycle fix (prevent leaked dispatchers)
- Colon-safe session filenames (Windows compatibility)
- Cooldown cache eviction (prevent unbounded memory growth)
Closes#51806
Document that default-agent heartbeat prompt injection still applies to memory-triggered and triggerless runs while cron remains excluded.
Made-with: Cursor
* feat: make workspace links clickable in agent context card and files list
Updated the agent context card and files list to render workspace names as clickable links, allowing users to easily access the corresponding workspace files. This enhances usability by providing direct navigation to the workspace location.
* style(ui): polish markdown preview dialog
* style(ui): reduce markdown preview list indentation
* style(ui): update markdown preview dialog width and alignment
* fix(ui): open usage filter popovers toward the right
* style(ui): adjust positioning of usage filter and export popovers
* style(ui): update sidebar footer padding and modify usage header z-index
* style(ui): adjust positioning of usage filter popover to the left and export popover to the right
* style(ui): simplify workspace link rendering in agent context card
* UI: make workspace paths interactive buttons or plain text
Agent Context card workspace (Channels/Cron panels): replace non-interactive
<div> with a real <button> wired to onSelectPanel('files'), matching the
Overview panel pattern.
Core Files footer workspace: drop workspace-link class since the user is
already on the Files panel — keep as plain text.
When the server returns a bare model name (e.g. "deepseek-chat") with
a session-level modelProvider (e.g. "zai"), the UI blindly prepends
the provider — producing "zai/deepseek-chat" instead of the correct
"deepseek/deepseek-chat". This causes "model not allowed" errors
when switching between models from different providers.
Root cause: resolveModelOverrideValue() and resolveDefaultModelValue()
in app-render.helpers.ts, plus the /model slash command handler in
slash-command-executor.ts, all call resolveServerChatModelValue()
which trusts the session's default provider. The session provider
reflects the PREVIOUS model, not the newly selected one.
Fix: for bare model names, create a raw ChatModelOverride and resolve
through normalizeChatModelOverrideValue() which looks up the correct
provider from the model catalog. Falls back to server-provided provider
only if the catalog lookup fails. All 3 call sites are fixed.
Closes#53031
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: HCL <chenglunhu@gmail.com>
Ensure repair-mode doctor prompts auto-accept recommended fixes even when running non-interactively, while still requiring --force for aggressive rewrites.
This restores the expected behavior for upgrade/doctor flows that rely on 'openclaw doctor --fix --non-interactive' to repair stale gateway service configuration such as entrypoint drift after global updates.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add edit/delete action handlers with toolContext.currentChannelId
fallback for in-thread edits/deletes without explicit target
- Add editMessageMSTeams/deleteMessageMSTeams to channel runtime
- Add updateActivity/deleteActivity to SendContext and MSTeamsTurnContext
- Extend content param with text/content/message fallback chain
- Update test mocks for new SendContext shape
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a channel plugin lacks a custom buildToolContext (e.g. Telegram),
the fallback path in buildThreadingToolContext did not set currentThreadTs
from the inbound MessageThreadId. This caused resolveTelegramAutoThreadId
to return undefined, so message tool sends without explicit threadId
would route to the main chat instead of the originating DM topic.
Fixes#52217
Previously, `--at` with an offset-less ISO datetime (e.g. `2026-03-23T23:00:00`)
was always interpreted as UTC, even when `--tz` was provided. This caused one-shot
jobs to fire at the wrong time.
Changes:
- `parseAt()` now accepts an optional `tz` parameter
- When `--tz` is provided with `--at`, offset-less datetimes are interpreted in
that IANA timezone using Intl.DateTimeFormat
- Datetimes with explicit offsets (e.g. `+01:00`, `Z`) are unaffected
- Removed the guard in cron-edit that blocked `--tz` with `--at`
- Updated `--at` help text to mention `--tz` support
- Added 2 tests verifying timezone resolution and offset preservation
* fix(line): pre-export clashing symbols to prevent jiti TypeError on startup
When jiti CJS-transforms extensions/line/runtime-api.ts, both
export * from "openclaw/plugin-sdk/line-runtime" and the subsequent
export * from individual source files attempt to define the same 13
symbols via Object.defineProperty with configurable:false. The second
call throws TypeError: Cannot redefine property.
The root cause is that src/plugin-sdk/line-runtime.ts re-exports
these symbols directly from the extension source files, creating a
circular path back to the same files that runtime-api.ts star-exports.
Fix: add named pre-exports for all symbols that plugin-sdk/line-runtime
re-exports from this extension. Named exports register in jiti's
_exportNames map at transform time; the star re-export's hasOwnProperty
guard then skips them, preventing the duplicate Object.defineProperty.
export * reordering cannot fix this: _exportNames is only populated
by named exports, not by export *, so the guard never fires regardless
of order.
This is the same class of bug as the Matrix plugin crash described in
issues #50868, #52780, and #52891, and uses the same fix pattern as
PR #50919.
* test: add LINE runtime-api Jiti regression (#53221) (thanks @Drickon)
* test: stabilize LINE Jiti regression (#53221) (thanks @Drickon)
* test: harden LINE Jiti regression (#53221) (thanks @Drickon)
* chore: retrigger PR checks (#53221)
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Use isSensitiveConfigPath to detect token/password/secret/apiKey paths
and display REDACTED_PLACEHOLDER instead of raw values in the config
diff panel, preventing credential exposure in the UI.
OPENCLAW_PLUGIN_API_VERSION was hardcoded to "1.2.0" while ClawHub-published
plugins require >=2026.3.22, making all plugin installs via ClawHub fail with
"requires plugin API >=2026.3.22, but this OpenClaw runtime exposes 1.2.0".
Use resolveRuntimeServiceVersion() (already imported) to read the actual
version from package.json at runtime.
Fixes#53038
- Use hasOwnProperty + isBlockedObjectKey in isConfiguredAuthPlugin to
prevent __proto__/constructor/prototype keys from matching config
- Sanitize plugin IDs with sanitizeForLog in ambiguity error messages
- Add regression test for __proto__ plugin ID
Add Standard API Key auth methods for China (dashscope.aliyuncs.com)
and Global/Intl (dashscope-intl.aliyuncs.com) pay-as-you-go endpoints
alongside the existing Coding Plan (subscription) endpoints.
Also updates group label to 'Qwen (Alibaba Cloud Model Studio)' and
fixes glm-4.7 -> glm-5 in Coding Plan note messages.
Co-authored-by: wenmeng zhou <wenmengzhou@users.noreply.github.com>
Recheck timed-out subagent announce waits against the latest runtime snapshot before announcing timeout, and keep that recheck best-effort so transient gateway failures do not suppress the announcement.
Co-authored-by: Val Alexander <68980965+BunsDev@users.noreply.github.com>
Bootstrap LanceDB into plugin runtime state on first use for packaged/global installs, keep @lancedb/lancedb plugin-local, and add regression coverage for bundled, cached, retry, and Nix fail-fast runtime paths.
Co-authored-by: Val Alexander <68980965+BunsDev@users.noreply.github.com>
Preserve Control UI scopes through the device-auth bypass path, normalize implied operator device-auth scopes, ignore cached under-scoped operator tokens, and degrade read-backed main pages gracefully when a connection truly lacks operator.read.
Co-authored-by: Val Alexander <68980965+BunsDev@users.noreply.github.com>
Brave is a bundled web search plugin but was missing from
BUNDLED_ENABLED_BY_DEFAULT, causing it to be filtered out during
provider resolution. This made web_search unavailable even when
plugins.entries.brave.enabled was configured.
Fixes#51937
Co-authored-by: Ubuntu <ubuntu@ip-172-26-10-234.us-west-2.compute.internal>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
- Sort providers alphabetically in docs.json nav
- Sort channels alphabetically in docs.json nav (slack before synology-chat)
- Add install/migrating-matrix to Maintenance nav section (was orphaned)
- Remove zh-CN/plugins/architecture from nav (file does not exist)
- Add Voice Call to channels index page
- Add missing providers to providers index (DeepSeek, GitHub Copilot, OpenCode Go, Synthetic)
- Sort providers index alphabetically
- Update stale claude-3-5-sonnet model reference to claude-sonnet-4-6 in webhook docs
* fix(config): keep built-in channels out of plugin allowlists
* docs(changelog): note doctor whatsapp allowlist fix
* docs(changelog): move doctor whatsapp fix to top
* feat(telegram): add asDocument param to message tool
Adds `asDocument` as a user-facing alias for the existing `forceDocument`
parameter in the message tool. When set to `true`, media files (images,
videos, GIFs) are sent via `sendDocument` instead of `sendPhoto`/
`sendVideo`/`sendAnimation`, preserving the original file quality
without Telegram compression.
This is useful when agents need to deliver high-resolution images or
uncompressed files to users via Telegram.
`asDocument` is intentionally an alias rather than a replacement — the
existing `forceDocument` continues to work unchanged.
Changes:
- src/agents/tools/message-tool.ts: add asDocument to send schema
- src/agents/tools/telegram-actions.ts: OR asDocument into forceDocument
- src/infra/outbound/message-action-runner.ts: same OR logic for outbound path
- extensions/telegram/src/channel-actions.ts: read and forward asDocument
- src/channels/plugins/actions/actions.test.ts: add test case
* fix: restore channel-actions.ts to main version (rebase conflict fix)
* fix(test): match asDocument test payload to actual params structure
* fix(telegram): preserve forceDocument alias semantics
* fix: document Telegram asDocument alias (#52461) (thanks @bakhtiersizhaev)
---------
Co-authored-by: Бахтиер Сижаев <bkh@MacBook-Air.local>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
When `replyLike.text` or `replyLike.caption` is an unexpected
non-string value (edge case from some Telegram API responses),
the reply body was coerced to "[object Object]" via string
concatenation. Add a `typeof === "string"` guard to gracefully
fall back to empty string, matching the existing pattern used
for `quoteText` in the same function.
Co-authored-by: Penchan <penchan@penchan.co>
The Telegram plugin injects a `buttons` property into the message tool
schema via `createMessageToolButtonsSchema()`, but without wrapping it
in `Type.Optional()`. This causes TypeBox to include `buttons` in the
JSON Schema `required` array.
In isolated sessions (e.g. cron jobs) where no `currentChannel` is set,
all plugin schemas are merged into the message tool. When the LLM calls
the message tool without a `buttons` parameter, AJV validation fails
with: `buttons: must have required property 'buttons'`.
Wrap the buttons schema in `Type.Optional()` so it is not required.
Merged via admin squash because current required CI failures are inherited from base and match latest `main` failures outside this PR's `memory-core` surface.
Prepared head SHA: df7f968581
Co-authored-by: artwalker <44759507+artwalker@users.noreply.github.com>
Reviewed-by: @frankekn
- Mark as experimental (not just unofficial)
- Add region and safeSearch tool parameters (from DDG schema)
- Add plugin config example for region/safeSearch defaults
- Document auto-detection order (100 = last)
- Note SafeSearch defaults to moderate
- Verified against extensions/duckduckgo/src/
- Apply normalizeEnvVarKey({ portable: true }) before security
filtering, matching the established pattern in env-vars.ts.
Rejects non-portable key names (spaces, special chars) that
would produce invalid plist/systemd syntax.
- Isolate existing tests from the developer's real ~/.openclaw/.env
by providing a temp HOME directory, preventing flaky failures
when the test machine has a populated .env file.
When building the gateway install plan, read and parse
~/.openclaw/.env (or $OPENCLAW_STATE_DIR/.env) and merge those
key-value pairs into the service environment at the lowest
priority — below config env vars, auth-profile refs, and the
core service environment (HOME, PATH, OPENCLAW_*).
This ensures that user-defined secrets stored in .env (e.g.
BRAVE_API_KEY, OPENROUTER_API_KEY, DISCORD_BOT_TOKEN) are
embedded in the LaunchAgent plist (macOS), systemd unit (Linux),
and Scheduled Task (Windows) at install time, rather than
relying solely on the gateway process loading them via
dotenv.config() at startup.
Previously, on macOS the LaunchAgent plist never included .env
vars, which meant:
- launchctl print did not show user secrets (hard to debug)
- Child processes spawned before dotenv loaded had no access
- If the same key existed in both .env and the plist, the stale
plist value won via dotenv override:false semantics
Dangerous host env vars (NODE_OPTIONS, LD_PRELOAD, etc.) are
filtered using the same security policy applied to config env
vars.
Fixes#37101
Relates to #22663
New page: tools/exa-search.md
- Neural/keyword/hybrid search modes with content extraction
- Tool parameters including contents (highlights, text, summary)
- Search mode reference table
Rewritten: tools/duckduckgo-search.md
- Aligned to consistent template (Setup, Config, Tool parameters, Notes, Related)
- Simplified from previous version
Aligned across all providers:
- Every search page now ends with a consistent ## Related section
- Replaced 'See [Web tools]' with proper Related links
- Added Exa + DuckDuckGo to web.md overview CardGroup and comparison table
- Added Exa to docs.json nav and redirects
New page: tools/duckduckgo-search.md
- Key-free fallback provider, no API key needed
- Clear Warning about unofficial HTML-based integration
- Limitations section covering bot-challenge risk and reliability
- CardGroup showing good-for vs not-recommended-for use cases
Updated: tools/web.md with DuckDuckGo in CardGroup and comparison table
Updated: docs.json nav and redirect
When an exec command fails (e.g. timeout), the tool previously rejected
with an Error, which the tool adapter caught and wrapped in a JSON object
({ status, tool, error }). The model then received this raw JSON as the
tool result and could parrot it verbatim to the user.
Now exec failures resolve with a proper tool result containing the error
as human-readable text in content[], matching the success path structure.
The model sees plain text it can naturally incorporate into its reply.
Also fixes a pre-existing format issue in update-cli.test.ts.
Fixes#52484
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Function is now async after switching to resolveGatewayProbeAuthSafeWithSecretInputs.
Missing await caused TS error: Property 'auth' does not exist on type 'Promise<...>'.
Address two Codex P1/P2 issues:
1. (P1) Plain 'openclaw status' and 'openclaw status --json' still went
through the sync resolveGatewayProbeAuthSafe path in
status.gateway-probe.ts, which cannot expand SecretRef objects.
Switched to async resolveGatewayProbeAuthSafeWithSecretInputs.
2. (P2) status-all.ts was eagerly resolving both local and remote probe
auth before deciding which to use. A stale SecretRef in the unused
branch could abort the command. Collapsed to a single resolution
call using the correct mode upfront.
Updated status.scan.test.ts to use mockResolvedValue since
resolveGatewayProbeAuthResolution is now async.
Fixes#52360
resolveGatewayProbeAuthSafe was called from status-all.ts without an
env argument, causing the credential resolution chain to fall back to
an empty object instead of process.env. This made env-backed SecretRef
tokens (gateway.auth.token, Telegram botToken, etc.) appear unresolved
in the status command path even when the runtime was healthy.
Added process.env as default fallback in buildGatewayProbeCredentialPolicy
and passed env explicitly from status-all.ts callers.
Related: #33070, #38973, #39415, #46014, #49730
- sdk-entrypoints.md: fix mislabeled 'Channel entry options' heading
(should be 'Options' — these are definePluginEntry options, not
channel-specific)
- sdk-overview.md: add 4 missing API object fields (version, description,
source, rootDir) from OpenClawPluginApi type
- sdk-runtime.md: add missing required params (runId, timeoutMs) to
runEmbeddedPiAgent example
- sdk-provider-plugins.md: add missing onModelSelected hook (#22),
clarify capabilities is data not callable, drop misleading '21' count
Update exact-match test assertions in send.test.ts to include the new
allow_sending_without_reply: true parameter. Tests using objectContaining
already pass, but several tests use exact object matching.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a Telegram message that OpenClaw is replying to gets deleted before
delivery, the Telegram API rejects the entire sendMessage call with
"message to be replied not found". This causes the bot's response to be
silently lost and stuck in the failed delivery queue permanently.
Setting allow_sending_without_reply: true tells Telegram to deliver the
message as a standalone message if the reply target no longer exists,
instead of failing the entire request.
Applied to all 6 locations across 4 source files where
reply_to_message_id is set:
- send.ts: buildTelegramReplyParams (both reply_parameters and plain reply)
- bot/delivery.send.ts: buildTelegramSendParams
- draft-stream.ts: draft stream reply params
- bot-handlers.runtime.ts: error reply messages (file too large, media download failed)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Codex P1: entries deferred by the recovery time budget kept retryCount=0
forever, so they could loop across restarts without ever reaching MAX_RETRIES.
After breaking on deadline, call failDelivery() for all remaining entries
so retryCount is incremented. Entries stay in queue until MAX_RETRIES is
reached and they are pruned normally.
Also updates the maxRecoveryMs test to assert retryCount=1 on deferred entries.
The stdio tuple overload resolves differently across @types/node versions
(v20 vs v24/v25). Cast the spawn() result to ChildProcessWithoutNullStreams
to ensure proc.stderr?.on/off type-checks regardless of installed @types/node.
P1-C: After now >= deadline, the old code would iterate all remaining queue
entries and call failDelivery() on each — O(n) work that nullified the
maxRecoveryMs wall-clock cap on large queues.
Fix: break out of the recovery loop immediately when the deadline is exceeded.
Remaining entries are picked up on next startup unchanged (retryCount not
incremented). The deadline means 'stop here', not 'fail everything remaining'.
When delivery recovery ran out of the 60s time budget, remaining pending
entries were silently deferred to the next restart with no retryCount
increment. This caused them to loop forever across restarts, never hitting
MAX_RETRIES and never moving to failed/.
Fix: call failDelivery() on each remaining entry before breaking out of
the recovery loop (both the deadline check and the backoff-exceeds-deadline
check). This increments retryCount so that entries eventually exhaust
MAX_RETRIES and are permanently skipped.
Fixes#24353
Prevents crash when totals is undefined in byModel/byProvider/byAgent
sort comparators. Fixes 'Cannot read properties of undefined (reading
totalTokens)' crash that causes context overflow in active sessions.
Teams silently drops blocks 2+ when each deliver() opens its own
continueConversation() call. Accumulate rendered messages across all
deliver() calls and flush them together in markDispatchIdle().
On batch failure, retry each message individually so trailing blocks
are not silently lost. Log a warning when any individual messages fail
so flush failures are visible in logs.
emitChatFinal frees buffers on clean run completion, and the
maintenance timer sweeps abortedRuns after ABORTED_RUN_TTL_MS. But
runs that get stuck (e.g. LLM timeout without triggering clean
lifecycle end) are never aborted and their string buffers persist
indefinitely. This is the direct trigger for the StringAdd_CheckNone
OOM crash reported in the issue.
Add a stale buffer sweep in the maintenance timer that cleans up
buffers, deltaSentAt, and deltaLastBroadcastLen for any run not
updated within ABORTED_RUN_TTL_MS, regardless of abort status.
Closes#51821
resolvePackageEntrySource() treats all openBoundaryFileSync failures
as path-escape security violations. When an extension entry file is
simply missing (ENOENT, reason="path"), the gateway emits "extension
entry escapes package directory" and aborts — crashing in a loop.
Root cause: src/plugins/discovery.ts:478 checks !opened.ok but never
inspects opened.reason. SafeOpenSyncResult already distinguishes
"path" (ENOENT) from "validation" (actual path escape).
Fix: only push the security diagnostic when opened.reason is
"validation". For "path" or "io" failures, return null to skip the
entry silently — a missing file is not a security violation.
Closes#52445
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: HCL <chenglunhu@gmail.com>
- azure.md: "What you'll do" -> "What you will do"
- standing-orders.md: "Don't" -> "Avoid"
Per CLAUDE.md: avoid em dashes and apostrophes in headings because
they break Mintlify anchor links.
Correct anchor is #env-vars-and-env-loading (matching the actual
heading '## Env vars and .env loading' in help/faq.md).
Fixed in: tools/web.md, tools/perplexity-search.md, perplexity.md
isSessionManagerCached() checks TTL before returning stale hits but
never deletes expired entries from the Map. They accumulate
indefinitely over the lifetime of a long-running gateway.
Delete the expired entry when the TTL check fails so the Map stays
bounded to active sessions.
Closes#51820
The Control UI websocket connect params declared only admin, approvals,
and pairing scopes, omitting operator.read and operator.write. This
caused the gateway to reject all agent/send RPC calls from the dashboard
webchat with "missing scope: operator.write".
Add the two missing scopes to the connect params array so dashboard
webchat can send messages and read session state. Also update the test
fixture in gateway.node.test.ts to match the new scope list.
Fixes#52087
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Control UI websocket connect params declared only admin, approvals,
and pairing scopes, omitting operator.read and operator.write. This
caused the gateway to reject all agent/send RPC calls from the dashboard
webchat with "missing scope: operator.write".
Add the two missing scopes to the connect params array so dashboard
webchat can send messages and read session state.
Fixes#52087
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OpenClaw now tries ClawHub before npm for bare plugin specs.
Update install examples and guidance across:
- building-plugins.md: intro and publish step
- sdk-setup.md: publishing section with clawhub:/npm: prefix examples
- tools/plugin.md: CLI reference table
- community.md: submission guidance and quality bar
- Rewrite building-plugins.md as focused quick-start with CardGroup routing
- Rewrite sdk-channel-plugins.md with Steps, CodeGroup, Accordion walkthrough
- Move SDK Migration under Building Plugins nav, rename to "Migrate to SDK"
- Fix code examples and use valid Lucide icons for Mintlify Cards
* fix(plugins): enforce min host versions
* fix(plugins): tighten min host version validation
* chore(plugins): trim dead min host version code
* fix(plugins): handle malformed min host metadata
* fix(plugins): key manifest cache by host version
* fix: normalize sessionKey=current in shared session resolution
Move the "current" alias handling from a narrow session_status-only
mapping into the shared session resolution layer so every session tool
(session_status, sessions_history, sessions_send) resolves it
consistently.
Changes:
- Register "current" as a canonical session key in looksLikeSessionKey
so it is never misclassified as a sessionId
- Normalize "current" to the requester's own session key inside
resolveSessionReference and resolveInternalSessionKey
- Add "current" normalization in session_status before local store
lookup via the existing "main" alias scoping
- Add regression tests covering both main-session and cross-agent
resolution paths
Fixes#39570
* fix: keep session_status current bound to requester
* fix: preserve literal current session targets
* fix: preserve literal current in session_status
* fix: defer current alias in session_status
* fix: scope session_status current to active store (#39574) (thanks @BryanTegomoh)
* fix: preserve literal current session previews (#39574) (thanks @BryanTegomoh)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* Android: update status bar appearance in OpenClawTheme
* fix: update Android status bar appearance (#51098) (thanks @goweii)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* Android: fix Bitmap memory leaks in PhotosHandler
Bitmaps created by decodeScaledBitmap and intermediate scaled copies
inside encodeJpegUnderBudget were never recycled, leaking native memory
on every photos.latest invocation (up to 20 bitmaps per call).
- latest(): wrap bitmap usage in try/finally to guarantee recycle
- decodeScaledBitmap(): recycle the decoded bitmap after scaling
- encodeJpegUnderBudget(): use try/finally to recycle intermediate
scaled bitmaps on all exit paths (success, compress failure, and
cannot-shrink-further early returns)
Made-with: Cursor
* Android: guard decodeScaledBitmap against scale() exceptions
* fix: note Android photos bitmap cleanup (#41888) (thanks @Kaneki-x)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* Android: fix Bitmap memory leaks in CanvasController snapshots
snapshotPngBase64() and snapshotBase64() create bitmaps via
captureBitmap() and scaleForMaxWidth() but never recycle them,
leaking native memory on every canvas snapshot invocation.
Wrap both methods in nested try/finally blocks:
- outer: always recycles the captured bitmap
- inner: recycles the scaled bitmap only when it differs from the
captured one (scaleForMaxWidth returns `this` when no scaling needed)
Made-with: Cursor
* fix: note Android canvas snapshot bitmap leak in changelog (#41889) (thanks @Kaneki-x)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* Android: fix temp file leak in CameraHandler.handleClip
When readBytes() throws (IOException, OOM, etc.), the recorded clip
file was never deleted because delete() only ran on the success path.
Move file.delete() into a finally block so the temp file is cleaned up
regardless of whether readBytes() succeeds or fails.
Made-with: Cursor
* fix: Android camera clip cleanup (#41890) (thanks @Kaneki-x)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
The contacts search passed user input directly into a LIKE pattern
without escaping % and _ characters, causing them to act as SQL
wildcards and return incorrect results.
Add an escapeLikePattern() helper that escapes \, %, and _ with a
backslash, and add ESCAPE '\' to the selection clause so SQLite
treats them as literal characters.
Made-with: Cursor
* feat(usage): add usage page styles and localization
- Introduced a new `usage.css` file for styling the usage overview page.
- Updated `en.ts` localization file to include new usage-related translations.
- Refactored the usage rendering components to utilize the new localization strings for improved user experience.
- Enhanced the `app-render-usage-tab.ts` to better structure the data passed to the rendering function.
* feat(ui): enhance styling and functionality for usage overview and chat components
- Updated `package.json` to include new built dependencies.
- Refined CSS styles across various files to improve UI consistency and accessibility, including adjustments to color themes and layout structures.
- Introduced new responsive grid layouts for usage overview and chat components, enhancing the user experience on different screen sizes.
- Added functionality to hide context notices based on token freshness in chat view.
- Implemented new rendering functions for usage statistics, improving data presentation and user interaction.
* feat(usage): enhance usage overview styling and rendering options
- Added new CSS classes for improved layout and styling of usage insight cards and error lists.
- Updated rendering functions to support customizable class names for usage insight cards and error lists, enhancing flexibility in UI presentation.
- Implemented a wide card layout and specific styling for error lists to improve visual clarity and user experience.
* fix(ui): address review feedback on usage and chat layout
* docs(changelog): add entry for usage UI improvements
* fix(plugin-sdk): remove relative extension boundary escapes
* Gate new plugin-sdk subpaths on host version
* Add changelog entry for #51939
* Fix local staging for plugin-sdk host version gate
* Raise host floor for line and googlechat plugins
---------
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
* feat(sessions): implement multi-session deletion and selection functionality
- Added `deleteSessionsAndRefresh` function to handle deletion of multiple sessions.
- Updated session state management to track selected session keys.
- Enhanced UI to support bulk actions for selected sessions, including delete and unselect options.
- Refactored related tests to accommodate new multi-session deletion logic.
- Improved responsiveness of sessions table with new CSS rules for mobile layouts.
* feat(sessions): add page deselection functionality and enhance error handling
- Implemented `onDeselectPage` method to allow deselecting specific pages in the session view.
- Updated `deleteSessionsAndRefresh` to handle multiple deletion errors, storing them in an array and displaying a consolidated error message.
- Enhanced tests to verify the new deselection behavior and updated error handling for session deletions.
* perf(core): narrow sandbox status imports for error helpers
* fix(build): add runtime boundaries for reply understanding
Add missing lazy-load runtime shim files required by get-reply.ts.
* fix(debug): remove duplicate spacing in ingress logs
Use logIngressStage suffix spacing consistently for media and link understanding debug lines.
The context-usage banner in the web UI fell back to inputTokens when
totalTokens was missing. inputTokens is accumulated across all API
calls in a run (tool-use loops, compaction retries), so it overstates
actual context window utilization -- e.g. showing "100% context used
757.3k / 200k" when the real prompt snapshot is only 46k/200k (23%).
Drop the inputTokens fallback so the banner only fires when a genuine
prompt snapshot (totalTokens) is available.
Made-with: Cursor
* fix(matrix): pass agentId to buildMentionRegexes for agent-level mention patterns
* fix(matrix): resolve conflicts from main branch
* Retrigger CI
---------
Co-authored-by: Dinakar Sarbada <dinakars777@users.noreply.github.com>
* feat(gateway): persist webchat inbound images to disk
Images sent via the webchat control UI (chat.send RPC) were parsed into
content blocks but never written to disk, unlike WhatsApp and Telegram
handlers which call saveMediaBuffer(). This caused:
- Images lost after conversation compaction (only existed as ephemeral base64)
- Image editing/generation workflows failing for webchat-origin images
- Incomplete ~/.openclaw/media/inbound/ directory
After parseMessageWithAttachments extracts parsedImages, iterate and
persist each via saveMediaBuffer(buffer, mimeType, 'inbound'). Uses
fire-and-forget (.catch + warn log) so disk I/O never blocks the
chat.send response path.
Fixes#47930
* fix(gateway): address PR review comments on webchat image persistence
- Move saveMediaBuffer calls after sendPolicy/stop/dedupe checks so
rejected or retried requests don't write files to disk (Codex P1)
- Await all saves and collect SavedMedia results into persistedImages
so the persisted paths are available in scope (Greptile P1)
- Preserve Error stack trace in warn log instead of coercing to
toString() (Greptile P2)
- Switch to Promise.all for concurrent writes
* fix(gateway): address remaining review comments on webchat image persistence
- Revert to fire-and-forget pattern (no await) to eliminate race window
where retried requests miss the in-flight guard during image saves
- Remove unused SavedMedia import and persistedImages collection
- Use formatForLog for consistent error logging with stack traces
- Add NOTE comment about path propagation being a follow-up task
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(gateway): gate image persistence to webchat callers and defer base64 decode
* fix: drop unrelated format churn in lifecycle.test.ts
* gateway: clarify image persistence scope covers all chat.send callers
* fix(gateway): use generic chat.send log prefix for image persistence warnings
* fix(gateway): persist chat.send image refs in transcript
* fix(gateway): keep chat.send image refs off visible text
* fix(gateway): persist chat send media refs on dispatch
* fix(gateway): serialize chat send image persistence
* fix(gateway): persist chat send media after dispatch
* fix: persist chat.send inbound images across follow-ups (#51324) (thanks @fuller-stack-dev)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* feat(telegram): auto-rename DM topics on first message
fix(telegram): use bot.api for topic rename to avoid SecretRef resolution
* fix(telegram): address security + test review feedback
- Fix test assertion: DEFAULT_PROMPT_SUBSTRING matches 'very short'
- Use RawBody instead of Body (no envelope metadata to LLM)
- Truncate user message to 500 chars for LLM prompt
- Remove user-derived content from verbose logs
- Remove redundant threadSpec.id null check
- Fix AutoTopicLabelParams type to match generateTopicLabel
* fix(telegram): use effective dm auto-topic config
* fix(telegram): detect direct auto-topic overrides
* fix: auto-rename Telegram DM topics on first message (#51502) (thanks @Lukavyi)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* feat(telegram): support custom apiRoot for alternative API endpoints
Add `apiRoot` config option to allow users to specify custom Telegram Bot
API endpoints (e.g., self-hosted Bot API servers). Threads the configured
base URL through all Telegram API call sites: bot creation, send, probe,
audit, media download, and api-fetch. Extends SSRF policy to dynamically
trust custom apiRoot hostname for media downloads.
Closes#28535
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(telegram): thread apiRoot through allowFrom lookups
* fix(telegram): honor lookup transport and local file paths
* refactor(telegram): unify username lookup plumbing
* fix(telegram): restore doctor lookup imports
* fix: document Telegram apiRoot support (#48842) (thanks @Cypherm)
---------
Co-authored-by: Cypherm <28184436+Cypherm@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
- Added a test to ensure no warnings for legacy Brave config when bundled web search allowlist compatibility is applied.
- Updated validation logic to incorporate compatibility configuration for bundled web search plugins.
- Refactored the ensureRegistry function to utilize the new compatibility handling.
* test: align extension runtime mocks with plugin-sdk
Update stale extension tests to mock the plugin-sdk runtime barrels that production code now imports, and harden the Signal tool-result harness around system-event assertions so the channels lane matches current extension boundaries.
Regeneration-Prompt: |
Verify the failing channels-lane tests against current origin/main in an isolated worktree before changing anything. If the failures reproduce on main, keep the fix test-only unless production behavior is clearly wrong. Recent extension refactors moved Telegram, WhatsApp, and Signal code onto plugin-sdk runtime barrels, so update stale tests that still mock old core module paths to intercept the seams production code now uses. For Signal reaction notifications, avoid brittle assertions that depend on shared queued system-event state when a direct harness spy on enqueue behavior is sufficient. Preserve scope: only touch the failing tests and their local harness, then rerun the reproduced targeted tests plus the full channels lane and repo check gate.
* test: fix extension test drift on main
* fix: lazy-load bundled web search plugin registry
* test: make matrix sweeper failure injection portable
* fix: split heavy matrix runtime-api seams
* fix: simplify bundled web search id lookup
* test: tolerate windows env key casing
Reuse pi-ai's Anthropic client injection seam for streaming, and add
the OpenClaw-side provider discovery, auth, model catalog, and tests
needed to expose anthropic-vertex cleanly.
Signed-off-by: sallyom <somalley@redhat.com>
When a non-default accountId is specified but not found in the accounts
config, resolveTelegramToken() falls through to channel-level defaults
(botToken, tokenFile, env) — silently routing messages via the wrong
bot's token. This is a cross-bot message leak with no error or warning.
Root cause: extensions/telegram/src/token.ts:44-46, resolveAccountCfg()
returns undefined for unknown accountIds but code continues to fallbacks.
Introduced in e5bca0832f when Telegram moved to extensions/.
Fix: return { token: "", source: "none" } with a diagnostic log when
a non-default accountId is not found. Existing behavior for known
accounts (with or without per-account tokens) preserved.
Test: added "does not fall through when non-default accountId not in
config" — 1/1 new, 10/10 existing unaffected.
Closes#49383
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: HCL <chenglunhu@gmail.com>
Fixes#35822 — Bot Framework conversation.id format is incompatible with
Graph API /chats/{chatId}. Added resolveGraphChatId() to look up the
Graph-native chat ID via GET /me/chats, cached in the conversation store.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add updateActivity/deleteActivity to MSTeamsAdapter
- Add onReactionsAdded/onReactionsRemoved to MSTeamsActivityHandler
- Implement directory self() to return bot identity from appId credential
- Add tests for self() in channel.directory.test.ts
When a route-level (teams/channel) allowlist was configured but the sender
allowlist (allowFrom/groupAllowFrom) was empty, resolveSenderScopedGroupPolicy
would downgrade the effective group policy from "allowlist" to "open", allowing
any Teams user to interact with the bot.
The fix: when channelGate.allowlistConfigured is true and effectiveGroupAllowFrom
is empty, preserve the configured groupPolicy ("allowlist") rather than letting
it be downgraded to "open". This ensures an empty sender allowlist with an active
route allowlist means deny-all rather than allow-all.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(azure): replace ARM template deployment with pure az CLI commands
Rewrites the Azure install guide to use individual az CLI commands
instead of referencing ARM templates in infra/azure/templates/ (removed
upstream). Each Azure resource (NSG, VNet, subnets, VM, Bastion) is now
created with explicit az commands, preserving the same security posture
(Bastion-only SSH, no public IP, NSG hardening).
Also addresses BradGroux review feedback from #47898:
- Add cost considerations section (Bastion ~$140/mo, VM ~$55/mo)
- Add cleanup/teardown section (az group delete)
- Remove stale /install/azure/azure redirect from docs.json
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs(azure): split into multiple Steps blocks for richer TOC
Add Quick path and What you need sections. Split the single Steps
block into three (Configure deployment, Deploy Azure resources,
Install OpenClaw) so H2 headers appear in the Mintlify sidebar TOC.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs(azure): remove Quick path section
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs(azure): fix cost section LaTeX rendering, remove comparison
Escape dollar signs to prevent Mintlify LaTeX interpretation.
Also escape underscores in VM SKU name within bold text.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs(azure): add caveat that deallocated VM stops Gateway
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs(azure): simplify install step with clearer description
Download then run pattern (no sudo). Clarify that installer handles
Node LTS, dependencies, OpenClaw install, and onboarding wizard.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs(azure): add Bastion provisioning latency note
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs(azure): use deployment variables in cost and cleanup sections
Replace hardcoded rg-openclaw/vm-openclaw with variables in
deallocate/start and group delete commands so users who customized
names in step 3 get correct commands.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs(azure): fix formatting (oxfmt)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs: add delegate architecture guide for organizational deployments
Adds a guide for running OpenClaw as a named delegate for organizations.
Covers three capability tiers (read-only, send-on-behalf, proactive),
M365 and Google Workspace delegation setup, security guardrails, and
integration with multi-agent routing.
AI-assisted: Claude Code (Opus 4.6)
Based on: Production deployment at a 501(c)(3) nonprofit
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: address review — add Google DWD warning, fix canvas in deny list
- Add security warning for Google Workspace domain-wide delegation
matching the existing M365 application access policy warning
- Add "canvas" to the security guardrails tool deny list for
consistency with the full example and multi-agent.md
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: fix Tier 1 description to match read-only permissions
Remove "draft replies (saved to Drafts folder)" from Tier 1 since
saving drafts requires write access. Tier 1 is strictly read-only —
the agent summarizes and flags via chat, human acts on the mailbox.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: fix oxfmt formatting for delegate-architecture and docs.json
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: fix broken links to /automation/standing-orders
Standing orders is a deployment pattern, not an existing doc page.
Replaced with inline descriptions and links to /automation/cron-jobs
and #security-guardrails anchor.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: move hardening to prerequisites before identity provider setup
Restructure per community feedback: isolation, tool restrictions,
sandbox, hard blocks, and audit trail now come BEFORE granting any
credentials. The most dangerous step (tenant-wide permissions) no
longer precedes the most important step (scoping and isolation).
Also strengthened M365 and Google Workspace security warnings with
actionable verification steps.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: add standing orders guide and fix broken links
Add docs/automation/standing-orders.md covering:
- Why standing orders (agent autonomy vs human bottleneck)
- Anatomy of a standing order (scope, triggers, gates, escalation)
- Integration with cron jobs for time-based enforcement
- Execute-Verify-Report pattern for execution discipline
- Three production-tested examples (content, finance, monitoring)
- Multi-program architecture for complex agents
- Best practices (do's and don'ts)
Update delegate-architecture.md to link standing orders references
to the new page instead of dead links.
Add standing-orders to Automation nav group in docs.json (en + zh-CN).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: address review feedback on standing-orders
- P1: Clarify that standing orders should go in AGENTS.md (auto-injected)
rather than arbitrary subdirectory files. Add Tip callout explaining
which workspace files are bootstrapped.
- P2: Remove dead /concepts/personality-files link, replace with
/concepts/agent-workspace which covers bootstrap files.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: reduce low-memory Vitest pressure
Reuse the bundled config baseline inside doc-baseline tests, keep that hotspot out of the shared unit-fast lane, and make OPENCLAW_TEST_PROFILE=low default to process forks instead of vmForks.
* test: keep low-profile vmForks in CI
Scope the low-profile forks fallback to local runs so the existing CI contracts lane keeps its current pool behavior.
* fix(matrix): load legacy helper natively when possible
* fix(matrix): narrow jiti fallback to source helpers
* fix(matrix): fall back to jiti for source-style helper wrappers
* fix(gateway): increase WS handshake timeout from 3s to 10s
The 3-second default is too aggressive when the event loop is under load
(concurrent sessions, compaction, agent turns), causing spurious
'gateway closed (1000)' errors on CLI commands like `openclaw cron list`.
Changes:
- Increase DEFAULT_HANDSHAKE_TIMEOUT_MS from 3_000 to 10_000
- Add OPENCLAW_HANDSHAKE_TIMEOUT_MS env var for user override (no VITEST gate)
- Keep OPENCLAW_TEST_HANDSHAKE_TIMEOUT_MS as fallback for existing tests
Fixes#46892
* fix: restore VITEST guard on test env var, use || for empty-string fallback, fix formatting
* fix: cover gateway handshake timeout env override (#49262) (thanks @fuller-stack-dev)
---------
Co-authored-by: Wilfred <wilfred@Wilfreds-Mac-mini.local>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix: make cleanup "keep" persist subagent sessions indefinitely
* feat: expose subagent session metadata in sessions list
* fix: include status and timing in sessions_list tool
* fix: hide injected timestamp prefixes in chat ui
* feat: push session list updates over websocket
* feat: expose child subagent sessions in subagents list
* feat: add admin http endpoint to kill sessions
* Emit session.message websocket events for transcript updates
* Estimate session costs in sessions list
* Add direct session history HTTP and SSE endpoints
* Harden dashboard session events and history APIs
* Add session lifecycle gateway methods
* Add dashboard session API improvements
* Add dashboard session model and parent linkage support
* fix: tighten dashboard session API metadata
* Fix dashboard session cost metadata
* Persist accumulated session cost
* fix: stop followup queue drain cfg crash
* Fix dashboard session create and model metadata
* fix: stop guessing session model costs
* Gateway: cache OpenRouter pricing for configured models
* Gateway: add timeout session status
* Fix subagent spawn test config loading
* Gateway: preserve operator scopes without device identity
* Emit user message transcript events and deduplicate plugin warnings
* feat: emit sessions.changed lifecycle event on subagent spawn
Adds a session-lifecycle-events module (similar to transcript-events)
that emits create events when subagents are spawned. The gateway
server.impl.ts listens for these events and broadcasts sessions.changed
with reason=create to SSE subscribers, so dashboards can pick up new
subagent sessions without polling.
* Gateway: allow persistent dashboard orchestrator sessions
* fix: preserve operator scopes for token-authenticated backend clients
Backend clients (like agent-dashboard) that authenticate with a valid gateway
token but don't present a device identity were getting their scopes stripped.
The scope-clearing logic ran before checking the device identity decision,
so even when evaluateMissingDeviceIdentity returned 'allow' (because
roleCanSkipDeviceIdentity passed for token-authed operators), scopes were
already cleared.
Fix: also check decision.kind before clearing scopes, so token-authenticated
operators keep their requested scopes.
* Gateway: allow operator-token session kills
* Fix stale active subagent status after follow-up runs
* Fix dashboard image attachments in sessions send
* Fix completed session follow-up status updates
* feat: stream session tool events to operator UIs
* Add sessions.steer gateway coverage
* Persist subagent timing in session store
* Fix subagent session transcript event keys
* Fix active subagent session status in gateway
* bump session label max to 512
* Fix gateway send session reactivation
* fix: publish terminal session lifecycle state
* feat: change default session reset to effectively never
- Change DEFAULT_RESET_MODE from "daily" to "idle"
- Change DEFAULT_IDLE_MINUTES from 60 to 0 (0 = disabled/never)
- Allow idleMinutes=0 through normalization (don't clamp to 1)
- Treat idleMinutes=0 as "no idle expiry" in evaluateSessionFreshness
- Default behavior: mode "idle" + idleMinutes 0 = sessions never auto-reset
- Update test assertion for new default mode
* fix: prep session management followups (#50101) (thanks @clay-datacurve)
---------
Co-authored-by: Tyler Yust <TYTYYUST@YAHOO.COM>
* fix(bluebubbles): auto-create chats for new numbers, persist outbound messages to session transcripts
Two fixes for BlueBubbles message tool behavior:
1. **Attachment sends to new phone numbers**: sendBlueBubblesAttachment now
auto-creates a new DM chat (via /api/v1/chat/new) when no existing chat
is found for a handle target, matching the behavior already present in
sendMessageBlueBubbles for text sends. The existing createNewChatWithMessage
is refactored into a reusable createChatForHandle that returns the chatGuid.
2. **Outbound message session persistence**: Ensures outbound messages sent
via the message tool are reliably tracked in session transcripts:
- ensureOutboundSessionEntry now falls back to directly creating a session
store entry when recordSessionMetaFromInbound returns null, guaranteeing
a sessionId exists for the subsequent mirror append.
- appendAssistantMessageToSessionTranscript now normalizes the session key
(lowercased) when looking up the store, preventing case mismatches
between the store keys and the mirror sessionKey.
Tests added for all changes.
* test(slack): verify outbound session tracking and new target sends for Slack
The shared infrastructure changes from the BlueBubbles fix (session key
normalization in transcript.ts and fallback session entry creation in
outbound-session.ts) already cover Slack. Slack's sendMessageSlack uses
conversations.open to auto-create DM channels for new user targets.
Add tests confirming:
- Slack user DM and channel session route resolution (outbound.test.ts)
- Slack session key normalization for transcript append (sessions.test.ts)
- Slack outbound sendText/sendMedia to new user and channel targets (channel.test.ts)
* fix(cron): skip stale delayed deliveries
* fix: prep PR #50092
* build: mirror uuid for msteams
Add uuid to both the msteams bundled extension and the root package so the workspace build can resolve @microsoft/agents-hosting during tsdown while standalone extension installs also have the runtime dependency available.
Regeneration-Prompt: |
pnpm build failed because @microsoft/agents-hosting 1.3.1 requires uuid in its published JS but does not declare it in its package manifest. The msteams extension dynamically imports that package, and the workspace build resolves it from the root dependency graph. Mirror uuid into the root package for workspace builds and keep it in extensions/msteams/package.json so standalone plugin installs also resolve it. Update the lockfile to match the manifest changes.
* build: prune stale plugin dist symlinks
Remove stale dist and dist-runtime plugin node_modules symlinks before tsdown runs. These links point back into extension installs, and tsdown's clean step can traverse them on rebuilds and hollow out the active pnpm dependency tree before plugin-sdk declaration generation runs.
Regeneration-Prompt: |
pnpm build was intermittently failing in the plugin-sdk:dts phase after earlier build steps had already run. The symptom looked like missing root packages such as zod, ajv, commander, and undici even though a fresh install briefly fixed the problem. Investigate the build pipeline step by step rather than patching TypeScript errors. Confirm whether rebuilds mutate node_modules, identify the first step that does it, and preserve existing runtime-postbuild behavior.
The key constraint is that dist and dist-runtime plugin node_modules links are intentional for runtime packaging, so do not remove that feature globally. Instead, make rebuilds safe by deleting only stale symlinks left in generated output before invoking tsdown, so tsdown cleanup cannot recurse back into the live pnpm install tree. Verify with repeated pnpm build runs.
import the config-backed Slack directory helpers into the Slack channel plugin so directory.listPeers and directory.listGroups no longer throw at runtime, and add a regression test covering configured DM peer listing
* MiniMax: add M2.7 models and update default to M2.7
- Add MiniMax-M2.7 and MiniMax-M2.7-highspeed to provider catalog and model definitions
- Update default model from MiniMax-M2.5 to MiniMax-M2.7 across onboard, portal, and provider configs
- Update isModernMiniMaxModel to recognize M2.7 prefix
- Update all test fixtures to reflect M2.7 as default
Made-with: Cursor
* MiniMax: add extension test for model definitions
* update 2.7
* feat: add MiniMax M2.7 models and update default (#49691) (thanks @liyuan97)
---------
Co-authored-by: George Zhang <georgezhangtj97@gmail.com>
description: End-to-end Parallels smoke, upgrade, and rerun workflow for OpenClaw across macOS, Windows, and Linux guests. Use when Codex needs to run, rerun, debug, or interpret VM-based install, onboarding, gateway smoke tests, latest-release-to-main upgrade checks, fresh snapshot retests, or optional Discord roundtrip verification under Parallels.
---
# OpenClaw Parallels Smoke
Use this skill for Parallels guest workflows and smoke interpretation. Do not load it for normal repo work.
## Global rules
- Use the snapshot most closely matching the requested fresh baseline.
- Gateway verification in smoke runs should use `openclaw gateway status --deep --require-rpc` unless the stable version being checked does not support it yet.
- Stable `2026.3.12` pre-upgrade diagnostics may require a plain `gateway status --deep` fallback.
- Treat `precheck=latest-ref-fail` on that stable pre-upgrade lane as baseline, not automatically a regression.
- Pass `--json` for machine-readable summaries.
- Per-phase logs land under `/tmp/openclaw-parallels-*`.
- Do not run local and gateway agent turns in parallel on the same fresh workspace or session.
- For `prlctl exec`, pass the VM name before `--current-user` (`prlctl exec "$VM" --current-user ...`), not the other way around.
- If the workflow installs OpenClaw from a repo checkout instead of the site installer/npm release, finish by installing a real guest CLI shim and verifying it in a fresh guest shell. `pnpm openclaw ...` inside the repo is not enough for handoff parity.
- On macOS guests, prefer a user-global install plus a stable PATH-visible shim:
- install with `NPM_CONFIG_PREFIX="$HOME/.npm-global" npm install -g .`
- make sure `~/.local/bin/openclaw` exists or `~/.npm-global/bin` is on PATH
- verify from a brand-new guest shell with `which openclaw` and `openclaw --version`
- Flow: fresh snapshot -> install npm package baseline -> smoke -> install current main tgz on the same guest -> smoke again.
- Same-guest update verification should set the default model explicitly to `openai/gpt-5.4` before the agent turn and use a fresh explicit `--session-id` so old session model state does not leak into the check.
- The aggregate npm-update wrapper must resolve the Linux VM with the same Ubuntu fallback policy as `parallels-linux-smoke.sh` before both fresh and update lanes. On Peter's current host, missing `Ubuntu 24.04.3 ARM64` should fall back to `Ubuntu 25.10`.
- On Windows same-guest update checks, restart the gateway after the npm upgrade before `gateway status` / `agent`; in-place global npm updates can otherwise leave stale hashed `dist/*` module imports alive in the running service.
- For Windows same-guest update checks, prefer the done-file/log-drain PowerShell runner pattern over one long-lived `prlctl exec ... powershell -EncodedCommand ...` transport. The guest can finish successfully while the outer `prlctl exec` still hangs.
- Linux same-guest update verification should also export `HOME=/root`, pass `OPENAI_API_KEY` via `prlctl exec ... /usr/bin/env`, and use `openclaw agent --local`; the fresh Linux baseline does not rely on persisted gateway credentials.
## CLI invocation footgun
- The Parallels smoke shell scripts should tolerate a literal bare `--` arg so `pnpm test:parallels:* -- --json` and similar forwarded invocations work without needing to call `bash scripts/e2e/...` directly.
- Default to the snapshot closest to `macOS 26.3.1 latest`.
- On Peter's Tahoe VM, `fresh-latest-march-2026` can hang in `prlctl snapshot-switch`; if restore times out there, rerun with `--snapshot-hint 'macOS 26.3.1 latest'` before blaming auth or the harness.
- The macOS smoke should include a dashboard load phase after gateway health: resolve the tokenized URL with `openclaw dashboard --no-open`, verify the served HTML contains the Control UI title/root shell, then open Safari and require an established localhost TCP connection from Safari to the gateway port.
-`prlctl exec` is fine for deterministic repo commands, but use the guest Terminal or `prlctl enter` when installer parity or shell-sensitive behavior matters.
- Multi-word `openclaw agent --message ...` checks should go through a guest shell wrapper (`guest_current_user_sh` / `guest_current_user_cli` or `/bin/sh -lc ...`), not raw `prlctl exec ... node openclaw.mjs ...`, or the message can be split into extra argv tokens and Commander reports `too many arguments for 'agent'`.
- When ref-mode onboarding stores `OPENAI_API_KEY` as an env secret ref, the post-onboard agent verification should also export `OPENAI_API_KEY` for the guest command. The gateway can still reject with pairing-required and fall back to embedded execution, and that fallback needs the env-backed credential available in the shell.
- On the fresh Tahoe snapshot, `brew` exists but `node` may be missing from PATH in noninteractive exec. Use `/opt/homebrew/bin/node` when needed.
- Fresh host-served tgz installs should install as guest root with `HOME=/var/root`, then run onboarding as the desktop user via `prlctl exec --current-user`.
- Root-installed tgz smoke can log plugin blocks for world-writable `extensions/*`; do not treat that as an onboarding or gateway failure unless plugin loading is the task.
- Use the snapshot closest to `pre-openclaw-native-e2e-2026-03-12`.
- Always use `prlctl exec --current-user`; plain `prlctl exec` lands in `NT AUTHORITY\\SYSTEM`.
- Prefer explicit `npm.cmd` and `openclaw.cmd`.
- Use PowerShell only as the transport with `-ExecutionPolicy Bypass`, then call the `.cmd` shims from inside it.
- Multi-word `openclaw agent --message ...` checks should call `& $openclaw ...` inside PowerShell, not `Start-Process ... -ArgumentList` against `openclaw.cmd`, or Commander can see split argv and throw `too many arguments for 'agent'`.
- Windows installer/tgz phases now retry once after guest-ready recheck; keep new Windows smoke steps idempotent so a transport-flake retry is safe.
- Windows global `npm install -g` phases can stay quiet for a minute or more even when healthy; inspect the phase log before calling it hung, and only treat it as a regression once the retry wrapper or timeout trips.
- Fresh Windows ref-mode onboard should use the same background PowerShell runner plus done-file/log-drain pattern as the npm-update helper, including startup materialization checks, host-side timeouts on short poll `prlctl exec` calls, and retry-on-poll-failure behavior for transient transport flakes.
- Fresh Windows ref-mode agent verification should set `OPENAI_API_KEY` in the PowerShell environment before invoking `openclaw.cmd agent`, for the same pairing-required fallback reason as macOS.
- The Windows upgrade smoke lane should restart the managed gateway after `upgrade.install-main` and before `upgrade.onboard-ref`, or the old process can keep the previous gateway token and fail `gateway-health` with `unauthorized: gateway token mismatch`.
- Keep onboarding and status output ASCII-clean in logs; fancy punctuation becomes mojibake in current capture paths.
- If you hit an older run with `rc=255` plus an empty `fresh.install-main.log` or `upgrade.install-main.log`, treat it as a likely `prlctl exec` transport drop after guest start-up, not immediate proof of an npm/package failure.
- Use the snapshot closest to fresh `Ubuntu 24.04.3 ARM64`.
- If that exact VM is missing on the host, fall back to the closest Ubuntu guest with a fresh poweroff snapshot. On Peter's host today, that is `Ubuntu 25.10`.
- Use plain `prlctl exec`; `--current-user` is not the right transport on this snapshot.
- Fresh snapshots may be missing `curl`, and `apt-get update` can fail on clock skew. Bootstrap with `apt-get -o Acquire::Check-Date=false update` and install `curl ca-certificates`.
- Fresh `main` tgz smoke still needs the latest-release installer first because the snapshot has no Node or npm before bootstrap.
- This snapshot does not have a usable `systemd --user` session; managed daemon install is unsupported.
-`prlctl exec` reaps detached Linux child processes on this snapshot, so detached background gateway runs are not trustworthy smoke signals.
- Treat `gateway=skipped-no-detached-linux-gateway` plus `daemon=systemd-user-unavailable` as baseline on that Linux lane, not a regression.
## Discord roundtrip
- Discord roundtrip is optional and should be enabled with:
-`--discord-token-env`
-`--discord-guild-id`
-`--discord-channel-id`
- Keep the Discord token only in a host env var.
- Use installed `openclaw message send/read`, not `node openclaw.mjs message ...`.
- Set `channels.discord.guilds` as one JSON object, not dotted config paths with snowflakes.
- Avoid long `prlctl enter` or expect-driven Discord config scripts; prefer `prlctl exec --current-user /bin/sh -lc ...` with short commands.
- For a narrower macOS-only Discord proof run, the existing `parallels-discord-roundtrip` skill is the deep-dive companion.
description: Maintainer workflow for reviewing, triaging, preparing, closing, or landing OpenClaw pull requests and related issues. Use when Codex needs to validate bug-fix claims, search for related issues or PRs, apply or recommend close/reason labels, prepare GitHub comments safely, check review-thread follow-up, or perform maintainer-style PR decision making before merge or closure.
---
# OpenClaw PR Maintainer
Use this skill for maintainer-facing GitHub workflow, not for ordinary code changes.
## Apply close and triage labels correctly
- If an issue or PR matches an auto-close reason, apply the label and let `.github/workflows/auto-response.yml` handle the comment/close/lock flow.
- Do not manually close plus manually comment for these reasons.
-`r:*` labels can be used on both issues and PRs.
- Current reasons:
-`r: skill`
-`r: support`
-`r: no-ci-pr`
-`r: too-many-prs`
-`r: testflight`
-`r: third-party-extension`
-`r: moltbook`
-`r: spam`
-`invalid`
-`dirty` for PRs only
## Enforce the bug-fix evidence bar
- Never merge a bug-fix PR based only on issue text, PR text, or AI rationale.
- Before landing, require:
1. symptom evidence such as a repro, logs, or a failing test
2. a verified root cause in code with file/line
3. a fix that touches the implicated code path
4. a regression test when feasible, or explicit manual verification plus a reason no test was added
- If the claim is unsubstantiated or likely wrong, request evidence or changes instead of merging.
- If the linked issue appears outdated or incorrect, correct triage first. Do not merge a speculative fix.
## Handle GitHub text safely
- For issue comments and PR comments, use literal multiline strings or `-F - <<'EOF'` for real newlines. Never embed `\n`.
- Do not use `gh issue/pr comment -b "..."` when the body contains backticks or shell characters. Prefer a single-quoted heredoc.
- Do not wrap issue or PR refs like `#24643` in backticks when you want auto-linking.
- PR landing comments should include clickable full commit links for landed and source SHAs when present.
## Search broadly before deciding
- Prefer targeted keyword search before proposing new work or closing something as duplicate.
- Use `--repo openclaw/openclaw` with `--match title,body` first.
- Add `--match comments` when triaging follow-up discussion.
- Do not stop at the first 500 results when the task requires a full search.
- If bot review conversations exist on your PR, address them and resolve them yourself once fixed.
- Leave a review conversation unresolved only when reviewer or maintainer judgment is still needed.
- When landing or merging any PR, follow the global `/landpr` process.
- Use `scripts/committer "<msg>" <file...>` for scoped commits instead of manual `git add` and `git commit`.
- Keep commit messages concise and action-oriented.
- Group related changes; avoid bundling unrelated refactors.
- Use `.github/pull_request_template.md` for PR submissions and `.github/ISSUE_TEMPLATE/` for issues.
## Extra safety
- If a close or reopen action would affect more than 5 PRs, ask for explicit confirmation with the exact count and target query first.
-`sync` means: if the tree is dirty, commit all changes with a sensible Conventional Commit message, then `git pull --rebase`, then `git push`. Stop if rebase conflicts cannot be resolved safely.
description: Maintainer workflow for OpenClaw releases, prereleases, changelog release notes, and publish validation. Use when Codex needs to prepare or verify stable or beta release steps, align version naming, assemble release notes, check release auth requirements, or validate publish-time commands and artifacts.
---
# OpenClaw Release Maintainer
Use this skill for release and publish-time workflow. Keep ordinary development changes and GHSA-specific advisory work outside this skill.
## Respect release guardrails
- Do not change version numbers without explicit operator approval.
- Ask permission before any npm publish or release step.
- This skill should be sufficient to drive the normal release flow end-to-end.
- Use the private maintainer release docs for credentials, recovery steps, and mac signing/notary specifics, and use `docs/reference/RELEASING.md` for public policy.
- Core `openclaw` publish is manual `workflow_dispatch`; creating or pushing a tag does not publish by itself.
## Keep release channel naming aligned
-`stable`: tagged releases only, published to npm `latest` and then mirrored onto npm `beta` unless `beta` already points at a newer prerelease
-`beta`: prerelease tags like `vYYYY.M.D-beta.N`, with npm dist-tag `beta`
- Prefer `-beta.N`; do not mint new `-1` or `-2` beta suffixes
-`dev`: moving head on `main`
- When using a beta Git tag, publish npm with the matching beta version suffix so the plain version is not consumed or blocked
- Before creating a release tag, make every version location above match the version encoded by that tag.
- For fallback correction tags like `vYYYY.M.D-N`, the repo version locations still stay at `YYYY.M.D`.
- “Bump version everywhere” means all version locations above except `appcast.xml`.
- Release signing and notary credentials live outside the repo in the private maintainer docs.
- Every OpenClaw release ships the npm package and macOS app together.
- The production Sparkle feed lives at `https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml`, and the canonical published file is `appcast.xml` on `main` in the `openclaw` repo.
- That shared production Sparkle feed is stable-only. Beta mac releases may
upload assets to the GitHub prerelease, but they must not replace the shared
`appcast.xml` unless a separate beta feed exists.
- For fallback correction tags like `vYYYY.M.D-N`, the repo version still stays
at `YYYY.M.D`, but the mac release must use a strictly higher numeric
`APP_BUILD` / Sparkle build than the original release so existing installs
see it as newer.
## Build changelog-backed release notes
- Changelog entries should be user-facing, not internal release-process notes.
- When cutting a mac release with a beta GitHub prerelease:
- tag `vYYYY.M.D-beta.N` from the release commit
- create a prerelease titled `openclaw YYYY.M.D-beta.N`
- use release notes from the matching `CHANGELOG.md` version section
- attach at least the zip and dSYM zip, plus dmg if available
- Keep the top version entries in `CHANGELOG.md` sorted by impact:
description: Investigate `pnpm test` memory growth, Vitest worker OOMs, and suspicious RSS increases in OpenClaw using the `scripts/test-parallel.mjs` heap snapshot tooling. Use when Codex needs to reproduce test-lane memory growth, collect repeated `.heapsnapshot` files, compare snapshots from the same worker PID, distinguish transformed-module retention from real data leaks, and fix or reduce the impact by patching cleanup logic or isolating hotspot tests.
---
# OpenClaw Test Heap Leaks
Use this skill for test-memory investigations. Do not guess from RSS alone when heap snapshots are available.
## Workflow
1. Reproduce the failing shape first.
- Match the real entrypoint if possible. For Linux CI-style unit failures, start with:
- Keep `OPENCLAW_TEST_MEMORY_TRACE=1` enabled so the wrapper prints per-file RSS summaries alongside the snapshots.
- If the report is about a specific shard or worker budget, preserve that shape.
2. Wait for repeated snapshots before concluding anything.
- Take at least two intervals from the same lane.
- Compare snapshots from the same PID inside one lane directory such as `.tmp/heapsnap/unit-fast/`.
- Use `scripts/heapsnapshot-delta.mjs` to compare either two files directly or the earliest/latest pair per PID in one lane directory.
3. Classify the growth before choosing a fix.
- If growth is dominated by Vite/Vitest transformed source strings, `Module`, `system / Context`, bytecode, descriptor arrays, or property maps, treat it as retained module graph growth in long-lived workers.
- If growth is dominated by app objects, caches, buffers, server handles, timers, mock state, sqlite state, or similar runtime objects, treat it as a likely cleanup or lifecycle leak.
4. Fix the right layer.
- For retained transformed-module growth in shared workers:
- Move hotspot files out of `unit-fast` by updating `test/fixtures/test-parallel.behavior.json`.
- Prefer `singletonIsolated` for files that are safe alone but inflate shared worker heaps.
- If the file should already have been peeled out by timings but is absent from `test/fixtures/test-timings.unit.json`, call that out explicitly. Missing timings are a scheduling blind spot.
- For real leaks:
- Patch the implicated test or runtime cleanup path.
- Look for missing `afterEach`/`afterAll`, module-reset gaps, retained global state, unreleased DB handles, or listeners/timers that survive the file.
5. Verify with the most direct proof.
- Re-run the targeted lane or file with heap snapshots enabled if the suite still finishes in reasonable time.
- If snapshot overhead pushes tests over Vitest timeouts, fall back to the same lane without snapshots and confirm the RSS trend or OOM is reduced.
- For wrapper-only changes, at minimum verify the expected lanes start and the snapshot files are written.
## Heuristics
- Do not call everything a leak. In this repo, large `unit-fast` growth can be a worker-lifetime problem rather than an application object leak.
-`scripts/test-parallel.mjs` and `scripts/test-parallel-memory.mjs` are the primary control points for wrapper diagnostics.
- The lane names printed by `[test-parallel] start ...` and `[test-parallel][mem] summary ...` tell you where to focus.
- When one or two files account for most of the delta and they are missing from timings, reducing impact by isolating them is usually the first pragmatic fix.
- When the same retained object families grow across multiple intervals in the same worker PID, trust the snapshots over intuition.
Read the top positive deltas first. Large positive growth in module-transform artifacts suggests lane isolation; large positive growth in runtime objects suggests a real leak.
## Output Expectations
When using this skill, report:
- The exact reproduce command.
- Which lane and PID were compared.
- The dominant retained object families from the snapshot delta.
- Whether the issue is a real leak or shared-worker retained module growth.
- The concrete fix or impact-reduction patch.
- What you verified, and what snapshot overhead prevented you from verifying.
description: Triage GitHub security advisories for OpenClaw with high-confidence close/keep decisions, exact tag and commit verification, trust-model checks, optional hardening notes, and a final reply ready to post and copy to clipboard.
---
# Security Triage
Use when reviewing OpenClaw security advisories, drafts, or GHSA reports.
Goal: high-confidence maintainers' triage without over-closing real issues or shipping unnecessary regressions.
## Close Bar
Close only if one of these is true:
- duplicate of an existing advisory or fixed issue
- invalid against shipped behavior
- out of scope under `SECURITY.md`
- fixed before any affected release/tag
Do not close only because `main` is fixed. If latest shipped tag or npm release is affected, keep it open until released or published with the right status.
## Required Reads
Before answering:
1. Read `SECURITY.md`.
2. Read the GHSA body with `gh api /repos/openclaw/openclaw/security-advisories/<GHSA>`.
3. Inspect the exact implicated code paths.
4. Verify shipped state:
-`git tag --sort=-creatordate | head`
-`npm view openclaw version --userconfig "$(mktemp)"`
-`git tag --contains <fix-commit>`
- if needed: `git show <tag>:path/to/file`
5. Search for canonical overlap:
- existing published GHSAs
- older fixed bugs
- same trust-model class already covered in `SECURITY.md`
## Review Method
For each advisory, decide:
-`close`
-`keep open`
-`keep open but narrow`
Check in this order:
1. Trust model
- Is the prerequisite already inside trusted host/local/plugin/operator state?
- Does `SECURITY.md` explicitly call this class out as out of scope or hardening-only?
2. Shipped behavior
- Is the bug present in the latest shipped tag or npm release?
- Was it fixed before release?
3. Exploit path
- Does the report show a real boundary bypass, not just prompt injection, local same-user control, or helper-level semantics?
4. Functional tradeoff
- If a hardening change would reduce intended user functionality, call that out before proposing it.
- Prefer fixes that preserve user workflows over deny-by-default regressions unless the boundary demands it.
## Response Format
When preparing a maintainer-ready close reply:
1. Print the GHSA URL first.
2. Then draft a detailed response the maintainer can post.
3. Include:
- exact reason for close
- exact code refs
- exact shipped tag / release facts
- exact fix commit or canonical duplicate GHSA when applicable
- optional hardening note only if worthwhile and functionality-preserving
Keep tone firm, specific, non-defensive.
## Clipboard Step
After drafting the final post body, copy it:
```bash
pbcopy <<'EOF'
<final response>
EOF
```
Tell the user that the clipboard now contains the proposed response.
## Useful Commands
```bash
gh api /repos/openclaw/openclaw/security-advisories/<GHSA>
gh api /repos/openclaw/openclaw/security-advisories --paginate
git tag --sort=-creatordate | head -n 20
npm view openclaw version --userconfig "$(mktemp)"
Thanks for filing this report. Keep it concise, reproducible, and evidence-based.
Thanks for filing this report. Keep every answer concise, reproducible, and grounded in observed evidence.
Do not speculate or infer beyond the evidence. If a narrative section cannot be answered from the available evidence, respond with exactly `NOT_ENOUGH_INFO`.
If this is a plugin beta-release blocker, rename the issue title to `Beta blocker: <plugin-name> - <summary>` and apply the `beta-blocker` label after filing.
- type:dropdown
id:bug_type
attributes:
@@ -19,39 +22,52 @@ body:
- Behavior bug (incorrect output/state without crash)
validations:
required:true
- type:dropdown
id:beta_blocker
attributes:
label:Beta release blocker
description:>
Choose `Yes` only if this blocks plugin compatibility during the current beta release window.
Selecting `Yes` does not apply the label automatically. You must also rename the issue title
to `Beta blocker: <plugin-name> - <summary>` for the automation to apply the `beta-blocker` label.
options:
- "No"
- "Yes"
validations:
required:true
- type:textarea
id:summary
attributes:
label:Summary
description:One-sentence statement of what is broken.
placeholder:After upgrading to <version>, <channel> behavior regressed from <prior version>.
description:One-sentence statement of what is broken, based only on observed evidence. If the evidence is insufficient, respond with exactly `NOT_ENOUGH_INFO`.
placeholder:After upgrading from 2026.2.10 to 2026.2.17, Telegram thread replies stopped posting; reproduced twice and confirmed by gateway logs.
validations:
required:true
- type:textarea
id:repro
attributes:
label:Steps to reproduce
description:Provide the shortest deterministic repro path.
description:Provide the shortest deterministic repro path supported by direct observation. If the repro path cannot be grounded from the evidence, respond with exactly `NOT_ENOUGH_INFO`.
placeholder:|
1. Configure channel X.
2. Send message Y.
3. Run command Z.
1. Start OpenClaw 2026.2.17 with the attached config.
2. Send a Telegram thread reply in the affected chat.
3. Observe no reply and confirm the attached `reply target not found` log line.
validations:
required:true
- type:textarea
id:expected
attributes:
label:Expected behavior
description:What should happen if the bug does not exist.
placeholder:Agent posts a reply in the same thread.
description:State the expected result using a concrete reference such as prior observed behavior, attached docs, or a known-good version. If no grounded reference exists, respond with exactly `NOT_ENOUGH_INFO`.
placeholder:In 2026.2.10, the agent posted replies in the same Telegram thread under the same workflow.
validations:
required:true
- type:textarea
id:actual
attributes:
label:Actual behavior
description:What happened instead, including user-visible errors.
placeholder:Noreply is posted; gateway logs "reply target not found".
description:Describe only the observed result, including user-visible errors and cited evidence. If the observed result cannot be grounded from the evidence, respond with exactly `NOT_ENOUGH_INFO`.
placeholder:Noreply is posted in the thread; the attached gateway log shows `reply target not found` at 14:23:08 UTC.
description:Include redacted logs/screenshots/recordings that prove the behavior.
description:Include the redacted logs, screenshots, recordings, docs, or version comparisons that support the grounded answers above.
render:shell
- type:textarea
id:impact
attributes:
label:Impact and severity
description:|
Explain who is affected, how severe it is, how often it happens, and the practical consequence.
Explain who is affected, how severe it is, how often it happens, and the practical consequence using only observed evidence.
If any part cannot be grounded from the evidence, respond with exactly `NOT_ENOUGH_INFO`.
Include:
- Affected users/systems/channels
- Severity (annoying, blocks workflow, data risk, etc.)
- Frequency (always/intermittent/edge case)
- Consequence (missed messages, failed onboarding, extra cost, etc.)
placeholder:|
Affected: Telegram group users on <version>
Severity: High (blocks replies)
Frequency: 100% repro
Consequence: Agents cannot respond in threads
Affected: Telegram group users on 2026.2.17
Severity: High (blocks thread replies)
Frequency: 4/4 observed attempts
Consequence: Agents do not respond in the affected threads
- type:textarea
id:additional_information
attributes:
label:Additional information
description:Add any context that helps triage but does not fit above. If this is a regression, include the last known good and first known bad versions.
placeholder:Last known good version <...>, first known bad version <...>, temporary workaround is ...
description:Add any remaining grounded context that helps triage but does not fit above. If this is a regression, include the last known good and first known bad versions when observed. If there is not enough evidence, respond with exactly `NOT_ENOUGH_INFO`.
placeholder:Last known good version 2026.2.10, first known bad version 2026.2.17, temporary workaround is sending a top-level message instead of a thread reply.
If this PR fixes a plugin beta-release blocker, title it `fix(<plugin-id>): beta blocker - <summary>` and link the matching `Beta blocker: <plugin-name> - <summary>` issue labeled `beta-blocker`. Contributors cannot label PRs, so the title is the PR-side signal for maintainers and automation.
- Problem:
- Why it matters:
- What changed:
@@ -11,7 +13,7 @@ Describe the problem and fix in 2–5 bullets:
- [ ] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Refactor required for the fix
- [ ] Docs
- [ ] Security hardening
- [ ] Chore/infra
@@ -31,12 +33,50 @@ Describe the problem and fix in 2–5 bullets:
- Closes #
- Related #
- [ ] This PR fixes a bug or regression
## Root Cause / Regression History (if applicable)
For bug fixes or regressions, explain why this happened, not just what changed. Otherwise write `N/A`. If the cause is unclear, write `Unknown`.
- Root cause:
- Missing detection / guardrail:
- Prior context (`git blame`, prior PR, issue, or refactor if known):
- Why this regressed now:
- If unknown, what was ruled out:
## Regression Test Plan (if applicable)
For bug fixes or regressions, name the smallest reliable test coverage that should have caught this. Otherwise write `N/A`.
- Coverage level that should have caught this:
- [ ] Unit test
- [ ] Seam / integration test
- [ ] End-to-end test
- [ ] Existing coverage already sufficient
- Target test or file:
- Scenario the test should lock in:
- Why this is the smallest reliable guardrail:
- Existing test that already covers this (if any):
- If no new test is added, why not:
## User-visible / Behavior Changes
List user-visible changes (including defaults/config).
If none, write `None`.
## Diagram (if applicable)
For UI changes or non-trivial logic flows, include a small ASCII diagram reviewers can scan quickly. Otherwise write `N/A`.
```text
Before:
[user action] -> [old state]
After:
[user action] -> [new state] -> [result]
```
## Security Impact (required)
- New permissions/capabilities? (`Yes/No`)
@@ -101,12 +141,6 @@ If a bot review conversation is addressed by this PR, resolve that conversation
- Migration needed? (`Yes/No`)
- If yes, exact upgrade steps:
## Failure Recovery (if this breaks)
- How to disable/revert this change quickly:
- Files/config to restore:
- Known bad symptoms reviewers should watch for:
## Risks and Mitigations
List only real risks for this PR. Add/remove entries as needed. If none, write `None`.
"Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.";
if (pullRequest) {
if (labelSet.has(dirtyLabel)) {
// `bad-barnacle` exempts PRs that Barnacle incorrectly marked dirty.
if (labelSet.has(dirtyLabel) && !labelSet.has(badBarnacleLabel)) {
echo "This workflow no longer builds, signs, notarizes, or uploads macOS assets."
echo
echo "Next step:"
echo "- Run \`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml\` with tag \`${RELEASE_TAG}\`."
echo "- Use \`preflight_only=true\` there for the full private mac preflight."
echo "- For the real publish path, the private run uploads the packaged \`.zip\`, \`.dmg\`, and \`.dSYM.zip\` files to the existing GitHub release in \`openclaw/openclaw\` automatically."
echo "- For stable releases, also download \`macos-appcast-${RELEASE_TAG}\` from the successful private run and commit \`appcast.xml\` back to \`main\` in \`openclaw/openclaw\`."
- In chat replies, file references must be repo-root relative only (example: `extensions/bluebubbles/src/channel.ts:80`); never absolute paths or `~/...`.
- GitHub issues/comments/PR comments: use literal multiline strings or `-F - <<'EOF'` (or $'...') for real newlines; never embed "\\n".
- GitHub comment footgun: never use `gh issue/pr comment -b "..."` when body contains backticks or shell chars. Always use single-quoted heredoc (`-F - <<'EOF'`) so no command substitution/escaping corruption.
- GitHub linking footgun: don’t wrap issue/PR refs like `#24643` in backticks when you want auto-linking. Use plain `#24643` (optionally add full URL).
- PR landing comments: always make commit SHAs clickable with full commit links (both landed SHA + source SHA when present).
- PR review conversations: if a bot leaves review conversations on your PR, address them and resolve those conversations yourself once fixed. Leave a conversation unresolved only when reviewer or maintainer judgment is still needed; do not leave bot-conversation cleanup to maintainers.
- GitHub searching footgun: don't limit yourself to the first 500 issues or PRs when wanting to search all. Unless you're supposed to look at the most recent, keep going until you've reached the last page in the search
- Security advisory analysis: before triage/severity decisions, read `SECURITY.md` to align with OpenClaw's trust model and design boundaries.
- In chat replies, file references must be repo-root relative only (example: `src/telegram/index.ts:80`); never absolute paths or `~/...`.
- Do not edit files covered by security-focused `CODEOWNERS` rules unless a listed owner explicitly asked for the change or is already reviewing it with you. Treat those paths as restricted surfaces, not drive-by cleanup.
## Auto-close labels (issues and PRs)
- If an issue/PR matches one of the reasons below, apply the label and let `.github/workflows/auto-response.yml` handle comment/close/lock.
- Do not manually close + manually comment for these reasons.
- Why: keeps wording consistent, preserves automation behavior (`state_reason`, locking), and keeps triage/reporting searchable by label.
-`r:*` labels can be used on both issues and PRs.
-`r: skill`: close with guidance to publish skills on Clawhub.
-`r: support`: close with redirect to Discord support + stuck FAQ.
-`r: no-ci-pr`: close test-fix-only PRs for failing `main` CI and post the standard explanation.
-`r: too-many-prs`: close when author exceeds active PR limit.
-`r: testflight`: close requests asking for TestFlight access/builds. OpenClaw does not provide TestFlight distribution yet, so use the standard response (“Not available, build from source.”) instead of ad-hoc replies.
-`r: third-party-extension`: close with guidance to ship as third-party plugin.
-`r: moltbook`: close + lock as off-topic (not affiliated).
-`r: spam`: close + lock as spam (`lock_reason: spam`).
-`invalid`: close invalid items (issues are closed as `not_planned`; PRs are closed).
-`dirty`: close PRs with too many unrelated/unexpected changes (PR-only label).
## PR truthfulness and bug-fix validation
- Never merge a bug-fix PR based only on issue text, PR text, or AI rationale.
- Before `/landpr`, run `/reviewpr` and require explicit evidence for bug-fix claims.
- Minimum merge gate for bug-fix PRs:
1. symptom evidence (repro/log/failing test),
2. verified root cause in code with file/line,
3. fix touches the implicated code path,
4. regression test (fail before/pass after) when feasible; if not feasible, include manual verification proof and why no test was added.
- If claim is unsubstantiated or likely hallucinated/BS: do not merge. Request evidence/changes, or close with `invalid` when appropriate.
- If linked issue appears wrong/outdated, correct triage first; do not merge speculative fixes.
## Project Structure & Module Organization
- Source code: `src/` (CLI wiring in `src/cli`, commands in `src/commands`, web provider in `src/provider-web.ts`, infra in `src/infra`, media pipeline in `src/media`).
- Tests: colocated `*.test.ts`.
- Docs: `docs/` (images, queue, Pi config). Built output lives in `dist/`.
-Plugins/extensions: live under `extensions/*` (workspace packages). Keep plugin-only deps in the extension `package.json`; do not add them to the root `package.json` unless core uses them.
-Nomenclature: use "plugin" / "plugins" in docs, UI, changelogs, and contributor guidance. The bundled workspace plugin tree remains the internal package layout to avoid repo-wide churn from a rename.
- Bundled plugin naming: for repo-owned workspace plugins, keep the canonical plugin id aligned across `openclaw.plugin.json:id`, the default workspace folder name, and package names anchored to the same id (`@openclaw/<id>` or approved suffix forms like `-provider`, `-plugin`, `-speech`, `-sandbox`, `-media-understanding`). Keep `openclaw.install.npmSpec` equal to the package name and `openclaw.channel.id` equal to the plugin id when present. Exceptions must be explicit and covered by the repo invariant test.
- Plugins: live in the bundled workspace plugin tree (workspace packages). Keep plugin-only deps in the extension `package.json`; do not add them to the root `package.json` unless core uses them.
- Plugins: install runs `npm install --omit=dev` in plugin dir; runtime deps must live in `dependencies`. Avoid `workspace:*` in `dependencies` (npm install breaks); put `openclaw` in `devDependencies` or `peerDependencies` instead (runtime resolves `openclaw/plugin-sdk` via jiti alias).
- Import boundaries: extension production code should treat `openclaw/plugin-sdk/*` plus local `api.ts` / `runtime-api.ts` barrels as the public surface. Do not import core `src/**`, `src/plugin-sdk-internal/**`, or another extension's `src/**` directly.
- Installers served from `https://openclaw.ai/*`: live in the sibling repo `../openclaw.ai` (`public/install.sh`, `public/install-cli.sh`, `public/install.ps1`).
- Rule: extensions must cross into core only through `openclaw/plugin-sdk/*`, manifest metadata, and documented runtime helpers. Do not import `src/**` from extension production code.
- Rule: core code and tests must not deep-import bundled plugin internals such as a plugin's `src/**` files or `onboard.js`. If core needs a bundled plugin helper, expose it through that plugin's `api.ts` and, when it is a real cross-package contract, through `src/plugin-sdk/<id>.ts`.
- Compatibility: new plugin seams are allowed, but they must be added as documented, backwards-compatible, versioned contracts. We have third-party plugins in the wild and do not break them casually.
- Channel boundary:
- Public docs: `docs/plugins/sdk-channel-plugins.md`, `docs/plugins/architecture.md`
- Rule: `src/channels/**` is core implementation. If plugin authors need a new seam, add it to the Plugin SDK instead of telling them to import channel internals.
- Provider/model boundary:
- Public docs: `docs/plugins/sdk-provider-plugins.md`, `docs/concepts/model-providers.md`, `docs/plugins/architecture.md`
- Rule: core owns the generic inference loop; provider plugins own provider-specific behavior through registration and typed hooks. Do not solve provider needs by reaching into unrelated core internals.
- Rule: avoid ad hoc reads of `plugins.entries.<id>.config` from unrelated core code. If core needs plugin-owned auth/config behavior, add or use a generic seam (`resolveSyntheticAuth`, public SDK/helper facades, manifest metadata, plugin auto-enable hooks) and honor plugin disablement plus SecretRef semantics.
- Rule: vendor-owned tools and settings belong in the owning plugin. Do not add provider-specific tool config, secret collection, or runtime enablement to core `tools.*` surfaces unless the tool is intentionally core-owned.
- Gateway protocol boundary:
- Public docs: `docs/gateway/protocol.md`, `docs/gateway/bridge-protocol.md`, `docs/concepts/architecture.md`
- Rule: keep manifest metadata, runtime registration, public SDK exports, and contract tests aligned. Do not create a hidden path around the declared plugin interfaces.
- Extension test boundary:
- Keep extension-owned onboarding/config/provider coverage under the owning bundled plugin package when feasible.
- If core tests need bundled plugin behavior, consume it through public `src/plugin-sdk/<id>.ts` facades or the plugin's `api.ts`, not private extension modules.
## Docs Linking (Mintlify)
@@ -63,7 +71,7 @@
- For docs, UI copy, and picker lists, order services/providers alphabetically unless the section is explicitly describing runtime behavior (for example auto-detection or execution order).
- Section cross-references: use anchors on root-relative paths (example: `[Hooks](/configuration#hooks)`).
- Doc headings and anchors: avoid em dashes and apostrophes in headings because they break Mintlify anchor links.
- When Peter asks for links, reply with full `https://docs.openclaw.ai/...` URLs (not root-relative).
- When the user asks for links, reply with full `https://docs.openclaw.ai/...` URLs (not root-relative).
- When you touch docs, end the reply with the `https://docs.openclaw.ai/...` URLs you referenced.
- README (GitHub): keep absolute docs URLs (`https://docs.openclaw.ai/...`) so links work on GitHub.
- Docs content must be generic: no personal device names/hostnames/paths; use placeholders like `user@gateway-host` and “gateway host”.
- If deps are missing (for example `node_modules` missing, `vitest not found`, or `command not found`), run the repo’s package-manager install command (prefer lockfile/README-defined PM), then rerun the exact requested command once. Apply this to test/build/lint/typecheck/dev commands; if retry still fails, report the command and first actionable error.
- Pre-commit hooks: `prek install` (runs same checks as CI)
- Pre-commit hooks: `prek install`. The hook runs the repo verification flow, including `pnpm check`.
-`FAST_COMMIT=1` skips the repo-wide `pnpm format` and `pnpm check` inside the pre-commit hook only. Use it when you intentionally want a faster commit path and are running equivalent targeted verification manually. It does not change CI and does not change what `pnpm check` itself does.
- Also supported: `bun install` (keep `pnpm-lock.yaml` + Bun patching in sync when touching deps/patches).
- Prefer Bun for TypeScript execution (scripts, dev, tests): `bun <file.ts>` / `bunx <tool>`.
- Run CLI in dev: `pnpm openclaw ...` (bun) or `pnpm dev`.
@@ -105,16 +114,49 @@
- Lint/format: `pnpm check`
- Format check: `pnpm format` (oxfmt --check)
- Format fix: `pnpm format:fix` (oxfmt --write)
- Terminology:
- "gate" means a verification command or command set that must be green for the decision you are making.
- A local dev gate is the fast default loop, usually `pnpm check` plus any scoped test you actually need.
- A landing gate is the broader bar before pushing `main`, usually `pnpm check`, `pnpm test`, and `pnpm build` when the touched surface can affect build output, packaging, lazy-loading/module boundaries, or published surfaces.
- A CI gate is whatever the relevant workflow enforces for that lane (for example `check`, `check-additional`, `build-smoke`, or release validation).
- Local dev gate: prefer `pnpm check` for the normal edit loop. It keeps the repo-architecture policy guards out of the default local loop.
- CI architecture gate: `check-additional` enforces architecture and boundary policy guards that are intentionally kept out of the default local loop.
- Formatting gate: the pre-commit hook runs `pnpm format` before `pnpm check`. If you want a formatting-only preflight locally, run `pnpm format` explicitly.
- If you need a fast commit loop, `FAST_COMMIT=1 git commit ...` skips the hook’s repo-wide `pnpm format` and `pnpm check`; use that only when you are deliberately covering the touched surface some other way.
- If you change config schema/help or the public Plugin SDK surface, update the matching baseline artifact and keep the two drift-check flows adjacent in scripts/workflows/docs guidance rather than inventing a third pattern.
- For narrowly scoped changes, prefer narrowly scoped tests that directly validate the touched behavior. If no meaningful scoped test exists, say so explicitly and use the next most direct validation available.
- Verification modes for work on `main`:
- Default mode: `main` is relatively stable. Count pre-commit hook coverage when it already verified the current tree, avoid rerunning the exact same checks just for ceremony, and prefer keeping CI/main green before landing.
- Fast-commit mode: `main` is moving fast and you intentionally optimize for shorter commit loops. Prefer explicit local verification close to the final landing point, and it is acceptable to use `--no-verify` for intermediate or catch-up commits after equivalent checks have already run locally.
- Preferred landing bar for pushes to `main`: in Default mode, favor `pnpm check` and `pnpm test` near the final rebase/push point when feasible. In fast-commit mode, verify the touched surface locally near landing without insisting every intermediate commit replay the full hook.
- Scoped tests prove the change itself. `pnpm test` remains the default `main` landing bar; scoped tests do not replace full-suite gates by default.
- Hard gate: if the change can affect build output, packaging, lazy-loading/module boundaries, or published surfaces, `pnpm build` MUST be run and MUST pass before pushing `main`.
- Default rule: do not land changes with failing format, lint, type, build, or required test checks when those failures are caused by the change or plausibly related to the touched surface. Fast-commit mode changes how verification is sequenced; it does not lower the requirement to validate and clean up the touched surface before final landing.
- For narrowly scoped changes, if unrelated failures already exist on latest `origin/main`, state that clearly, report the scoped tests you ran, and ask before broadening scope into unrelated fixes or landing despite those failures.
- Do not use scoped tests as permission to ignore plausibly related failures.
- Formatting/linting via Oxlint and Oxfmt; run `pnpm check` before commits.
- Never add `@ts-nocheck` and do not disable `no-explicit-any`; fix root causes and update Oxlint/Oxfmt config only when required.
- Formatting/linting via Oxlint and Oxfmt.
- Never add `@ts-nocheck` and do not add inline lint suppressions by default. Fix root causes first; only keep a suppression when the code is intentionally correct, the rule cannot express that safely, and the comment explains why.
- Do not disable `no-explicit-any`; prefer real types, `unknown`, or a narrow adapter/helper instead. Update Oxlint/Oxfmt config only when required.
- Prefer `zod` or existing schema helpers at external boundaries such as config, webhook payloads, CLI/JSON output, persisted JSON, and third-party API responses.
- Prefer discriminated unions when parameter shape changes runtime behavior.
- Prefer `Result<T, E>`-style outcomes and closed error-code unions for recoverable runtime decisions.
- Keep human-readable strings for logs, CLI output, and UI; do not use freeform strings as the source of truth for internal branching.
- Avoid `?? 0`, empty-string, empty-object, or magic-string sentinels when they can change runtime meaning silently.
- If introducing a new optional field or nullable semantic in core logic, prefer an explicit union or dedicated type when the value changes behavior.
- New runtime control-flow code should not branch on `error: string` or `reason: string` when a closed code union would be reasonable.
- Dynamic import guardrail: do not mix `await import("x")` and static `import ... from "x"` for the same module in production code paths. If you need lazy loading, create a dedicated `*.runtime.ts` boundary (that re-exports from `x`) and dynamically import that boundary from lazy callers only.
- Dynamic import verification: after refactors that touch lazy-loading/module boundaries, run `pnpm build` and check for `[INEFFECTIVE_DYNAMIC_IMPORT]` warnings before submitting.
- Extension SDK self-import guardrail: inside an extension package, do not import that same extension via `openclaw/plugin-sdk/<extension>` from production files. Route internal imports through a local barrel such as `./api.ts` or `./runtime-api.ts`, and keep the `plugin-sdk/<extension>` path as the external contract only.
- Extension package boundary guardrail: inside a bundled plugin package, do not use relative imports/exports that resolve outside that same package root. If shared code belongs in the plugin SDK, import `openclaw/plugin-sdk/<subpath>` instead of reaching into `src/plugin-sdk/**` or other repo paths via `../`.
- Extension API surface rule: `openclaw/plugin-sdk/<subpath>` is the only public cross-package contract for extension-facing SDK code. If an extension needs a new seam, add a public subpath first; do not reach into `src/plugin-sdk/**` by relative path.
- Never share class behavior via prototype mutation (`applyPrototypeMixins`, `Object.defineProperty` on `.prototype`, or exporting `Class.prototype` for merges). Use explicit inheritance/composition (`A extends B extends C`) or helper composition so TypeScript can typecheck.
- If this pattern is needed, stop and get explicit approval before shipping; default behavior is to split/refactor into an explicit class hierarchy and keep members strongly typed.
- In tests, prefer per-instance stubs over prototype mutation (`SomeClass.prototype.method = ...`) unless a test explicitly documents why prototype-level patching is required.
@@ -124,22 +166,26 @@
- Naming: use **OpenClaw** for product/app/docs headings; use `openclaw` for CLI command, package/binary, paths, and config keys.
- Written English: use American spelling and grammar in code, comments, docs, and UI strings (e.g. "color" not "colour", "behavior" not "behaviour", "analyze" not "analyse").
## Release Channels (Naming)
## Release / Advisory Workflows
-stable: tagged releases only (e.g. `vYYYY.M.D`), npm dist-tag `latest`.
-beta naming: prefer `-beta.N`; do not mint new `-1/-2` betas. Legacy `vYYYY.M.D-<patch>` and `vYYYY.M.D.beta.N` remain recognized.
- dev: moving head on `main` (no tag; git checkout main).
-Use `$openclaw-release-maintainer` at `.agents/skills/openclaw-release-maintainer/SKILL.md` for release naming, version coordination, release auth, and changelog-backed release-note workflows.
-Use `$openclaw-ghsa-maintainer` at `.agents/skills/openclaw-ghsa-maintainer/SKILL.md` for GHSA advisory inspection, patch/publish flow, private-fork checks, and GHSA API validation.
-Release and publish remain explicit-approval actions even when using the skill.
## Testing Guidelines
- Framework: Vitest with V8 coverage thresholds (70% lines/branches/functions/statements).
- Naming: match source names with `*.test.ts`; e2e in `*.e2e.test.ts`.
- When tests need example Anthropic/OpenAI model constants, prefer `sonnet-4.6` and `gpt-5.4`; update older Anthropic/GPT examples when you touch those tests.
- Run `pnpm test` (or `pnpm test:coverage`) before pushing when you touch logic.
- Write tests to clean up timers, env, globals, mocks, sockets, temp dirs, and module state so `--isolate=false` stays green.
- Agents MUST NOT modify baseline, inventory, ignore, snapshot, or expected-failure files to silence failing checks without explicit approval in this chat.
- For targeted/local debugging, keep using the wrapper: `pnpm test -- <path-or-filter> [vitest args...]` (for example `pnpm test -- src/commands/onboard-search.test.ts -t "shows registered plugin providers"`); do not default to raw `pnpm vitest run ...` because it bypasses wrapper config/profile/pool routing.
- Do not set test workers above 16; tried already.
-If local Vitest runs cause memory pressure (common on non-Mac-Studio hosts), use `OPENCLAW_TEST_PROFILE=low OPENCLAW_TEST_SERIAL_GATEWAY=1 pnpm test` for land/gate runs.
-Keep Vitest on `forks` only. Do not introduce or reintroduce any non-`forks` Vitest pool or alternate execution mode in configs, wrapper scripts, or default test commands without explicit approval in this chat. This includes `threads`, `vmThreads`, `vmForks`, and any future/nonstandard pool variant.
-If local Vitest runs cause memory pressure, the wrapper now derives budgets from host capabilities (CPU, memory band, current load). For a conservative explicit override during land/gate runs, use `OPENCLAW_TEST_PROFILE=serial OPENCLAW_TEST_SERIAL_GATEWAY=1 pnpm test`.
- Live tests (real keys): `OPENCLAW_LIVE_TEST=1 pnpm test:live` (OpenClaw-only) or `LIVE=1 pnpm test:live` (includes provider live tests). Docker: `pnpm test:docker:live-models`, `pnpm test:docker:live-gateway`. Onboarding Docker E2E: `pnpm test:docker:onboard`.
- Changelog placement: in the active version block, append new entries to the end of the target section (`### Changes` or `### Fixes`); do not insert new entries at the top of a section.
@@ -149,7 +195,9 @@
## Commit & Pull Request Guidelines
**Full maintainer PR workflow (optional):** If you want the repo's end-to-end maintainer workflow (triage order, quality bar, rebase rules, commit/changelog conventions, co-contributor policy, and the `review-pr` > `prepare-pr` > `merge-pr` pipeline), see `.agents/skills/PR_WORKFLOW.md`. Maintainers may use other workflows; when a maintainer specifies a workflow, follow that. If no workflow is specified, default to PR_WORKFLOW.
- Use `$openclaw-pr-maintainer` at `.agents/skills/openclaw-pr-maintainer/SKILL.md` for maintainer PR triage, review, close, search, and landing workflows.
- This includes auto-close labels, bug-fix evidence gates, GitHub comment/search footguns, and maintainer PR decision flow.
- For the repo's end-to-end maintainer PR workflow, use `$openclaw-pr-maintainer` at `.agents/skills/openclaw-pr-maintainer/SKILL.md`.
-`/landpr` lives in the global Codex prompts (`~/.codex/prompts/landpr.md`); when landing or merging any PR, always follow that `/landpr` process.
- Create commits with `scripts/committer "<msg>" <file...>`; avoid manual `git add`/`git commit` so staging stays scoped.
-`sync`: if working tree is dirty, commit all changes (pick a sensible Conventional Commit message), then `git pull --rebase`; if rebase conflicts and cannot resolve, stop; otherwise `git push`.
## Git Notes
- If `git branch -d/-D <branch>` is policy-blocked, delete the local ref directly: `git update-ref -d refs/heads/<branch>`.
- Agents MUST NOT create or push merge commits on `main`. If `main` has advanced, rebase local commits onto the latest `origin/main` before pushing.
- Bulk PR close/reopen safety: if a close action would affect more than 5 PRs, first ask for explicit user confirmation with the exact PR count and target scope/query.
## GitHub Search (`gh`)
- Prefer targeted keyword search before proposing new work or duplicating fixes.
- Use `--repo openclaw/openclaw` + `--match title,body` first; add `--match comments` when triaging follow-up threads.
- Web provider stores creds at `~/.openclaw/credentials/`; rerun `openclaw login` if logged out.
- Pi sessions live under `~/.openclaw/sessions/` by default; the base directory is not configurable.
- Environment variables: see `~/.profile`.
- Never commit or publish real phone numbers, videos, or live configuration values. Use obviously fake placeholders in docs, tests, and examples.
- Release flow: use the private [maintainer release docs](https://github.com/openclaw/maintainers/blob/main/release/README.md) for the actual runbook; use`docs/reference/RELEASING.md` for the public release policy.
- Release flow: use the private [maintainer release docs](https://github.com/openclaw/maintainers/blob/main/release/README.md) for the actual runbook,`docs/reference/RELEASING.md` for the public release policy, and `$openclaw-release-maintainer` for the maintainership workflow.
## GHSA (Repo Advisory) Patch/Publish
- Before reviewing security advisories, read `SECURITY.md`.
- Fetch: `gh api /repos/openclaw/openclaw/security-advisories/<GHSA>`
- Latest npm: `npm view openclaw version --userconfig "$(mktemp)"`
- Private fork PRs must be closed:
`fork=$(gh api /repos/openclaw/openclaw/security-advisories/<GHSA> | jq -r .private_fork.full_name)`
`gh pr list -R "$fork" --state open` (must be empty)
- Description newline footgun: write Markdown via heredoc to `/tmp/ghsa.desc.md` (no `"\\n"` strings)
- Rebrand/migration issues or legacy config/service warnings: run `openclaw doctor` (see `docs/gateway/doctor.md`).
## Agent-Specific Notes
## Local Runtime / Platform Notes
- Vocabulary: "makeup" = "mac app".
-Parallels macOS retests: use the snapshot most closely named like `macOS 26.3.1 fresh` when the user asks for a clean/fresh macOS rerun; avoid older Tahoe snapshots unless explicitly requested.
-Parallels beta smoke: use `--target-package-spec openclaw@<beta-version>` for the beta artifact, and pin the stable side with both `--install-version <stable-version>` and `--latest-version <stable-version>` for upgrade runs. npm dist-tags can move mid-run.
-Parallels beta smoke, Windows nuance: old stable `2026.3.12` still prints the Unicode Windows onboarding banner, so mojibake during the stable precheck log is expected there. Judge the beta package by the post-upgrade lane.
- Parallels macOS smoke playbook:
-`prlctl exec` is fine for deterministic repo commands, but it can misrepresent interactive shell behavior (`PATH`, `HOME`, `curl | bash`, shebang resolution). For installer parity or shell-sensitive repros, prefer the guest Terminal or `prlctl enter`.
- Fresh Tahoe snapshot current reality: `brew` exists, `node` may not be on `PATH` in noninteractive guest exec. Use absolute `/opt/homebrew/bin/node` for repo/CLI runs when needed.
- Preferred automation entrypoint: `pnpm test:parallels:macos`. It restores the snapshot most closely matching `macOS 26.3.1 fresh`, serves the current `main` tarball from the host, then runs fresh-install and latest-release-to-main smoke lanes.
- Discord roundtrip smoke is opt-in. Pass `--discord-token-env <VAR> --discord-guild-id <guild> --discord-channel-id <channel>`; the harness will configure Discord in-guest, post a guest message, verify host-side visibility via the Discord REST API, post a fresh host-side message back into the channel, then verify `openclaw message read` sees it in-guest.
- Keep the Discord token in a host env var only. For Peter’s Mac Studio bot, fetch it into a temp env var from `~/.openclaw/openclaw.json` over SSH instead of hardcoding it in repo files/shell history.
- For Discord smoke on this snapshot: use `openclaw message send/read` via the installed wrapper, not `node openclaw.mjs message ...`; lazy `message` subcommands do not resolve the same way through the direct module entrypoint.
- For Discord guild allowlists: set `channels.discord.guilds` as one JSON object. Do not use dotted `config set channels.discord.guilds.<snowflake>...` paths; numeric snowflakes get treated as array indexes.
- Avoid `prlctl enter` / expect for the Discord config phase; long lines get mangled. Use `prlctl exec --current-user /bin/sh -lc ...` with short commands or temp files.
- Gateway verification in smoke runs should use `openclaw gateway status --deep --require-rpc`, not plain `--deep`, so probe failures go non-zero.
- Latest-release pre-upgrade diagnostics still need compatibility fallback: stable `2026.3.12` does not know `--require-rpc`, so precheck status dumps should fall back to plain `gateway status --deep` until the guest is upgraded.
- Harness output: pass `--json` for machine-readable summary; per-phase logs land under `/tmp/openclaw-parallels-smoke.*`.
- All-OS parallel runs should share the host `dist` build via `/tmp/openclaw-parallels-build.lock` instead of rebuilding three times.
- Current expected outcome on latest stable pre-upgrade: `precheck=latest-ref-fail` is normal on `2026.3.12`; treat it as a baseline signal, not a regression, unless the post-upgrade `main` lane also fails.
- Fresh host-served tgz install: restore fresh snapshot, install tgz as guest root with `HOME=/var/root`, then run onboarding as the desktop user via `prlctl exec --current-user`.
- For `openclaw onboard --non-interactive --secret-input-mode ref --install-daemon`, expect env-backed auth-profile refs (for example `OPENAI_API_KEY`) to be copied into the service env at install time; this path was fixed and should stay green.
- Don’t run local + gateway agent turns in parallel on the same fresh workspace/session; they can collide on the session lock. Run sequentially.
- Root-installed tarball smoke on Tahoe can still log plugin blocks for world-writable `extensions/*` under `/opt/homebrew/lib/node_modules/openclaw`; treat that as separate from onboarding/gateway health unless the task is plugin loading.
- Parallels Windows smoke playbook:
- Preferred automation entrypoint: `pnpm test:parallels:windows`. It restores the snapshot most closely matching `pre-openclaw-native-e2e-2026-03-12`, serves the current `main` tarball from the host, then runs fresh-install and latest-release-to-main smoke lanes.
- Gateway verification in smoke runs should use `openclaw gateway status --deep --require-rpc`, not plain `--deep`, so probe failures go non-zero.
- Latest-release pre-upgrade diagnostics still need compatibility fallback: stable `2026.3.12` does not know `--require-rpc`, so precheck status dumps should fall back to plain `gateway status --deep` until the guest is upgraded.
- Always use `prlctl exec --current-user` for Windows guest runs; plain `prlctl exec` lands in `NT AUTHORITY\SYSTEM` and does not match the real desktop-user install path.
- Prefer explicit `npm.cmd` / `openclaw.cmd`. Bare `npm` / `openclaw` in PowerShell can hit the `.ps1` shim and fail under restrictive execution policy.
- Use PowerShell only as the transport (`powershell.exe -NoProfile -ExecutionPolicy Bypass`) and call the `.cmd` shims explicitly from inside it.
- Harness output: pass `--json` for machine-readable summary; per-phase logs land under `/tmp/openclaw-parallels-windows.*`.
- Current expected outcome on latest stable pre-upgrade: `precheck=latest-ref-fail` is normal on `2026.3.12`; treat it as a baseline signal, not a regression, unless the post-upgrade `main` lane also fails.
- Keep Windows onboarding/status text ASCII-clean in logs. Fancy punctuation in banners shows up as mojibake through the current guest PowerShell capture path.
- Parallels Linux smoke playbook:
- Preferred automation entrypoint: `pnpm test:parallels:linux`. It restores the snapshot most closely matching `fresh` on `Ubuntu 24.04.3 ARM64`, serves the current `main` tarball from the host, then runs fresh-install and latest-release-to-main smoke lanes.
- Use plain `prlctl exec` on this snapshot. `--current-user` is not the right transport there.
- Fresh snapshot reality: `curl` is missing and `apt-get update` can fail on clock skew. Bootstrap with `apt-get -o Acquire::Check-Date=false update` and install `curl ca-certificates` before testing installer paths.
- Fresh `main` tgz smoke on Linux still needs the latest-release installer first, because this snapshot has no Node/npm before bootstrap. The harness does stable bootstrap first, then overlays current `main`.
- This snapshot does not have a usable `systemd --user` session. Treat managed daemon install as unsupported here; use `--skip-health`, then verify with direct `openclaw gateway run --bind loopback --port 18789 --force`.
- Env-backed auth refs are still fine, but any direct shell launch (`openclaw gateway run`, `openclaw agent --local`, Linux `gateway status --deep` against that direct run) must inherit the referenced env vars in the same shell.
-`prlctl exec` reaps detached Linux child processes on this snapshot, so a background `openclaw gateway run` launched from automation is not a trustworthy smoke path. The harness verifies installer + `agent --local`; do direct gateway checks only from an interactive guest shell when needed.
- When you do run Linux gateway checks manually from an interactive guest shell, use `openclaw gateway status --deep --require-rpc` so an RPC miss is a hard failure.
- Prefer direct argv guest commands for fetch/install steps (`curl`, `npm install -g`, `openclaw ...`) over nested `bash -lc` quoting; Linux guest quoting through Parallels was the flaky part.
- Harness output: pass `--json` for machine-readable summary; per-phase logs land under `/tmp/openclaw-parallels-linux.*`.
- Current expected outcome on Linux smoke: fresh + upgrade should pass installer and `agent --local`; gateway remains `skipped-no-detached-linux-gateway` on this snapshot and should not be treated as a regression by itself.
-Rebrand/migration issues or legacy config/service warnings: run `openclaw doctor` (see `docs/gateway/doctor.md`).
-Use `$openclaw-parallels-smoke` at `.agents/skills/openclaw-parallels-smoke/SKILL.md` for Parallels smoke, rerun, upgrade, debug, and result-interpretation workflows across macOS, Windows, and Linux guests.
-For the macOS Discord roundtrip deep dive, use the narrower `.agents/skills/parallels-discord-roundtrip/SKILL.md` companion skill.
- Never edit `node_modules` (global/Homebrew/npm/git installs too). Updates overwrite. Skill notes go in `tools.md` or `AGENTS.md`.
- If you need local-only `.agents` ignores, use `.git/info/exclude` instead of repo `.gitignore`.
- When adding a new `AGENTS.md` anywhere in the repo, also add a `CLAUDE.md` symlink pointing to it (example: `ln -s AGENTS.md CLAUDE.md`).
- Signal: "update fly" => `fly ssh console -a flawd-bot -C "bash -lc 'cd /data/clawd/openclaw && git pull --rebase origin main'"` then `fly machines restart e825232f34d058 -a flawd-bot`.
- When working on a GitHub Issue or PR, print the full URL at the end of the task.
- When answering questions, respond with high-confidence answers only: verify in code; do not guess.
- Never update the Carbon dependency.
- Any dependency with `pnpm.patchedDependencies` must use an exact version (no `^`/`~`).
- Patching dependencies (pnpm patches, overrides, or vendored changes) requires explicit approval; do not do this by default.
- Gateway currently runs only as the menubar app; there is no separate LaunchAgent/helper label installed. Restart via the OpenClaw Mac app or `scripts/restart-mac.sh`; to verify/kill use `launchctl print gui/$UID | grep openclaw` rather than assuming a fixed label. **When debugging on macOS, start/stop the gateway via the app, not ad-hoc tmux sessions; kill any temporary tunnels before handoff.**
@@ -271,8 +244,23 @@
- iOS Team ID lookup: `security find-identity -p codesigning -v` → use Apple Development (…) TEAMID. Fallback: `defaults read com.apple.dt.Xcode IDEProvisioningTeamIdentifiers`.
- A2UI bundle hash: `src/canvas-host/a2ui/.bundle.hash` is auto-generated; ignore unexpected changes, and only regenerate via `pnpm canvas:a2ui:bundle` (or `scripts/bundle-a2ui.sh`) when needed. Commit the hash as a separate commit.
- Release signing/notary credentials are managed outside the repo; maintainers keep that setup in the private [maintainer release docs](https://github.com/openclaw/maintainers/tree/main/release).
- Lobster palette: use the shared CLI palette in `src/terminal/palette.ts` (no hardcoded colors); apply palette to onboarding/config prompts and other TTY UI output as needed.
- When asked to open a “session” file, open the Pi session logs under `~/.openclaw/agents/<agentId>/sessions/*.jsonl` (use the `agent=<id>` value in the Runtime line of the system prompt; newest unless a specific ID is given), not the default `sessions.json`. If logs are needed from another machine, SSH via Tailscale and read the same path there.
- Do not rebuild the macOS app over SSH; rebuilds must be run directly on the Mac.
- Voice wake forwarding tips:
- Command template should stay `openclaw-mac agent --message "${text}" --thinking low`; `VoiceWakeForwarder` already shell-escapes `${text}`. Don’t add extra quotes.
- launchd PATH is minimal; ensure the app’s launch agent PATH includes standard system paths plus your pnpm bin (typically `$HOME/Library/pnpm`) so `pnpm`/`openclaw` binaries resolve when invoked via `openclaw-mac`.
## Collaboration / Safety Notes
- When working on a GitHub Issue or PR, print the full URL at the end of the task.
- When answering questions, respond with high-confidence answers only: verify in code; do not guess.
- Never update the Carbon dependency.
- Any dependency with `pnpm.patchedDependencies` must use an exact version (no `^`/`~`).
- Patching dependencies (pnpm patches, overrides, or vendored changes) requires explicit approval; do not do this by default.
- **Multi-agent safety:** do **not** create/apply/drop `git stash` entries unless explicitly requested (this includes `git pull --rebase --autostash`). Assume other agents may be working; keep unrelated WIP untouched and avoid cross-cutting state changes.
- **Multi-agent safety:** when the user says "push", you may `git pull --rebase` to integrate latest changes (never discard other agents' work). When the user says "commit", scope to your changes only. When the user says "commit all", commit everything in grouped chunks.
- **Multi-agent safety:** prefer grouped `commit` / `pull --rebase` / `push` cycles for related work instead of many tiny syncs.
- **Multi-agent safety:** do **not** switch branches / check out a different branch unless explicitly requested.
- **Multi-agent safety:** running multiple agents is OK as long as each agent has its own session.
@@ -281,41 +269,12 @@
- If staged+unstaged diffs are formatting-only, auto-resolve without asking.
- If commit/push already requested, auto-stage and include formatting-only follow-ups in the same commit (or a tiny follow-up commit if needed), no extra confirmation.
- Only ask when changes are semantic (logic/data/behavior).
- Lobster palette: use the shared CLI palette in `src/terminal/palette.ts` (no hardcoded colors); apply palette to onboarding/config prompts and other TTY UI output as needed.
- **Multi-agent safety:** focus reports on your edits; avoid guard-rail disclaimers unless truly blocked; when multiple agents touch the same file, continue if safe; end with a brief “other files present” note only if relevant.
- Bug investigations: read source code of relevant npm dependencies and all related local code before concluding; aim for high-confidence root cause.
- Code style: add brief comments for tricky logic; keep files under ~500 LOC when feasible (split/refactor as needed).
- Tool schema guardrails (google-antigravity): avoid `Type.Union` in tool input schemas; no `anyOf`/`oneOf`/`allOf`. Use `stringEnum`/`optionalStringEnum` (Type.Unsafe enum) for string lists, and `Type.Optional(...)` instead of `... | null`. Keep top-level tool schema as `type: "object"` with `properties`.
- Tool schema guardrails: avoid raw `format` property names in tool schemas; some validators treat `format` as a reserved keyword and reject the schema.
- When asked to open a “session” file, open the Pi session logs under `~/.openclaw/agents/<agentId>/sessions/*.jsonl` (use the `agent=<id>` value in the Runtime line of the system prompt; newest unless a specific ID is given), not the default `sessions.json`. If logs are needed from another machine, SSH via Tailscale and read the same path there.
- Do not rebuild the macOS app over SSH; rebuilds must be run directly on the Mac.
- Never send streaming/partial replies to external messaging surfaces (WhatsApp, Telegram); only final replies should be delivered there. Streaming/tool events may still go to internal UIs/control channel.
- Voice wake forwarding tips:
- Command template should stay `openclaw-mac agent --message "${text}" --thinking low`; `VoiceWakeForwarder` already shell-escapes `${text}`. Don’t add extra quotes.
- launchd PATH is minimal; ensure the app’s launch agent PATH includes standard system paths plus your pnpm bin (typically `$HOME/Library/pnpm`) so `pnpm`/`openclaw` binaries resolve when invoked via `openclaw-mac`.
- For manual `openclaw message send` messages that include `!`, use the heredoc pattern noted below to avoid the Bash tool’s escaping.
- Release guardrails: do not change version numbers without operator’s explicit consent; always ask permission before running any npm publish/release step.
- Beta release guardrail: when using a beta Git tag (for example `vYYYY.M.D-beta.N`), publish npm with a matching beta version suffix (for example `YYYY.M.D-beta.N`) rather than a plain version on `--tag beta`; otherwise the plain version name gets consumed/blocked.
## Release Auth
- Core `openclaw` publish uses GitHub trusted publishing; do not use `NPM_TOKEN` or the plugin OTP flow for core releases.
- Separate `@openclaw/*` plugin publishes use a different maintainer-only auth flow.
- Maintainers: private 1Password item names, tmux rules, plugin publish helpers, and local mac signing/notary setup live in the private [maintainer release docs](https://github.com/openclaw/maintainers/blob/main/release/README.md).
## Changelog Release Notes
- When cutting a mac release with beta GitHub prerelease:
- Tag `vYYYY.M.D-beta.N` from the release commit (example: `v2026.2.15-beta.1`).
- Create prerelease with title `openclaw YYYY.M.D-beta.N`.
- Use release notes from `CHANGELOG.md` version section (`Changes` + `Fixes`, no title duplicate).
- Attach at least `OpenClaw-YYYY.M.D.zip` and `OpenClaw-YYYY.M.D.dSYM.zip`; include `.dmg` if available.
- Keep top version entries in `CHANGELOG.md` sorted by impact:
-`### Changes` first.
-`### Fixes` deduped and ranked with user-facing fixes first.
- Before tagging/publishing, run:
-`node --import tsx scripts/release-check.ts`
-`pnpm release:check`
-`pnpm test:install:smoke` or `OPENCLAW_INSTALL_SMOKE_SKIP_NONROOT=1 pnpm test:install:smoke` for non-root smoke path.
3.**Refactor-only PRs** → Don't open a PR. We are not accepting refactor-only changes unless a maintainer explicitly asks for them as part of a concrete fix.
4.**Test/CI-only PRs for known `main` failures** → Don't open a PR. The Maintainer team is already tracking those failures, and PRs that only tweak tests or CI to chase them will be closed unless they are required to validate a new fix.
-`pnpm test:extension --list` to see valid extension ids
- If you changed shared plugin or channel surfaces, run `pnpm test:contracts`
- For targeted shared-surface work, use `pnpm test:contracts:channels` or `pnpm test:contracts:plugins`
- These commands also cover the shared seam/smoke files that the default unit lane skips
- If you changed broader runtime behavior, still run the relevant wider lanes (`pnpm test:extensions`, `pnpm test:channels`, or `pnpm test`) before asking for review
- If you have access to Codex, run `codex review --base origin/main` locally before opening or updating your PR. Treat this as the current highest standard of AI review, even if GitHub Codex review also runs.
- Do not submit refactor-only PRs unless a maintainer explicitly requested that refactor for an active fix or deliverable.
- Do not submit test or CI-config fixes for failures already red on `main` CI. If a failure is already visible in the [main branch CI runs](https://github.com/openclaw/openclaw/actions), it's a known issue the Maintainer team is tracking, and a PR that only addresses those failures will be closed automatically. If you spot a _new_ regression not yet shown in main CI, report it as an issue first.
- Do not submit test-only PRs that just try to make known `main` CI failures pass. Test changes are acceptable when they are required to validate a new fix or cover new behavior in the same PR.
- Ensure CI checks pass
- Keep PRs focused (one thing per PR; do not mix unrelated concerns)
**OpenClaw** is a _personal AI assistant_ you run on your own devices.
It answers you on the channels you already use (WhatsApp, Telegram, Slack, Discord, Google Chat, Signal, iMessage, BlueBubbles, IRC, Microsoft Teams, Matrix, Feishu, LINE, Mattermost, Nextcloud Talk, Nostr, Synology Chat, Tlon, Twitch, Zalo, Zalo Personal, WebChat). It can speak and listen on macOS/iOS/Android, and can render a live Canvas you control. The Gateway is just the control plane — the product is the assistant.
It answers you on the channels you already use (WhatsApp, Telegram, Slack, Discord, Google Chat, Signal, iMessage, BlueBubbles, IRC, Microsoft Teams, Matrix, Feishu, LINE, Mattermost, Nextcloud Talk, Nostr, Synology Chat, Tlon, Twitch, Zalo, Zalo Personal, WeChat, WebChat). It can speak and listen on macOS/iOS/Android, and can render a live Canvas you control. The Gateway is just the control plane — the product is the assistant.
If you want a personal, single-user assistant that feels local, fast, and always-on, this is it.
@@ -49,7 +49,7 @@ Model note: while many providers/models are supported, for the best experience a
## Install (recommended)
Runtime: **Node ≥22**.
Runtime: **Node 24 (recommended) or Node 22.16+**.
```bash
npm install -g openclaw@latest
@@ -62,7 +62,7 @@ OpenClaw Onboard installs the Gateway daemon (launchd/systemd user service) so i
## Quick start (TL;DR)
Runtime: **Node ≥22**.
Runtime: **Node 24 (recommended) or Node 22.16+**.
Full beginner guide (auth, pairing, channels): [Getting started](https://docs.openclaw.ai/start/getting-started)
openclaw message send --to +1234567890 --message "Hello from OpenClaw"
# Talk to the assistant (optionally deliver back to any connected channel: WhatsApp/Telegram/Slack/Discord/Google Chat/Signal/iMessage/BlueBubbles/IRC/Microsoft Teams/Matrix/Feishu/LINE/Mattermost/Nextcloud Talk/Nostr/Synology Chat/Tlon/Twitch/Zalo/Zalo Personal/WebChat)
# Talk to the assistant (optionally deliver back to any connected channel: WhatsApp/Telegram/Slack/Discord/Google Chat/Signal/iMessage/BlueBubbles/IRC/Microsoft Teams/Matrix/Feishu/LINE/Mattermost/Nextcloud Talk/Nostr/Synology Chat/Tlon/Twitch/Zalo/Zalo Personal/WeChat/WebChat)
openclaw agent --message "Ship checklist" --thinking high
```
@@ -126,7 +126,7 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies.
## Highlights
- **[Local-first Gateway](https://docs.openclaw.ai/gateway)** — single control plane for sessions, channels, tools, and events.
- **[Voice Wake](https://docs.openclaw.ai/nodes/voicewake) + [Talk Mode](https://docs.openclaw.ai/nodes/talk)** — wake words on macOS/iOS and continuous voice on Android (ElevenLabs + system TTS fallback).
- **[Live Canvas](https://docs.openclaw.ai/platforms/mac/canvas)** — agent-driven visual workspace with [A2UI](https://docs.openclaw.ai/platforms/mac/canvas#canvas-a2ui).
@@ -150,7 +150,7 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies.
@@ -101,7 +101,7 @@ OpenClaw does **not** model one gateway as a multi-tenant, adversarial user boun
- If multiple users need OpenClaw, use one VPS (or host/OS user boundary) per user.
- For advanced setups, multiple gateways on one machine are possible, but only with strict isolation and are not the recommended default.
- Exec behavior is host-first by default: `agents.defaults.sandbox.mode` defaults to `off`.
-`tools.exec.host` defaults to `sandbox` as a routing preference, but if sandbox runtime is not active for the session, exec runs on the gateway host.
-`tools.exec.host` defaults to `auto`: sandbox when sandbox runtime is active for the session, otherwise gateway.
- Implicit exec calls (no explicit host in the tool call) follow the same behavior.
- This is expected in OpenClaw's one-user trusted-operator model. If you need isolation, enable sandbox mode (`non-main`/`all`) and keep strict tool policy.
<li>Providers/Qwen: remove the deprecated <code>qwen-portal-auth</code> OAuth integration for <code>portal.qwen.ai</code>; migrate to Model Studio with <code>openclaw onboard --auth-choice modelstudio-api-key</code>. (#52709) Thanks @pomelo-nwu.</li>
<li>Config/Doctor: drop automatic config migrations older than two months; very old legacy keys now fail validation instead of being rewritten on load or by <code>openclaw doctor</code>.</li>
</ul>
<h3>Changes</h3>
<ul>
<li>Android/chat settings: redesign the chat settings sheet with grouped device and media sections, refresh the Connect and Voice tabs, and tighten the chat composer/session header for a denser mobile layout. (#44894) Thanks @obviyus.</li>
<li>iOS/onboarding: add a first-run welcome pager before gateway setup, stop auto-opening the QR scanner, and show <code>/pair qr</code> instructions on the connect step. (#45054) Thanks @ngutman.</li>
<li>Browser/existing-session: add an official Chrome DevTools MCP attach mode for signed-in live Chrome sessions, with docs for <code>chrome://inspect/#remote-debugging</code> enablement and direct backlinks to Chrome’s own setup guides.</li>
<li>Browser/agents: add built-in <code>profile="user"</code> for the logged-in host browser and <code>profile="chrome-relay"</code> for the extension relay, so agent browser calls can prefer the real signed-in browser without the extra <code>browserSession</code> selector.</li>
<li>Browser/act automation: add batched actions, selector targeting, and delayed clicks for browser act requests with normalized batch dispatch. Thanks @vincentkoc.</li>
<li>Docker/timezone override: add <code>OPENCLAW_TZ</code> so <code>docker-setup.sh</code> can pin gateway and CLI containers to a chosen IANA timezone instead of inheriting the daemon default. (#34119) Thanks @Lanfei.</li>
<li>Dependencies/pi: bump <code>@mariozechner/pi-agent-core</code>, <code>@mariozechner/pi-ai</code>, <code>@mariozechner/pi-coding-agent</code>, and <code>@mariozechner/pi-tui</code> to <code>0.58.0</code>.</li>
<li>xAI/tools: move the bundled xAI provider to the Responses API, add first-class <code>x_search</code>, and auto-enable the xAI plugin from owned web-search and tool config so bundled Grok auth/configured search flows work without manual plugin toggles. (#56048) Thanks @huntharo.</li>
<li>xAI/onboarding: let the bundled Grok web-search plugin offer optional <code>x_search</code> setup during <code>openclaw onboard</code> and <code>openclaw configure --section web</code>, including an x_search model picker with the shared xAI key.</li>
<li>MiniMax: add image generation provider for <code>image-01</code> model, supporting generate and image-to-image editing with aspect ratio control. (#54487) Thanks @liyuan97.</li>
<li>Plugins/hooks: add async <code>requireApproval</code> to <code>before_tool_call</code> hooks, letting plugins pause tool execution and prompt the user for approval via the exec approval overlay, Telegram buttons, Discord interactions, or the <code>/approve</code> command on any channel. The <code>/approve</code> command now handles both exec and plugin approvals with automatic fallback. (#55339) Thanks @vaclavbelak and @joshavant.</li>
<li>ACP/channels: add current-conversation ACP binds for Discord, BlueBubbles, and iMessage so <code>/acp spawn codex --bind here</code> can turn the current chat into a Codex-backed workspace without creating a child thread, and document the distinction between chat surface, ACP session, and runtime workspace.</li>
<li>OpenAI/apply_patch: enable <code>apply_patch</code> by default for OpenAI and OpenAI Codex models, and align its sandbox policy access with <code>write</code> permissions.</li>
<li>Plugins/CLI backends: move bundled Claude CLI, Codex CLI, and Gemini CLI inference defaults onto the plugin surface, add bundled Gemini CLI backend support, and replace <code>gateway run --claude-cli-logs</code> with generic <code>--cli-backend-logs</code> while keeping the old flag as a compatibility alias.</li>
<li>Plugins/startup: auto-load bundled provider and CLI-backend plugins from explicit config refs, so bundled Claude CLI, Codex CLI, and Gemini CLI message-provider setups no longer need manual <code>plugins.allow</code> entries.</li>
<li>Podman: simplify the container setup around the current rootless user, install the launch helper under <code>~/.local/bin</code>, and document the host-CLI <code>openclaw --container <name> ...</code> workflow instead of a dedicated <code>openclaw</code> service user.</li>
<li>Slack/tool actions: add an explicit <code>upload-file</code> Slack action that routes file uploads through the existing Slack upload transport, with optional filename/title/comment overrides for channels and DMs.</li>
<li>Message actions/files: start unifying file-first sends on the canonical <code>upload-file</code> action by adding explicit support for Microsoft Teams and Google Chat, and by exposing BlueBubbles file sends through <code>upload-file</code> while keeping the legacy <code>sendAttachment</code> alias.</li>
<li>Plugins/Matrix TTS: send auto-TTS replies as native Matrix voice bubbles instead of generic audio attachments. (#37080) thanks @Matthew19990919.</li>
<li>CLI: add <code>openclaw config schema</code> to print the generated JSON schema for <code>openclaw.json</code>. (#54523) Thanks @kvokka.</li>
<li>Config/TTS: auto-migrate legacy speech config on normal reads and secret resolution, keep legacy diagnostics for Doctor, and remove regular-mode runtime fallback for old bundled <code>tts.<provider></code> API-key shapes.</li>
<li>Memory/plugins: move the pre-compaction memory flush plan behind the active memory plugin contract so <code>memory-core</code> owns flush prompts and target-path policy instead of hardcoded core logic.</li>
<li>MiniMax: trim model catalog to M2.7 only, removing legacy M2, M2.1, M2.5, and VL-01 models. (#54487) Thanks @liyuan97.</li>
<li>Plugins/runtime: expose <code>runHeartbeatOnce</code> in the plugin runtime <code>system</code> namespace so plugins can trigger a single heartbeat cycle with an explicit delivery target override (e.g. <code>heartbeat: { target: "last" }</code>). (#40299) Thanks @loveyana.</li>
<li>Agents/compaction: preserve the post-compaction AGENTS refresh on stale-usage preflight compaction for both immediate replies and queued followups. (#49479) Thanks @jared596.</li>
<li>Agents/compaction: surface safeguard-specific cancel reasons and relabel benign manual <code>/compact</code> no-op cases as skipped instead of failed. (#51072) Thanks @afurm.</li>
<li>Docs: add <code>pnpm docs:check-links:anchors</code> for Mintlify anchor validation while keeping <code>scripts/docs-link-audit.mjs</code> as the stable link-audit entrypoint. (#55912) Thanks @velvet-shark.</li>
<li>Tavily: mark outbound API requests with <code>X-Client-Source: openclaw</code> so Tavily can attribute OpenClaw-originated traffic. (#55335) Thanks @lakshyaag-tavily.</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>Dashboard/chat UI: stop reloading full chat history on every live tool result in dashboard v2 so tool-heavy runs no longer trigger UI freeze/re-render storms while the final event still refreshes persisted history. (#45541) Thanks @BunsDev.</li>
<li>Gateway/client requests: reject unanswered gateway RPC calls after a bounded timeout and clear their pending state, so stalled connections no longer leak hanging <code>GatewayClient.request()</code> promises indefinitely.</li>
<li>Build/plugin-sdk bundling: bundle plugin-sdk subpath entries in one shared build pass so published packages stop duplicating shared chunks and avoid the recent plugin-sdk memory blow-up. (#45426) Thanks @TarasShyn.</li>
<li>Ollama/reasoning visibility: stop promoting native <code>thinking</code> and <code>reasoning</code> fields into final assistant text so local reasoning models no longer leak internal thoughts in normal replies. (#45330) Thanks @xi7ang.</li>
<li>Android/onboarding QR scan: switch setup QR scanning to Google Code Scanner so onboarding uses a more reliable scanner instead of the legacy embedded ZXing flow. (#45021) Thanks @obviyus.</li>
<li>Browser/existing-session: harden driver validation and session lifecycle so transport errors trigger reconnects while tool-level errors preserve the session, and extract shared ARIA role sets to deduplicate Playwright and Chrome MCP snapshot paths. (#45682) Thanks @odysseus0.</li>
<li>Browser/existing-session: accept text-only <code>list_pages</code> and <code>new_page</code> responses from Chrome DevTools MCP so live-session tab discovery and new-tab open flows keep working when the server omits structured page metadata.</li>
<li>Control UI/insecure auth: preserve explicit shared token and password auth on plain-HTTP Control UI connects so LAN and reverse-proxy sessions no longer drop shared auth before the first WebSocket handshake. (#45088) Thanks @velvet-shark.</li>
<li>Gateway/session reset: preserve <code>lastAccountId</code> and <code>lastThreadId</code> across gateway session resets so replies keep routing back to the same account and thread after <code>/reset</code>. (#44773) Thanks @Lanfei.</li>
<li>macOS/onboarding: avoid self-restarting freshly bootstrapped launchd gateways and give new daemon installs longer to become healthy, so <code>openclaw onboard --install-daemon</code> no longer false-fails on slower Macs and fresh VM snapshots.</li>
<li>Gateway/status: add <code>openclaw gateway status --require-rpc</code> and clearer Linux non-interactive daemon-install failure reporting so automation can fail hard on probe misses instead of treating a printed RPC error as green.</li>
<li>macOS/exec approvals: respect per-agent exec approval settings in the gateway prompter, including allowlist fallback when the native prompt cannot be shown, so gateway-triggered <code>system.run</code> requests follow configured policy instead of always prompting or denying unexpectedly. (#13707) Thanks @sliekens.</li>
<li>Telegram/media downloads: thread the same direct or proxy transport policy into SSRF-guarded file fetches so inbound attachments keep working when Telegram falls back between env-proxy and direct networking. (#44639) Thanks @obviyus.</li>
<li>Telegram/inbound media IPv4 fallback: retry SSRF-guarded Telegram file downloads once with the same IPv4 fallback policy as Bot API calls so fresh installs on IPv6-broken hosts no longer fail to download inbound images.</li>
<li>Windows/gateway install: bound <code>schtasks</code> calls and fall back to the Startup-folder login item when task creation hangs, so native <code>openclaw gateway install</code> fails fast instead of wedging forever on broken Scheduled Task setups.</li>
<li>Windows/gateway stop: resolve Startup-folder fallback listeners from the installed <code>gateway.cmd</code> port, so <code>openclaw gateway stop</code> now actually kills fallback-launched gateway processes before restart.</li>
<li>Windows/gateway status: reuse the installed service command environment when reading runtime status, so startup-fallback gateways keep reporting the configured port and running state in <code>gateway status --json</code> instead of falling back to <code>gateway port unknown</code>.</li>
<li>Windows/gateway auth: stop attaching device identity on local loopback shared-token and password gateway calls, so native Windows agent replies no longer log stale <code>device signature expired</code> fallback noise before succeeding.</li>
<li>Discord/gateway startup: treat plain-text and transient <code>/gateway/bot</code> metadata fetch failures as transient startup errors so Discord gateway boot no longer crashes on unhandled rejections. (#44397) Thanks @jalehman.</li>
<li>Slack/probe: keep <code>auth.test()</code> bot and team metadata mapping stable while simplifying the probe result path. (#44775) Thanks @Cafexss.</li>
<li>Dashboard/chat UI: render oversized plain-text replies as normal paragraphs instead of capped gray code blocks, so long desktop chat responses stay readable without tab-switching refreshes.</li>
<li>Dashboard/chat UI: restore the <code>chat-new-messages</code> class on the New messages scroll pill so the button uses its existing compact styling instead of rendering as a full-screen SVG overlay. (#44856) Thanks @Astro-Han.</li>
<li>Gateway/Control UI: restore the operator-only device-auth bypass and classify browser connect failures so origin and device-identity problems no longer show up as auth errors in the Control UI and web chat. (#45512) thanks @sallyom.</li>
<li>macOS/voice wake: stop crashing wake-word command extraction when speech segment ranges come from a different transcript instance.</li>
<li>Discord/allowlists: honor raw <code>guild_id</code> when hydrated guild objects are missing so allowlisted channels and threads like <code>#maintainers</code> no longer get false-dropped before channel allowlist checks.</li>
<li>macOS/runtime locator: require Node >=22.16.0 during macOS runtime discovery so the app no longer accepts Node versions that the main runtime guard rejects later. Thanks @sumleo.</li>
<li>Agents/custom providers: preserve blank API keys for loopback OpenAI-compatible custom providers by clearing the synthetic Authorization header at runtime, while keeping explicit apiKey and oauth/token config from silently downgrading into fake bearer auth. (#45631) Thanks @xinhuagu.</li>
<li>Models/google-vertex Gemini flash-lite normalization: apply existing bare-ID preview normalization to <code>google-vertex</code> model refs and provider configs so <code>google-vertex/gemini-3.1-flash-lite</code> resolves as <code>gemini-3.1-flash-lite-preview</code>. (#42435) thanks @scoootscooob.</li>
<li>iMessage/remote attachments: reject unsafe remote attachment paths before spawning SCP, so sender-controlled filenames can no longer inject shell metacharacters into remote media staging. Thanks @lintsinghua.</li>
<li>Telegram/webhook auth: validate the Telegram webhook secret before reading or parsing request bodies, so unauthenticated requests are rejected immediately instead of consuming up to 1 MB first. Thanks @space08.</li>
<li>Security/device pairing: make bootstrap setup codes single-use so pending device pairing requests cannot be silently replayed and widened to admin before approval. Thanks @tdjackey.</li>
<li>Security/external content: strip zero-width and soft-hyphen marker-splitting characters during boundary sanitization so spoofed <code>EXTERNAL_UNTRUSTED_CONTENT</code> markers fall back to the existing hardening path instead of bypassing marker normalization.</li>
<li>Security/exec approvals: unwrap more <code>pnpm</code> runtime forms during approval binding, including <code>pnpm --reporter ... exec</code> and direct <code>pnpm node</code> file runs, with matching regression coverage and docs updates.</li>
<li>Security/exec approvals: fail closed for Perl <code>-M</code> and <code>-I</code> approval flows so preload and load-path module resolution stays outside approval-backed runtime execution unless the operator uses a broader explicit trust path.</li>
<li>Security/exec approvals: recognize PowerShell <code>-File</code> and <code>-f</code> wrapper forms during inline-command extraction so approval and command-analysis paths treat file-based PowerShell launches like the existing <code>-Command</code> variants.</li>
<li>Security/exec approvals: unwrap <code>env</code> dispatch wrappers inside shell-segment allowlist resolution on macOS so <code>env FOO=bar /path/to/bin</code> resolves against the effective executable instead of the wrapper token.</li>
<li>Security/exec approvals: treat backslash-newline as shell line continuation during macOS shell-chain parsing so line-continued <code>$(</code> substitutions fail closed instead of slipping past command-substitution checks.</li>
<li>Security/exec approvals: bind macOS skill auto-allow trust to both executable name and resolved path so same-basename binaries no longer inherit trust from unrelated skill bins.</li>
<li>Build/plugin-sdk bundling: bundle plugin-sdk subpath entries in one shared build pass so published packages stop duplicating shared chunks and avoid the recent plugin-sdk memory blow-up. (#45426) Thanks @TarasShyn.</li>
<li>Cron/isolated sessions: route nested cron-triggered embedded runner work onto the nested lane so isolated cron jobs no longer deadlock when compaction or other queued inner work runs. Thanks @vincentkoc.</li>
<li>Agents/OpenAI-compatible compat overrides: respect explicit user <code>models[].compat</code> opt-ins for non-native <code>openai-completions</code> endpoints so usage-in-streaming capability overrides no longer get forced off when the endpoint actually supports them. (#44432) Thanks @cheapestinference.</li>
<li>Agents/Azure OpenAI startup prompts: rephrase the built-in <code>/new</code>, <code>/reset</code>, and post-compaction startup instruction so Azure OpenAI deployments no longer hit HTTP 400 false positives from the content filter. (#43403) Thanks @xingsy97.</li>
<li>Agents/memory bootstrap: load only one root memory file, preferring <code>MEMORY.md</code> and using <code>memory.md</code> as a fallback, so case-insensitive Docker mounts no longer inject duplicate memory context. (#26054) Thanks @Lanfei.</li>
<li>Agents/compaction: compare post-compaction token sanity checks against full-session pre-compaction totals and skip the check when token estimation fails, so sessions with large bootstrap context keep real token counts instead of falling back to unknown. (#28347) thanks @efe-arv.</li>
<li>Agents/compaction: preserve safeguard compaction summary language continuity via default and configurable custom instructions so persona drift is reduced after auto-compaction. (#10456) Thanks @keepitmello.</li>
<li>Agents/tool warnings: distinguish gated core tools like <code>apply_patch</code> from plugin-only unknown entries in <code>tools.profile</code> warnings, so unavailable core tools now report current runtime/provider/model/config gating instead of suggesting a missing plugin.</li>
<li>Config/validation: accept documented <code>agents.list[].params</code> per-agent overrides in strict config validation so <code>openclaw config validate</code> no longer rejects runtime-supported <code>cacheRetention</code>, <code>temperature</code>, and <code>maxTokens</code> settings. (#41171) Thanks @atian8179.</li>
<li>Config/web fetch: restore runtime validation for documented <code>tools.web.fetch.readability</code> and <code>tools.web.fetch.firecrawl</code> settings so valid web fetch configs no longer fail with unrecognized-key errors. (#42583) Thanks @stim64045-spec.</li>
<li>Signal/config validation: add <code>channels.signal.groups</code> schema support so per-group <code>requireMention</code>, <code>tools</code>, and <code>toolsBySender</code> overrides no longer get rejected during config validation. (#27199) Thanks @unisone.</li>
<li>Config/discovery: accept <code>discovery.wideArea.domain</code> in strict config validation so unicast DNS-SD gateway configs no longer fail with an unrecognized-key error. (#35615) Thanks @ingyukoh.</li>
<li>Telegram/media errors: redact Telegram file URLs before building media fetch errors so failed inbound downloads do not leak bot tokens into logs. Thanks @space08.</li>
<li>Agents/Anthropic: recover unhandled provider stop reasons (e.g. <code>sensitive</code>) as structured assistant errors instead of crashing the agent run. (#56639)</li>
<li>Google/models: resolve Gemini 3.1 pro, flash, and flash-lite for all Google provider aliases by passing the actual runtime provider ID and adding a template-provider fallback; fix flash-lite prefix ordering. (#56567)</li>
<li>OpenAI Codex/image tools: register Codex for media understanding and route image prompts through Codex instructions so image analysis no longer fails on missing provider registration or missing <code>instructions</code>. (#54829) Thanks @neeravmakwana.</li>
<li>Agents/image tool: restore the generic image-runtime fallback when no provider-specific media-understanding provider is registered, so image analysis works again for providers like <code>openrouter</code> and <code>minimax-portal</code>. (#54858) Thanks @MonkeyLeeT.</li>
<li>WhatsApp: fix infinite echo loop in self-chat DM mode where the bot's own outbound replies were re-processed as new inbound user messages. (#54570) Thanks @joelnishanth</li>
<li>Telegram/splitting: replace proportional text estimate with verified HTML-length search so long messages split at word boundaries instead of mid-word; gracefully degrade when tag overhead exceeds the limit. (#56595)</li>
<li>Telegram/delivery: skip whitespace-only and hook-blanked text replies in bot delivery to prevent GrammyError 400 empty-text crashes. (#56620)</li>
<li>Telegram/send: validate <code>replyToMessageId</code> at all four API sinks with a shared normalizer that rejects non-numeric, NaN, and mixed-content strings. (#56587)</li>
<li>Mistral: normalize OpenAI-compatible request flags so official Mistral API runs no longer fail with remaining <code>422 status code (no body)</code> chat errors.</li>
<li>Control UI/config: keep sensitive raw config hidden by default, replace the blank blocked editor with an explicit reveal-to-edit state, and restore raw JSON editing without auto-exposing secrets. Fixes #55322.</li>
<li>CLI/zsh: defer <code>compdef</code> registration until <code>compinit</code> is available so zsh completion loads cleanly with plugin managers and manual setups. (#56555)</li>
<li>BlueBubbles/debounce: guard debounce flush against null message text by sanitizing at the enqueue boundary and adding an independent combiner guard. (#56573)</li>
<li>Auto-reply: suppress JSON-wrapped <code>{"action":"NO_REPLY"}</code> control envelopes before channel delivery with a strict single-key detector; preserves media when text is only a silent envelope. (#56612)</li>
<li>ACP/ACPX agent registry: align OpenClaw's ACPX built-in agent mirror with the latest <code>openclaw/acpx</code> command defaults and built-in aliases, pin versioned <code>npx</code> built-ins to exact versions, and stop unknown ACP agent ids from falling through to raw <code>--agent</code> command execution on the MCP-proxy path. (#28321) Thanks @m0nkmaster and @vincentkoc.</li>
<li>Security/audit: extend web search key audit to recognize Gemini, Grok/xAI, Kimi, Moonshot, and OpenRouter credentials via a boundary-safe bundled-web-search registry shim. (#56540)</li>
<li>Docs/FAQ: remove broken Xfinity SSL troubleshooting cross-links from English and zh-CN FAQ entries — both sections already contain the full workaround inline. (#56500)</li>
<li>Telegram: deliver verbose tool summaries inside forum topic sessions again, so threaded topic chats now match DM verbose behavior. (#43236) Thanks @frankbuild.</li>
<li>BlueBubbles/CLI agents: restore inbound prompt image refs for CLI routed turns, reapply embedded runner image size guardrails, and cover both CLI image transport paths with regression tests. (#51373)</li>
<li>BlueBubbles/groups: optionally enrich unnamed participant lists with local macOS Contacts names after group gating passes, so group member context can show names instead of only raw phone numbers.</li>
<li>Discord/reconnect: drain stale gateway sockets, clear cached resume state before forced fresh reconnects, and fail closed when old sockets refuse to die so Discord recovery stops looping on poisoned resume state. (#54697) Thanks @ngutman.</li>
<li>iMessage: stop leaking inline <code>[[reply_to:...]]</code> tags into delivered text by sending <code>reply_to</code> as RPC metadata and stripping stray directive tags from outbound messages. (#39512) Thanks @mvanhorn.</li>
<li>CLI/plugins: make routed commands use the same auto-enabled bundled-channel snapshot as gateway startup, so configured bundled channels like Slack load without requiring a prior config rewrite. (#54809) Thanks @neeravmakwana.</li>
<li>CLI/message send: write manual <code>openclaw message send</code> deliveries into the resolved agent session transcript again by always threading the default CLI agent through outbound mirroring. (#54187) Thanks @KevInTheCloud5617.</li>
<li>CLI/onboarding: show the Kimi Code API key option again in the Moonshot setup menu so the interactive picker includes all Kimi setup paths together. Fixes #54412 Thanks @sparkyrider</li>
<li>Agents/status: use provider-aware context window lookup for fresh Anthropic 4.6 model overrides so <code>/status</code> shows the correct 1.0m window instead of an underreported shared-cache minimum. (#54796) Thanks @neeravmakwana.</li>
<li>OpenAI/WebSocket: preserve reasoning replay metadata and tool-call item ids on WebSocket tool turns, and start a fresh response chain when full-context resend is required. (#53856) Thanks @xujingchen1996.</li>
<li>OpenAI/WS: restore reasoning blocks for Responses WebSocket runs and keep reasoning/tool-call replay metadata intact so resumed sessions do not lose or break follow-up reasoning-capable turns. (#53856) Thanks @xujingchen1996.</li>
<li>Agents/errors: surface provider quota/reset details when available, but keep HTML/Cloudflare rate-limit pages on the generic fallback so raw error pages are not shown to users. (#54512) Thanks @bugkill3r.</li>
<li>Claude CLI: switch the bundled Claude CLI backend to <code>stream-json</code> output so watchdogs see progress on long runs, and keep session/usage metadata even when Claude finishes with an empty result line. (#49698) Thanks @felear2022.</li>
<li>Claude CLI/MCP: always pass a strict generated <code>--mcp-config</code> overlay for background Claude CLI runs, including the empty-server case, so Claude does not inherit ambient user/global MCP servers. (#54961) Thanks @markojak.</li>
<li>Agents/embedded replies: surface mid-turn 429 and overload failures when embedded runs end without a user-visible reply, while preserving successful media-only replies that still use legacy <code>mediaUrl</code>. (#50930) Thanks @infichen.</li>
<li>Chat/UI: move the chat send button onto the shared ghost-button theme styling, while keeping the stop button icon readable on the danger state. (#55075) Thanks @bottenbenny.</li>
<li>WhatsApp/allowFrom: show a specific allowFrom policy error for valid blocked targets instead of the misleading <code><E.164|group JID></code> formathint. Thanks @mcaxtr.</li>
<li>Agents/cooldowns: scope rate-limit cooldowns per model so one 429 no longer blocks every model on the same auth profile, replace the exponential 1 min -> 1 h escalation with a stepped 30 s / 1 min / 5 min ladder, and surface a user-facing countdown message when all models are rate-limited. (#49834) Thanks @kiranvk-2011.</li>
<li>Agents/embedded transport errors: distinguish common network failures like connection refused, DNS lookup failure, and interrupted sockets from true timeouts in embedded-run user messaging and lifecycle diagnostics. (#51419) Thanks @scoootscooob.</li>
<li>Telegram/pairing: ignore self-authored DM <code>message</code> updates so bot-pinned status cards and similar service updates do not trigger bogus pairing requests or re-enter inbound dispatch. (#54530) thanks @huntharo</li>
<li>Mattermost/replies: keep pairing replies, slash-command fallback replies, and model-picker messages on the resolved config path so <code>exec:</code> SecretRef bot tokens work across all outbound reply branches. (#48347) thanks @mathiasnagler.</li>
<li>Microsoft Teams/config: accept the existing <code>welcomeCard</code>, <code>groupWelcomeCard</code>, <code>promptStarters</code>, and feedback/reflection keys in strict config validation so already-supported Teams runtime settings stop failing schema checks. (#54679) Thanks @gumclaw.</li>
<li>MCP/channels: add a Gateway-backed channel MCP bridge with Codex/Claude-facing conversation tools, Claude channel notifications, and safer stdio bridge lifecycle handling for reconnects and routed session discovery.</li>
<li>Plugins/SDK: thread <code>moduleUrl</code> through plugin-sdk alias resolution so user-installed plugins outside the openclaw directory correctly resolve <code>openclaw/plugin-sdk/*</code> subpath imports, and gate <code>plugin-sdk:check-exports</code> in <code>release:check</code>. (#54283) Thanks @xieyongliang.</li>
<li>Config/web fetch: allow the documented <code>tools.web.fetch.maxResponseBytes</code> setting in runtime schema validation so valid configs no longer fail with unrecognized-key errors. (#53401) Thanks @erhhung.</li>
<li>Message tool/buttons: keep the shared <code>buttons</code> schema optional in merged tool definitions so plain <code>action=send</code> calls stop failing validation when no buttons are provided. (#54418) Thanks @adzendo.</li>
<li>Agents/openai-compatible tool calls: deduplicate repeated tool call ids across live assistant messages and replayed history so OpenAI-compatible backends no longer reject duplicate <code>tool_call_id</code> values with HTTP 400. (#40996) Thanks @xaeon2026.</li>
<li>Models/openai-completions: default non-native OpenAI-compatible providers to omit tool-definition <code>strict</code> fields unless users explicitly opt back in, so tool calling keeps working on providers that reject that option. (#45497) Thanks @sahancava.</li>
<li>Plugins/context engines: retry strict legacy <code>assemble()</code> calls without the new <code>prompt</code> field when older engines reject it, preserving prompt-aware retrieval compatibility for pre-prompt plugins. (#50848) thanks @danhdoan.</li>
<li>CLI/update status: explicitly say <code>up to date</code> when the local version already matches npm latest, while keeping the availability logic unchanged. (#51409) Thanks @dongzhenye.</li>
<li>Daemon/Linux: stop flagging non-gateway systemd services as duplicate gateways just because their unit files mention OpenClaw, reducing false-positive doctor/log noise. (#45328) Thanks @gregretkowski.</li>
<li>Feishu: close WebSocket connections on monitor stop/abort so ghost connections no longer persist, preventing duplicate event processing and resource leaks across restart cycles. (#52844) Thanks @schumilin.</li>
<li>Feishu: use the original message <code>create_time</code> instead of <code>Date.now()</code> for inbound timestamps so offline-retried messages carry the correct authoring time, preventing mis-targeted agent actions on stale instructions. (#52809) Thanks @schumilin.</li>
<li>Control UI/Skills: open skill detail dialogs with the browser modal lifecycle so clicking a skill row keeps the panel centered instead of rendering it off-screen at the bottom of the page.</li>
<li>Matrix/replies: include quoted poll question/options in inbound reply context so the agent sees the original poll content when users reply to Matrix poll messages. (#55056) Thanks @alberthild.</li>
<li>Matrix/plugins: keep plugin bootstrap from crashing when built runtime mixes bare and deep <code>matrix-js-sdk</code> entrypoints, so unrelated channels do not get taken down during plugin load. (#56273) Thanks @aquaright1.</li>
<li>Agents/sandbox: honor <code>tools.sandbox.tools.alsoAllow</code>, let explicit sandbox re-allows remove matching built-in default-deny tools, and keep sandbox explain/error guidance aligned with the effective sandbox tool policy. (#54492) Thanks @ngutman.</li>
<li>Agents/sandbox: make blocked-tool guidance glob-aware again, redact/sanitize session-specific explain hints for safer copy-paste, and avoid leaking control-character session keys in those hints. (#54684) Thanks @ngutman.</li>
<li>Agents/compaction: trigger timeout recovery compaction before retrying high-context LLM timeouts so embedded runs stop repeating oversized requests. (#46417) thanks @joeykrug.</li>
<li>Agents/compaction: reconcile <code>sessions.json.compactionCount</code> after a late embedded auto-compaction success so persisted session counts catch up once the handler reports completion. (#45493) Thanks @jackal092927.</li>
<li>Agents/failover: classify Codex accountId token extraction failures as auth errors so model fallback continues to the next configured candidate. (#55206) Thanks @cosmicnet.</li>
<li>Plugins/runtime: reuse only compatible active plugin registries across tools, providers, web search, and channel bootstrap, align <code>/tools/invoke</code> plugin loading with the session workspace, and retry outbound channel recovery when the pinned channel surface changes so plugin tools and channels stop disappearing or re-registering from mismatched runtime loads. Thanks @gumadeiras.</li>
<li>Talk/macOS: stop direct system-voice failures from replaying system speech, use app-locale fallback for shared watchdog timing, and add regression coverage for the macOS fallback route and language-aware timeout policy. (#53511) thanks @hongsw.</li>
<li>Discord/gateway cleanup: keep late Carbon reconnect-exhausted errors suppressed through startup/dispose cleanup so Discord monitor shutdown no longer crashes on late gateway close events. (#55373) Thanks @Takhoffman.</li>
<li>Discord/gateway shutdown: treat expected reconnect-exhausted events during intentional lifecycle stop as clean shutdowns so startup-abort cleanup no longer surfaces false gateway failures. (#55324) Thanks @joelnishanth.</li>
<li>Discord/gateway shutdown: suppress reconnect-exhausted events that were already buffered before teardown flips <code>lifecycleStopping</code>, so stale-socket Discord restarts no longer crash the whole gateway. Fixes #55403 and #55421. Thanks @lml2468 and @vincentkoc.</li>
<li>GitHub Copilot/auth refresh: treat large <code>expires_at</code> values as seconds epochs and clamp far-future runtime auth refresh timers so Copilot token refresh cannot fall into a <code>setTimeout</code> overflow hot loop. (#55360) Thanks @michael-abdo.</li>
<li>Agents/status: use the persisted runtime session model in <code>session_status</code> when no explicit override exists, and honor per-agent <code>thinkingDefault</code> in both <code>session_status</code> and <code>/status</code>. (#55425) Thanks @scoootscooob, @xaeon2026, and @ysfbsf.</li>
<li>Heartbeat/runner: guarantee the interval timer is re-armed after heartbeat runs and unexpected runner errors so scheduled heartbeats do not silently stop after an interrupted cycle. (#52270) Thanks @MiloStack.</li>
<li>Config/Doctor: rewrite stale bundled plugin load paths from legacy bundled-plugin locations to the packaged bundled path, including directory-name mismatches and slash-suffixed config entries. (#55054) Thanks @SnowSky1.</li>
<li>WhatsApp/mentions: stop treating mentions embedded in quoted messages as direct mentions so replying to a message that @mentioned the bot no longer falsely triggers mention gating. (#52711) Thanks @lurebat.</li>
<li>Matrix: keep separate 2-person rooms out of DM routing after <code>m.direct</code> seeds successfully, while still honoring explicit <code>is_direct</code> state and startup fallback recovery. (#54890) thanks @private-peter</li>
<li>Agents/ollama fallback: surface non-2xx Ollama HTTP errors with a leading status code so HTTP 503 responses trigger model fallback again. (#55214) Thanks @bugkill3r.</li>
<li>Feishu/tools: stop synthetic agent ids like <code>agent-spawner</code> from being treated as Feishu account ids during tool execution, so tools fall back to the configured/default Feishu account unless the contextual id is a real enabled Feishu account. (#55627) Thanks @MonkeyLeeT.</li>
<li>Google/tools: strip empty <code>required: []</code> arrays from Gemini tool schemas so optional-only tool parameters no longer trigger Google validator 400s. (#52106) Thanks @oliviareid-svg.</li>
<li>Onboarding/TUI/local gateways: show the resolved gateway port in setup output, clarify no-daemon local health/dashboard messaging, and preserve loopback Control UI auth on reruns and explicit local gateway URLs so local quickstart flows recover cleanly. (#55730) Thanks @shakkernerd.</li>
<li>TUI/chat log: keep system messages as single logical entries and prune overflow at whole-message boundaries so wrapped system spacing stays intact. (#55732) Thanks @shakkernerd.</li>
<li>TUI/activation: validate <code>/activation</code> arguments in the TUI and reject invalid values instead of silently coercing them to <code>mention</code>. (#55733) Thanks @shakkernerd.</li>
<li>Agents/model switching: apply <code>/model</code> changes to active embedded runs at the next safe retry boundary, so overloaded or retrying turns switch to the newly selected model instead of staying pinned to the old provider.</li>
<li>Agents/Codex fallback: classify Codex <code>server_error</code> payloads as failoverable, sanitize <code>Codex error:</code> payloads before they reach chat, preserve context-overflow guidance for prefixed <code>invalid_request_error</code> payloads, and omit provider <code>request_id</code> values from user-facing UI copy. (#42892) Thanks @xaeon2026.</li>
<li>Memory/search: share memory embedding provider registrations across split plugin runtimes so memory search no longer fails with unknown provider errors after memory-core registers built-in adapters. (#55945) Thanks @glitch418x.</li>
<li>Discord/Carbon beta: update <code>@buape/carbon</code> to the latest beta and pass the new <code>RateLimitError</code> request argument so Discord stays compatible with the upstream beta constructor change. (#55980) Thanks @ngutman.</li>
<li>Plugins/inbound claims: pass full inbound attachment arrays through <code>inbound_claim</code> hook metadata while keeping the legacy singular media attachment fields for compatibility. (#55452) Thanks @huntharo.</li>
<li>Plugins/Matrix: preserve sender filenames for inbound media by forwarding <code>originalFilename</code> to <code>saveMediaBuffer</code>. (#55692) thanks @esrehmki.</li>
<li>Matrix/mentions: recognize <code>matrix.to</code> mentions whose visible label uses the bot's room display name, so <code>requireMention: true</code> rooms respond correctly in modern Matrix clients. (#55393) thanks @nickludlam.</li>
<li>Ollama/thinking off: route <code>thinkingLevel=off</code> through the live Ollama extension request path so thinking-capable Ollama models now receive top-level <code>think: false</code> instead of silently generating hidden reasoning tokens. (#53200) Thanks @BruceMacD.</li>
<li>Plugins/diffs: stage bundled <code>@pierre/diffs</code> runtime dependencies during packaged updates so the bundled diff viewer keeps loading after global installs and updates. (#56077) Thanks @gumadeiras.</li>
<li>Plugins/diffs: load bundled Pierre themes without JSON module imports so diff rendering keeps working on newer Node builds. (#45869) thanks @NickHood1984.</li>
<li>Plugins/uninstall: remove owned <code>channels.<id></code> config when uninstalling channel plugins, and keep the uninstall preview aligned with explicit channel ownership so built-in channels and shared keys stay intact. (#35915) Thanks @wbxl2000.</li>
<li>Plugins/Matrix: prefer explicit DM signals when choosing outbound direct rooms and routing unmapped verification summaries, so strict 2-person fallback rooms do not outrank the real DM. (#56076) thanks @gumadeiras</li>
<li>Plugins/Matrix: resolve env-backed <code>accessToken</code> and <code>password</code> SecretRefs against the active Matrix config env path during startup, and officially accept SecretRef <code>accessToken</code> config values. (#54980) thanks @kakahu2015.</li>
<li>Microsoft Teams/proactive DMs: prefer the freshest personal conversation reference for <code>user:<aadObjectId></code> sends when multiple stored references exist, so replies stop targeting stale DM threads. (#54702) Thanks @gumclaw.</li>
<li>Gateway/plugins: reuse the session workspace when building HTTP <code>/tools/invoke</code> tool lists and harden tool construction to infer the session agent workspace by default, so workspace plugins do not re-register on repeated HTTP tool calls. (#56101) thanks @neeravmakwana</li>
<li>Brave/web search: normalize unsupported Brave <code>country</code> filters to <code>ALL</code> before request and cache-key generation so locale-derived values like <code>VN</code> stop failing with upstream 422 validation errors. (#55695) Thanks @chen-zhang-cs-code.</li>
<li>Discord/replies: preserve leading indentation when stripping inline reply tags so reply-tagged plain text and fenced code blocks keep their formatting. (#55960) Thanks @Nanako0129.</li>
<li>Daemon/status: surface immediate gateway close reasons from lightweight probes and prefer those concrete auth or pairing failures over generic timeouts in <code>openclaw daemon status</code>. (#56282) Thanks @mbelinky.</li>
<li>Agents/failover: classify HTTP 410 errors as retryable timeouts by default while still preserving explicit session-expired, billing, and auth signals from the payload. (#55201) thanks @nikus-pan.</li>
<li>Agents/subagents: restore completion announce delivery for extension channels like BlueBubbles. (#56348)</li>
<li>Plugins/Matrix: load bundled <code>@matrix-org/matrix-sdk-crypto-nodejs</code> through <code>createRequire(...)</code> so E2EE media send and receive keep the package-local native binding lookup working in packaged ESM builds. (#54566) thanks @joelnishanth.</li>
<li>Plugins/Matrix: encrypt E2EE image thumbnails with <code>thumbnail_file</code> while keeping unencrypted-room previews on <code>thumbnail_url</code>, so encrypted Matrix image events keep thumbnail metadata without leaking plaintext previews. (#54711) thanks @frischeDaten.</li>
<li>Telegram/forum topics: keep native <code>/new</code> and <code>/reset</code> routed to the active topic by preserving the topic target on forum-thread command context. (#35963)</li>
</ul>
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
<li>Control UI/dashboard-v2: refresh the gateway dashboard with modular overview, chat, config, agent, and session views, plus a command palette, mobile bottom tabs, and richer chat tools like slash commands, search, export, and pinned messages. (#41503) Thanks @BunsDev.</li>
<li>OpenAI/GPT-5.4 fast mode: add configurable session-level fast toggles across <code>/fast</code>, TUI, Control UI, and ACP, with per-model config defaults and OpenAI/Codex request shaping.</li>
<li>Anthropic/Claude fast mode: map the shared <code>/fast</code> toggle and <code>params.fastMode</code> to direct Anthropic API-key <code>service_tier</code> requests, with live verification for both Anthropic and OpenAI fast-mode tiers.</li>
<li>Models/plugins: move Ollama, vLLM, and SGLang onto the provider-plugin architecture, with provider-owned onboarding, discovery, model-picker setup, and post-selection hooks so core provider wiring is more modular.</li>
<li>Docs/Kubernetes: Add a starter K8s install path with raw manifests, Kind setup, and deployment docs. Thanks @sallyom @dzianisv @egkristi</li>
<li>Agents/subagents: add <code>sessions_yield</code> so orchestrators can end the current turn immediately, skip queued tool work, and carry a hidden follow-up payload into the next session turn. (#36537) thanks @jriff</li>
<li>Slack/agent replies: support <code>channelData.slack.blocks</code> in the shared reply delivery path so agents can send Block Kit messages through standard Slack outbound delivery. (#44592) Thanks @vincentkoc.</li>
<li>Gateway/OpenAI compatibility: add <code>/v1/models</code> and <code>/v1/embeddings</code>, and forward explicit model overrides through <code>/v1/chat/completions</code> and <code>/v1/responses</code> for broader client and RAG compatibility. Thanks @vincentkoc.</li>
<li>Agents/tools: make <code>/tools</code> show the tools the current agent can actually use right now, add a compact default view with an optional detailed mode, and add a live "Available Right Now" section in the Control UI so it is easier to see what will work before you ask.</li>
<li>Microsoft Teams: migrate to the official Teams SDK and add AI-agent UX best practices including streaming 1:1 replies, welcome cards with prompt starters, feedback/reflection, informative status updates, typing indicators, and native AI labeling. (#51808)</li>
<li>Microsoft Teams: add message edit and delete support for sent messages, including in-thread fallbacks when no explicit target is provided. (#49925)</li>
<li>Skills/install metadata: add one-click install recipes to bundled skills (coding-agent, gh-issues, openai-whisper-api, session-logs, tmux, trello, weather) so the CLI and Control UI can offer dependency installation when requirements are missing. (#53411) Thanks @BunsDev.</li>
<li>Control UI/skills: add status-filter tabs (All / Ready / Needs Setup / Disabled) with counts, replace inline skill cards with a click-to-detail dialog showing requirements, toggle switch, install action, API key entry, source metadata, and homepage link. (#53411) Thanks @BunsDev.</li>
<li>Slack/interactive replies: restore rich reply parity for direct deliveries, auto-render simple trailing <code>Options:</code> lines as buttons/selects, improve Slack interactive setup defaults, and isolate reply controls from plugin interactive handlers. (#53389) Thanks @vincentkoc.</li>
<li>CLI/containers: add <code>--container</code> and <code>OPENCLAW_CONTAINER</code> to run <code>openclaw</code> commands inside a running Docker or Podman OpenClaw container. (#52651) Thanks @sallyom.</li>
<li>Discord/auto threads: add optional <code>autoThreadName: "generated"</code> naming so new auto-created threads can be renamed asynchronously with concise LLM-generated titles while keeping the existing message-based naming as the default. (#43366) Thanks @davidguttman.</li>
<li>Plugins/hooks: add <code>before_dispatch</code> with canonical inbound metadata and route handled replies through the normal final-delivery path, preserving TTS and routed delivery semantics. (#50444) Thanks @gfzhx.</li>
<li>Control UI/agents: convert agent workspace file rows to expandable <code><details></code> with lazy-loaded inline markdown preview, and add comprehensive <code>.sidebar-markdown</code> styles for headings, lists, code blocks, tables, blockquotes, and details/summary elements. (#53411) Thanks @BunsDev.</li>
<li>Control UI/markdown preview: restyle the agent workspace file preview dialog with a frosted backdrop, sized panel, and styled header, and integrate <code>@create-markdown/preview</code> v2 system theme for rich markdown rendering (headings, tables, code blocks, callouts, blockquotes) that auto-adapts to the app's light/dark design tokens. (#53411) Thanks @BunsDev.</li>
<li>macOS app/config: replace horizontal pill-based subsection navigation with a collapsible tree sidebar using disclosure chevrons and indented subsection rows. (#53411) Thanks @BunsDev.</li>
<li>CLI/skills: soften missing-requirements label from "missing" to "needs setup" and surface API key setup guidance (where to get a key, CLI save command, storage path) in <code>openclaw skills info</code> output. (#53411) Thanks @BunsDev.</li>
<li>macOS app/skills: add "Get your key" homepage link and storage-path hint to the API key editor dialog, and show the config path in save confirmation messages. (#53411) Thanks @BunsDev.</li>
<li>Control UI/agents: add a "Not set" placeholder to the default agent model selector dropdown. (#53411) Thanks @BunsDev.</li>
<li>Runtime/install: lower the supported Node 22 floor to <code>22.14+</code> while continuing to recommend Node 24, so npm installs and self-updates do not strand Node 22.14 users on older releases.</li>
<li>CLI/update: preflight the target npm package <code>engines.node</code> before <code>openclaw update</code> runs a global package install, so outdated Node runtimes fail with a clear upgrade message instead of attempting an unsupported latest release.</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>Security/device pairing: switch <code>/pair</code> and <code>openclaw qr</code> setup codes to short-lived bootstrap tokens so the next release no longer embeds shared gateway credentials in chat or QR pairing payloads. Thanks @lintsinghua.</li>
<li>Security/plugins: disable implicit workspace plugin auto-load so cloned repositories cannot execute workspace plugin code without an explicit trust decision. (<code>GHSA-99qw-6mr3-36qr</code>)(#44174) Thanks @lintsinghua and @vincentkoc.</li>
<li>Models/Kimi Coding: send <code>anthropic-messages</code> tools in native Anthropic format again so <code>kimi-coding</code> stops degrading tool calls into XML/plain-text pseudo invocations instead of real <code>tool_use</code> blocks. (#38669, #39907, #40552) Thanks @opriz.</li>
<li>TUI/chat log: reuse the active assistant message component for the same streaming run so <code>openclaw tui</code> no longer renders duplicate assistant replies. (#35364) Thanks @lisitan.</li>
<li>Telegram/model picker: make inline model button selections persist the chosen session model correctly, clear overrides when selecting the configured default, and include effective fallback models in <code>/models</code> button validation. (#40105) Thanks @avirweb.</li>
<li>Cron/proactive delivery: keep isolated direct cron sends out of the write-ahead resend queue so transient-send retries do not replay duplicate proactive messages after restart. (#40646) Thanks @openperf and @vincentkoc.</li>
<li>Models/Kimi Coding: send the built-in <code>User-Agent: claude-code/0.1.0</code> header by default for <code>kimi-coding</code> while still allowing explicit provider headers to override it, so Kimi Code subscription auth can work without a local header-injection proxy. (#30099) Thanks @Amineelfarssi and @vincentkoc.</li>
<li>Models/OpenAI Codex Spark: keep <code>gpt-5.3-codex-spark</code> working on the <code>openai-codex/*</code> path via resolver fallbacks and clearer Codex-only handling, while continuing to suppress the stale direct <code>openai/*</code> Spark row that OpenAI rejects live.</li>
<li>Ollama/Kimi Cloud: apply the Moonshot Kimi payload compatibility wrapper to Ollama-hosted Kimi models like <code>kimi-k2.5:cloud</code>, so tool routing no longer breaks when thinking is enabled. (#41519) Thanks @vincentkoc.</li>
<li>Moonshot CN API: respect explicit <code>baseUrl</code> (api.moonshot.cn) in implicit provider resolution so platform.moonshot.cn API keys authenticate correctly instead of returning HTTP 401. (#33637) Thanks @chengzhichao-xydt.</li>
<li>Kimi Coding/provider config: respect explicit <code>models.providers["kimi-coding"].baseUrl</code> when resolving the implicit provider so custom Kimi Coding endpoints no longer get overwritten by the built-in default. (#36353) Thanks @2233admin.</li>
<li>Gateway/main-session routing: keep TUI and other <code>mode:UI</code> main-session sends on the internal surface when <code>deliver</code> is enabled, so replies no longer inherit the session's persisted Telegram/WhatsApp route. (#43918) Thanks @obviyus.</li>
<li>BlueBubbles/self-chat echo dedupe: drop reflected duplicate webhook copies only when a matching <code>fromMe</code> event was just seen for the same chat, body, and timestamp, preventing self-chat loops without broad webhook suppression. Related to #32166. (#38442) Thanks @vincentkoc.</li>
<li>iMessage/self-chat echo dedupe: drop reflected duplicate copies only when a matching <code>is_from_me</code> event was just seen for the same chat, text, and <code>created_at</code>, preventing self-chat loops without broad text-only suppression. Related to #32166. (#38440) Thanks @vincentkoc.</li>
<li>Subagents/completion announce retries: raise the default announce timeout to 90 seconds and stop retrying gateway-timeout failures for externally delivered completion announces, preventing duplicate user-facing completion messages after slow gateway responses. Fixes #41235. Thanks @vasujain00 and @vincentkoc.</li>
<li>Mattermost/block streaming: fix duplicate message delivery (one threaded, one top-level) when block streaming is active by excluding <code>replyToId</code> from the block reply dedup key and adding an explicit <code>threading</code> dock to the Mattermost plugin. (#41362) Thanks @mathiasnagler and @vincentkoc.</li>
<li>Mattermost/reply media delivery: pass agent-scoped <code>mediaLocalRoots</code> through shared reply delivery so allowed local files upload correctly from button, slash-command, and model-picker replies. (#44021) Thanks @LyleLiu666.</li>
<li>macOS/Reminders: add the missing <code>NSRemindersUsageDescription</code> to the bundled app so <code>apple-reminders</code> can trigger the system permission prompt from OpenClaw.app. (#8559) Thanks @dinakars777.</li>
<li>Gateway/session discovery: discover disk-only and retired ACP session stores under custom templated <code>session.store</code> roots so ACP reconciliation, session-id/session-label targeting, and run-id fallback keep working after restart. (#44176) thanks @gumadeiras.</li>
<li>Plugins/env-scoped roots: fix plugin discovery/load caches and provenance tracking so same-process <code>HOME</code>/<code>OPENCLAW_HOME</code> changes no longer reuse stale plugin state or misreport <code>~/...</code> plugins as untracked. (#44046) thanks @gumadeiras.</li>
<li>Models/OpenRouter native ids: canonicalize native OpenRouter model keys across config writes, runtime lookups, fallback management, and <code>models list --plain</code>, and migrate legacy duplicated <code>openrouter/openrouter/...</code> config entries forward on write.</li>
<li>Windows/native update: make package installs use the npm update path instead of the git path, carry portable Git into native Windows updates, and mirror the installer's Windows npm env so <code>openclaw update</code> no longer dies early on missing <code>git</code> or <code>node-llama-cpp</code> download setup.</li>
<li>Sandbox/write: preserve pinned mutation-helper payload stdin so sandboxed <code>write</code> no longer reports success while creating empty files. (#43876) Thanks @glitch418x.</li>
<li>Security/exec approvals: escape invisible Unicode format characters in approval prompts so zero-width command text renders as visible <code>\u{...}</code> escapes instead of spoofing the reviewed command. (<code>GHSA-pcqg-f7rg-xfvv</code>)(#43687) Thanks @EkiXu and @vincentkoc.</li>
<li>Hooks/loader: fail closed when workspace hook paths cannot be resolved with <code>realpath</code>, so unreadable or broken internal hook paths are skipped instead of falling back to unresolved imports. (#44437) Thanks @vincentkoc.</li>
<li>Hooks/agent deliveries: dedupe repeated hook requests by optional idempotency key so webhook retries can reuse the first run instead of launching duplicate agent executions. (#44438) Thanks @vincentkoc.</li>
<li>Security/exec detection: normalize compatibility Unicode and strip invisible formatting code points before obfuscation checks so zero-width and fullwidth command tricks no longer suppress heuristic detection. (<code>GHSA-9r3v-37xh-2cf6</code>)(#44091) Thanks @wooluo and @vincentkoc.</li>
<li>Security/exec allowlist: preserve POSIX case sensitivity and keep <code>?</code> within a single path segment so exact-looking allowlist patterns no longer overmatch executables across case or directory boundaries. (<code>GHSA-f8r2-vg7x-gh8m</code>)(#43798) Thanks @zpbrent and @vincentkoc.</li>
<li>Security/commands: require sender ownership for <code>/config</code> and <code>/debug</code> so authorized non-owner senders can no longer reach owner-only config and runtime debug surfaces. (<code>GHSA-r7vr-gr74-94p8</code>)(#44305) Thanks @tdjackey and @vincentkoc.</li>
<li>Security/gateway auth: clear unbound client-declared scopes on shared-token WebSocket connects so device-less shared-token operators cannot self-declare elevated scopes. (<code>GHSA-rqpp-rjj8-7wv8</code>)(#44306) Thanks @LUOYEcode and @vincentkoc.</li>
<li>Security/browser.request: block persistent browser profile create/delete routes from write-scoped <code>browser.request</code> so callers can no longer persist admin-only browser profile changes through the browser control surface. (<code>GHSA-vmhq-cqm9-6p7q</code>)(#43800) Thanks @tdjackey and @vincentkoc.</li>
<li>Security/agent: reject public spawned-run lineage fields and keep workspace inheritance on the internal spawned-session path so external <code>agent</code> callers can no longer override the gateway workspace boundary. (<code>GHSA-2rqg-gjgv-84jm</code>)(#43801) Thanks @tdjackey and @vincentkoc.</li>
<li>Security/session_status: enforce sandbox session-tree visibility and shared agent-to-agent access guards before reading or mutating target session state, so sandboxed subagents can no longer inspect parent session metadata or write parent model overrides via <code>session_status</code>. (<code>GHSA-wcxr-59v9-rxr8</code>)(#43754) Thanks @tdjackey and @vincentkoc.</li>
<li>Security/agent tools: mark <code>nodes</code> as explicitly owner-only and document/test that <code>canvas</code> remains a shared trusted-operator surface unless a real boundary bypass exists.</li>
<li>Security/exec approvals: fail closed for Ruby approval flows that use <code>-r</code>, <code>--require</code>, or <code>-I</code> so approval-backed commands no longer bind only the main script while extra local code-loading flags remain outside the reviewed file snapshot.</li>
<li>Security/device pairing: cap issued and verified device-token scopes to each paired device's approved scope baseline so stale or overbroad tokens cannot exceed approved access. (<code>GHSA-2pwv-x786-56f8</code>)(#43686) Thanks @tdjackey and @vincentkoc.</li>
<li>Docs/onboarding: align the legacy wizard reference and <code>openclaw onboard</code> command docs with the Ollama onboarding flow so all onboarding reference paths now document <code>--auth-choice ollama</code>, Cloud + Local mode, and non-interactive usage. (#43473) Thanks @BruceMacD.</li>
<li>Models/secrets: enforce source-managed SecretRef markers in generated <code>models.json</code> so runtime-resolved provider secrets are not persisted when runtime projection is skipped. (#43759) Thanks @joshavant.</li>
<li>Security/WebSocket preauth: shorten unauthenticated handshake retention and reject oversized pre-auth frames before application-layer parsing to reduce pre-pairing exposure on unsupported public deployments. (<code>GHSA-jv4g-m82p-2j93</code>)(#44089) (<code>GHSA-xwx2-ppv2-wx98</code>)(#44089) Thanks @ez-lbz and @vincentkoc.</li>
<li>Security/proxy attachments: restore the shared media-store size cap for persisted browser proxy files so oversized payloads are rejected instead of overriding the intended 5 MB limit. (<code>GHSA-6rph-mmhp-h7h9</code>)(#43684) Thanks @tdjackey and @vincentkoc.</li>
<li>Security/host env: block inherited <code>GIT_EXEC_PATH</code> from sanitized host exec environments so Git helper resolution cannot be steered by host environment state. (<code>GHSA-jf5v-pqgw-gm5m</code>)(#43685) Thanks @zpbrent and @vincentkoc.</li>
<li>Security/Feishu webhook: require <code>encryptKey</code> alongside <code>verificationToken</code> in webhook mode so unsigned forged events are rejected instead of being processed with token-only configuration. (<code>GHSA-g353-mgv3-8pcj</code>)(#44087) Thanks @lintsinghua and @vincentkoc.</li>
<li>Security/Feishu reactions: preserve looked-up group chat typing and fail closed on ambiguous reaction context so group authorization and mention gating cannot be bypassed through synthetic <code>p2p</code> reactions. (<code>GHSA-m69h-jm2f-2pv8</code>)(#44088) Thanks @zpbrent and @vincentkoc.</li>
<li>Security/LINE webhook: require signatures for empty-event POST probes too so unsigned requests no longer confirm webhook reachability with a <code>200</code> response. (<code>GHSA-mhxh-9pjm-w7q5</code>)(#44090) Thanks @TerminalsandCoffee and @vincentkoc.</li>
<li>Security/Zalo webhook: rate limit invalid secret guesses before auth so weak webhook secrets cannot be brute-forced through unauthenticated churned requests without pre-auth <code>429</code> responses. (<code>GHSA-5m9r-p9g7-679c</code>)(#44173) Thanks @zpbrent and @vincentkoc.</li>
<li>Security/Zalouser groups: require stable group IDs for allowlist auth by default and gate mutable group-name matching behind <code>channels.zalouser.dangerouslyAllowNameMatching</code>. Thanks @zpbrent.</li>
<li>Security/Slack and Teams routing: require stable channel and team IDs for allowlist routing by default, with mutable name matching only via each channel's <code>dangerouslyAllowNameMatching</code> break-glass flag.</li>
<li>Security/exec approvals: fail closed for ambiguous inline loader and shell-payload script execution, bind the real script after POSIX shell value-taking flags, and unwrap <code>pnpm</code>/<code>npm exec</code>/<code>npx</code> script runners before approval binding. (<code>GHSA-57jw-9722-6rf2</code>)(<code>GHSA-jvqh-rfmh-jh27</code>)(<code>GHSA-x7pp-23xv-mmr4</code>)(<code>GHSA-jc5j-vg4r-j5jx</code>)(#44247) Thanks @tdjackey and @vincentkoc.</li>
<li>Doctor/gateway service audit: canonicalize service entrypoint paths before comparing them so symlink-vs-realpath installs no longer trigger false "entrypoint does not match the current install" repair prompts. (#43882) Thanks @ngutman.</li>
<li>Doctor/gateway service audit: earlier groundwork for this fix landed in the superseded #28338 branch. Thanks @realriphub.</li>
<li>Gateway/session stores: regenerate the Swift push-test protocol models and align Windows native session-store realpath handling so protocol checks and sync session discovery stop drifting on Windows. (#44266) thanks @jalehman.</li>
<li>Context engine/session routing: forward optional <code>sessionKey</code> through context-engine lifecycle calls so plugins can see structured routing metadata during bootstrap, assembly, post-turn ingestion, and compaction. (#44157) thanks @jalehman.</li>
<li>Agents/failover: classify z.ai <code>network_error</code> stop reasons as retryable timeouts so provider connectivity failures trigger fallback instead of surfacing raw unhandled-stop-reason errors. (#43884) Thanks @hougangdev.</li>
<li>Memory/session sync: add mode-aware post-compaction session reindexing with <code>agents.defaults.compaction.postIndexSync</code> plus <code>agents.defaults.memorySearch.sync.sessions.postCompactionForce</code>, so compacted session memory can refresh immediately without forcing every deployment into synchronous reindexing. (#25561) thanks @rodrigouroz.</li>
<li>Telegram/model picker: make inline model button selections persist the chosen session model correctly, clear overrides when selecting the configured default, and include effective fallback models in <code>/models</code> button validation. (#40105) Thanks @avirweb.</li>
<li>Telegram/native command sync: suppress expected <code>BOT_COMMANDS_TOO_MUCH</code> retry error noise, add a final fallback summary log, and document the difference between command-menu overflow and real Telegram network failures.</li>
<li>Mattermost/reply media delivery: pass agent-scoped <code>mediaLocalRoots</code> through shared reply delivery so allowed local files upload correctly from button, slash-command, and model-picker replies. (#44021) Thanks @LyleLiu666.</li>
<li>Plugins/env-scoped roots: fix plugin discovery/load caches and provenance tracking so same-process <code>HOME</code>/<code>OPENCLAW_HOME</code> changes no longer reuse stale plugin state or misreport <code>~/...</code> plugins as untracked. (#44046) thanks @gumadeiras.</li>
<li>Gateway/session discovery: discover disk-only and retired ACP session stores under custom templated <code>session.store</code> roots so ACP reconciliation, session-id/session-label targeting, and run-id fallback keep working after restart. (#44176) thanks @gumadeiras.</li>
<li>Models/OpenRouter native ids: canonicalize native OpenRouter model keys across config writes, runtime lookups, fallback management, and <code>models list --plain</code>, and migrate legacy duplicated <code>openrouter/openrouter/...</code> config entries forward on write.</li>
<li>Gateway/hooks: bucket hook auth failures by forwarded client IP behind trusted proxies and warn when <code>hooks.allowedAgentIds</code> leaves hook routing unrestricted.</li>
<li>Agents/compaction: skip the post-compaction <code>cache-ttl</code> marker write when a compaction completed in the same attempt, preventing the next turn from immediately triggering a second tiny compaction. (#28548) thanks @MoerAI.</li>
<li>Native chat/macOS: add <code>/new</code>, <code>/reset</code>, and <code>/clear</code> reset triggers, keep shared main-session aliases aligned, and ignore stale model-selection completions so native chat state stays in sync across reset and fast model changes. (#10898) Thanks @Nachx639.</li>
<li>Agents/compaction safeguard: route missing-model and missing-API-key cancellation warnings through the shared subsystem logger so they land in structured and file logs. (#9974) Thanks @dinakars777.</li>
<li>Cron/doctor: stop flagging canonical <code>agentTurn</code> and <code>systemEvent</code> payload kinds as legacy cron storage, while still normalizing whitespace-padded and non-canonical variants. (#44012) Thanks @shuicici.</li>
<li>ACP/client final-message delivery: preserve terminal assistant text snapshots before resolving <code>end_turn</code>, so ACP clients no longer drop the last visible reply when the gateway sends the final message body on the terminal chat event. (#17615) Thanks @pjeby.</li>
<li>Telegram/Discord status reactions: show a temporary compacting reaction during auto-compaction pauses and restore thinking afterward so the bot no longer appears frozen while context is being compacted. (#35474) thanks @Cypherm.</li>
<li>Outbound media/local files: align outbound media access with the configured fs policy so host-local files and inbound-media paths keep sending when <code>workspaceOnly</code> is off, while strict workspace-only agents remain sandboxed.</li>
<li>Security/sandbox media dispatch: close the <code>mediaUrl</code>/<code>fileUrl</code> alias bypass so outbound tool and message actions cannot escape media-root restrictions. (#54034)</li>
<li>Gateway/restart sentinel: wake the interrupted agent session via heartbeat after restart instead of only sending a best-effort restart note, retry outbound delivery once on transient failure, and preserve explicit thread/topic routing through the wake path so replies land in the correct Telegram topic or Slack thread. (#53940) Thanks @VACInc.</li>
<li>Docker/setup: avoid the pre-start <code>openclaw-cli</code> shared-network namespace loop by routing setup-time onboard/config writes through <code>openclaw-gateway</code>, so fresh Docker installs stop failing before the gateway comes up. (#53385) Thanks @amsminn.</li>
<li>Gateway/channels: keep channel startup sequential while isolating per-channel boot failures, so one broken channel no longer blocks later channels from starting. (#54215) Thanks @JonathanJing.</li>
<li>Embedded runs/secrets: stop unresolved <code>SecretRef</code> config from crashing embedded agent runs by falling back to the resolved runtime snapshot when needed. Fixes #45838.</li>
<li>WhatsApp/groups: track recent gateway-sent message IDs and suppress only matching group echoes, preserving owner <code>/status</code>, <code>/new</code>, and <code>/activation</code> commands from linked-account <code>fromMe</code> traffic. (#53624) Thanks @w-sss.</li>
<li>WhatsApp/reply-to-bot detection: restore implicit group reply detection by unwrapping <code>botInvokeMessage</code> payloads and reading <code>selfLid</code> from <code>creds.json</code>, so reply-based mentions reach the bot again in linked-account group chats.</li>
<li>Telegram/forum topics: recover <code>#General</code> topic <code>1</code> routing when Telegram omits forum metadata, including native commands, interactive callbacks, inbound message context, and fallback error replies. (#53699) thanks @huntharo</li>
<li>Discord/gateway supervision: centralize gateway error handling behind a lifetime-owned supervisor so early, active, and late-teardown Carbon gateway errors stay classified consistently and stop surfacing as process-killing teardown crashes.</li>
<li>Discord/timeouts: send a visible timeout reply when the inbound Discord worker times out before a final reply starts, including created auto-thread targets and queued-run ordering. (#53823) Thanks @Kimbo7870.</li>
<li>ACP/direct chats: always deliver a terminal ACP result when final TTS does not yield audio, even if block text already streamed earlier, and skip redundant empty-text final synthesis. (#53692) Thanks @w-sss.</li>
<li>Telegram/outbound errors: preserve actionable 403 membership/block/kick details and treat <code>bot not a member</code> as a permanent delivery failure so Telegram sends stop retrying doomed chats. (#53635) Thanks @w-sss.</li>
<li>Telegram/photos: preflight Telegram photo dimension and aspect-ratio rules, and fall back to document sends when image metadata is invalid or unavailable so photo uploads stop failing with <code>PHOTO_INVALID_DIMENSIONS</code>. (#52545) Thanks @hnshah.</li>
<li>Slack/runtime defaults: trim Slack DM reply overhead, restore Codex auto transport, and tighten Slack/web-search runtime defaults around DM preview threading, cache scoping, warning dedupe, and explicit web-search opt-in. (#53957) Thanks @vincentkoc.</li>
</ul>
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
<li>CLI/backup: add <code>openclaw backup create</code> and <code>openclaw backup verify</code> for local state archives, including <code>--only-config</code>, <code>--no-include-workspace</code>, manifest/payload validation, and backup guidance in destructive flows. (#40163) thanks @shichangs.</li>
<li>macOS/onboarding: add a remote gateway token field for remote mode, preserve existing non-plaintext <code>gateway.remote.token</code> config values until explicitly replaced, and warn when the loaded token shape cannot be used directly from the macOS app. (#40187, supersedes #34614) Thanks @cgdusek.</li>
<li>Talk mode: add top-level <code>talk.silenceTimeoutMs</code> config so Talk waits a configurable amount of silence before auto-sending the current transcript, while keeping each platform's existing default pause window when unset. (#39607) Thanks @danodoesdesign. Fixes #17147.</li>
<li>TUI: infer the active agent from the current workspace when launched inside a configured agent workspace, while preserving explicit <code>agent:</code> session targets. (#39591) thanks @arceus77-7.</li>
<li>Tools/Brave web search: add opt-in <code>tools.web.search.brave.mode: "llm-context"</code> so <code>web_search</code> can call Brave's LLM Context endpoint and return extracted grounding snippets with source metadata, plus config/docs/test coverage. (#33383) Thanks @thirumaleshp.</li>
<li>CLI/install: include the short git commit hash in <code>openclaw --version</code> output when metadata is available, and keep installer version checks compatible with the decorated format. (#39712) thanks @sourman.</li>
<li>CLI/backup: improve archive naming for date sorting, add config-only backup mode, and harden backup planning, publication, and verification edge cases. (#40163) Thanks @gumadeiras.</li>
<li>ACP/Provenance: add optional ACP ingress provenance metadata and visible receipt injection (<code>openclaw acp --provenance off|meta|meta+receipt</code>) so OpenClaw agents can retain and report ACP-origin context with session trace IDs. (#40473) thanks @mbelinky.</li>
<li>Tools/web search: alphabetize provider ordering across runtime selection, onboarding/configure pickers, and config metadata, so provider lists stay neutral and multi-key auto-detect now prefers Grok before Kimi. (#40259) thanks @kesku.</li>
<li>Docs/Web search: restore $5/month free-credit details, replace defunct "Data for Search"/"Data for AI" plan names with current "Search" plan, and note legacy subscription validity in Brave setup docs. Follows up on #26860. (#40111) Thanks @remusao.</li>
<li>Extensions/ACPX tests: move the shared runtime fixture helper from <code>src/runtime-internals/</code> to <code>src/test-utils/</code> so the test-only helper no longer looks like shipped runtime code.</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>macOS app/chat UI: route browser proxy through the local node browser service, preserve plain-text paste semantics, strip completed assistant trace/debug wrapper noise from transcripts, refresh permission state after returning from System Settings, and tolerate malformed cron rows in the macOS tab. (#39516) Thanks @Imhermes1.</li>
<li>Android/Play distribution: remove self-update, background location, <code>screen.record</code>, and background mic capture from the Android app, narrow the foreground service to <code>dataSync</code> only, and clean up the legacy <code>location.enabledMode=always</code> preference migration. (#39660) Thanks @obviyus.</li>
<li>Telegram/DM routing: dedupe inbound Telegram DMs per agent instead of per session key so the same DM cannot trigger duplicate replies when both <code>agent:main:main</code> and <code>agent:main:telegram:direct:<id></code> resolve for one agent. Fixes #40005. Supersedes #40116. (#40519) thanks @obviyus.</li>
<li>Cron/Telegram announce delivery: route text-only announce jobs through the real outbound adapters after finalizing descendant output so plain Telegram targets no longer report <code>delivered: true</code> when no message actually reached Telegram. (#40575) thanks @obviyus.</li>
<li>Matrix/DM routing: add safer fallback detection for broken <code>m.direct</code> homeservers, honor explicit room bindings over DM classification, and preserve room-bound agent selection for Matrix DM rooms. (#19736) Thanks @derbronko.</li>
<li>Feishu/plugin onboarding: clear the short-lived plugin discovery cache before reloading the registry after installing a channel plugin, so onboarding no longer re-prompts to download Feishu immediately after a successful install. Fixes #39642. (#39752) Thanks @GazeKingNuWu.</li>
<li>Plugins/channel onboarding: prefer bundled channel plugins over duplicate npm-installed copies during onboarding and release-channel sync, preventing bundled plugins from being shadowed by npm installs with the same plugin ID. (#40092)</li>
<li>Config/runtime snapshots: keep secrets-runtime-resolved config and auth-profile snapshots intact after config writes so follow-up reads still see file-backed secret values while picking up the persisted config update. (#37313) thanks @bbblending.</li>
<li>Gateway/Control UI: resolve bundled dashboard assets through symlinked global wrappers and auto-detected package roots, while keeping configured and custom roots on the strict hardlink boundary. (#40385) Thanks @LarytheLord.</li>
<li>Browser/extension relay: add <code>browser.relayBindHost</code> so the Chrome relay can bind to an explicit non-loopback address for WSL2 and other cross-namespace setups, while preserving loopback-only defaults. (#39364) Thanks @mvanhorn.</li>
<li>Browser/CDP: normalize loopback direct WebSocket CDP URLs back to HTTP(S) for <code>/json/*</code> tab operations so local <code>ws://</code> / <code>wss://</code> profiles can still list, focus, open, and close tabs after the new direct-WS support lands. (#31085) Thanks @shrey150.</li>
<li>Browser/CDP: rewrite wildcard <code>ws://0.0.0.0</code> and <code>ws://[::]</code> debugger URLs from remote <code>/json/version</code> responses back to the external CDP host/port, fixing Browserless-style container endpoints. (#17760) Thanks @joeharouni.</li>
<li>Browser/extension relay: wait briefly for a previously attached Chrome tab to reappear after transient relay drops before failing with <code>tab not found</code>, reducing noisy reconnect flakes. (#32461) Thanks @AaronWander.</li>
<li>macOS/Tailscale gateway discovery: keep Tailscale Serve probing alive when other remote gateways are already discovered, prefer direct transport for resolved <code>.ts.net</code> and Tailscale Serve gateways, and set <code>TERM=dumb</code> for GUI-launched Tailscale CLI discovery. (#40167) thanks @ngutman.</li>
<li>TUI/theme: detect light terminal backgrounds via <code>COLORFGBG</code> and pick a WCAG AA-compliant light palette, with <code>OPENCLAW_THEME=light|dark</code> override for terminals without auto-detection. (#38636) Thanks @ademczuk and @vincentkoc.</li>
<li>Agents/openai-codex: normalize <code>gpt-5.4</code> fallback transport back to <code>openai-codex-responses</code> on <code>chatgpt.com/backend-api</code> when config drifts to the generic OpenAI responses endpoint. (#38736) Thanks @0xsline.</li>
<li>Models/openai-codex GPT-5.4 forward-compat: use the GPT-5.4 1,050,000-token context window and 128,000 max tokens for <code>openai-codex/gpt-5.4</code> instead of inheriting stale legacy Codex limits in resolver fallbacks and model listing. (#37876) thanks @yuweuii.</li>
<li>Tools/web search: restore Perplexity OpenRouter/Sonar compatibility for legacy <code>OPENROUTER_API_KEY</code>, <code>sk-or-...</code>, and explicit <code>perplexity.baseUrl</code> / <code>model</code> setups while keeping direct Perplexity keys on the native Search API path. (#39937) Thanks @obviyus.</li>
<li>Agents/failover: detect Amazon Bedrock <code>Too many tokens per day</code> quota errors as rate limits across fallback, cron retry, and memory embeddings while keeping context-window <code>too many tokens per request</code> errors out of the rate-limit lane. (#39377) Thanks @gambletan.</li>
<li>Mattermost replies: keep <code>root_id</code> pinned to the existing thread root when an agent replies inside a thread, while still using reply-target threading for top-level posts. (#27744) thanks @hnykda.</li>
<li>Telegram/DM partial streaming: keep DM preview lanes on real message edits instead of native draft materialization so final replies no longer flash a second duplicate copy before collapsing back to one.</li>
<li>macOS overlays: fix VoiceWake, Talk, and Notify overlay exclusivity crashes by removing shared <code>inout</code> visibility mutation from <code>OverlayPanelFactory.present</code>, and add a repeated Talk overlay smoke test. (#39275, #39321) Thanks @fellanH.</li>
<li>macOS Talk Mode: set the speech recognition request <code>taskHint</code> to <code>.dictation</code> for mic capture, and add regression coverage for the request defaults. (#38445) Thanks @dmiv.</li>
<li>macOS release packaging: default <code>scripts/package-mac-app.sh</code> to universal binaries for <code>BUILD_CONFIG=release</code>, and clarify that <code>scripts/package-mac-dist.sh</code> already produces the release zip + DMG. (#33891) Thanks @cgdusek.</li>
<li>Hooks/session-memory: keep <code>/new</code> and <code>/reset</code> memory artifacts in the bound agent workspace and align saved reset session keys with that workspace when stale main-agent keys leak into the hook path. (#39875) thanks @rbutera.</li>
<li>Sessions/model switch: clear stale cached <code>contextTokens</code> when a session changes models so status and runtime paths recompute against the active model window. (#38044) thanks @yuweuii.</li>
<li>ACP/session history: persist transcripts for successful ACP child runs, preserve exact transcript text, record ACP spawned-session lineage, and keep spawn-time transcript-path persistence best-effort so history storage failures do not block execution. (#40137) thanks @mbelinky.</li>
<li>Docs/browser: add a layered WSL2 + Windows remote Chrome CDP troubleshooting guide, including Control UI origin pitfalls and extension-relay bind-address guidance. (#39407) Thanks @Owlock.</li>
<li>Context engine registry/bundled builds: share the registry state through a <code>globalThis</code> singleton so duplicated bundled module copies can resolve engines registered by each other at runtime, with regression coverage for duplicate-module imports. (#40115) thanks @jalehman.</li>
<li>Podman/setup: fix <code>cannot chdir: Permission denied</code> in <code>run_as_user</code> when <code>setup-podman.sh</code> is invoked from a directory the target user cannot access, by wrapping user-switch calls in a subshell that cd's to <code>/tmp</code> with <code>/</code> fallback. (#39435) Thanks @langdon and @jlcbk.</li>
<li>Podman/SELinux: auto-detect SELinux enforcing/permissive mode and add <code>:Z</code> relabel to bind mounts in <code>run-openclaw-podman.sh</code> and the Quadlet template, fixing <code>EACCES</code> on Fedora/RHEL hosts. Supports <code>OPENCLAW_BIND_MOUNT_OPTIONS</code> override. (#39449) Thanks @langdon and @githubbzxs.</li>
<li>Agents/context-engine plugins: bootstrap runtime plugins once at embedded-run, compaction, and subagent boundaries so plugin-provided context engines and hooks load from the active workspace before runtime resolution. (#40232)</li>
<li>Docs/Changelog: correct the contributor credit for the bundled Control UI global-install fix to @LarytheLord. (#40420) Thanks @velvet-shark.</li>
<li>Telegram/media downloads: time out only stalled body reads so polling recovers from hung file downloads without aborting slow downloads that are still streaming data. (#40098) thanks @tysoncung.</li>
<li>Docker/runtime image: prune dev dependencies, strip build-only dist metadata for smaller Docker images. (#40307) Thanks @vincentkoc.</li>
<li>Gateway/restart timeout recovery: exit non-zero when restart-triggered shutdown drains time out so launchd/systemd restart the gateway instead of treating the failed restart as a clean stop. Landed from contributor PR #40380 by @dsantoreis. Thanks @dsantoreis.</li>
<li>Gateway/config restart guard: validate config before service start/restart and keep post-SIGUSR1 startup failures from crashing the gateway process, reducing invalid-config restart loops and macOS permission loss. Landed from contributor PR #38699 by @lml2468. Thanks @lml2468.</li>
<li>Gateway/launchd respawn detection: treat <code>XPC_SERVICE_NAME</code> as a launchd supervision hint so macOS restarts exit cleanly under launchd instead of attempting detached self-respawn. Landed from contributor PR #20555 by @dimat. Thanks @dimat.</li>
<li>Telegram/poll restart cleanup: abort the in-flight Telegram API fetch when shutdown or forced polling restarts stop a runner, preventing stale <code>getUpdates</code> long polls from colliding with the replacement runner. Landed from contributor PR #23950 by @Gkinthecodeland. Thanks @Gkinthecodeland.</li>
<li>Cron/restart catch-up staggering: limit immediate missed-job replay on startup and reschedule the deferred remainder from the post-catchup clock so restart bursts do not starve the gateway or silently skip overdue recurring jobs. Landed from contributor PR #18925 by @rexlunae. Thanks @rexlunae.</li>
<li>Cron/owner-only tools: pass trusted isolated cron runs into the embedded agent with owner context so <code>cron</code>/<code>gateway</code> tooling remains available after the owner-auth hardening narrowed direct-message ownership inference.</li>
<li>Browser/SSRF: block private-network intermediate redirect hops in strict browser navigation flows and fail closed when remote tab-open paths cannot inspect redirect chains. Thanks @zpbrent.</li>
<li>MS Teams/authz: keep <code>groupPolicy: "allowlist"</code> enforcing sender allowlists even when a team/channel route allowlist is configured, so route matches no longer widen group access to every sender in that route. Thanks @zpbrent.</li>
<li>Security/system.run: bind approved <code>bun</code> and <code>deno run</code> script operands to on-disk file snapshots so post-approval script rewrites are denied before execution.</li>
<li>Skills/download installs: pin the validated per-skill tools root before writing downloaded archives, so rebinding the lexical tools path cannot redirect download writes outside the intended tools directory. Thanks @tdjackey.</li>
<li>Browser/Chrome MCP: wait for existing-session browser tabs to become usable after attach instead of treating the initial Chrome MCP handshake as ready, which reduces user-profile timeouts and repeated consent churn on macOS Chrome attach flows. Fixes #52930. Thanks @vincentkoc.</li>
<li>Browser/CDP: reuse an already-running loopback browser after a short initial reachability miss instead of immediately falling back to relaunch detection, which fixes second-run browser start/open regressions on slower headless Linux setups. Fixes #53004. Thanks @vincentkoc.</li>
<li>ClawHub/macOS auth: honor macOS auth config and XDG auth paths for saved ClawHub credentials, so <code>openclaw skills ...</code> and gateway skill browsing keep using the signed-in auth state instead of silently falling back to unauthenticated mode. Fixes #53034.</li>
<li>ClawHub/macOS: read the local ClawHub login from the macOS Application Support path and still honor XDG config on macOS, so skill browsing uses the logged-in token on both default and XDG-style setups. Fixes #52949. Thanks @scoootscooob.</li>
<li>ClawHub/skills: resolve the local ClawHub auth token for gateway skill browsing and switch browse-all requests to search so ClawControl stops falling into unauthenticated 429s and empty authenticated skill lists. Fixes #52949. Thanks @vincentkoc.</li>
<li>Plugins/message tool: make Discord <code>components</code> and Slack <code>blocks</code> optional again, and route Feishu <code>message(..., media=...)</code> sends through the outbound media path, so pin/unpin/react flows stop failing schema validation and Feishu file/image attachments actually send. Fixes #52970 and #52962. Thanks @vincentkoc.</li>
<li>Gateway/model pricing: stop <code>openrouter/auto</code> pricing refresh from recursing indefinitely during bootstrap, so OpenRouter auto routes can populate cached pricing and <code>usage.cost</code> again. Fixes #53035. Thanks @vincentkoc.</li>
<li>Mistral/models: lower bundled Mistral max-token defaults to safe output budgets and teach <code>openclaw doctor --fix</code> to repair old persisted Mistral provider configs that still carry context-sized output limits, avoiding deterministic Mistral 422 rejects on fresh and existing setups. Fixes #52599. Thanks @vincentkoc.</li>
<li>Agents/web_search: use the active runtime <code>web_search</code> provider instead of stale/default selection, so agent turns keep hitting the provider you actually configured. Fixes #53020. Thanks @jzakirov.</li>
<li>Models/OpenAI Codex OAuth: bootstrap the env-configured HTTP/HTTPS proxy dispatcher on the stored-credential refresh path before token renewal runs, so expired Codex OAuth profiles can refresh successfully in proxy-required environments instead of locking users out after the first token expiry.</li>
<li>Plugins/memory-lancedb: bootstrap LanceDB into plugin runtime state on first use when the bundled npm install does not already have it, so <code>plugins.slots.memory="memory-lancedb"</code> works again after global npm installs without moving LanceDB into OpenClaw core dependencies. Fixes #26100.</li>
<li>Config/plugins: treat stale unknown <code>plugins.allow</code> ids as warnings instead of fatal config errors, so recovery commands like <code>plugins install</code>, <code>doctor --fix</code>, and <code>status</code> still run when a plugin is missing locally. Fixes #52992. Thanks @vincentkoc.</li>
<li>Doctor/WhatsApp: stop auto-enable from appending built-in channel ids like <code>whatsapp</code> to <code>plugins.allow</code>, so <code>openclaw doctor --fix</code> no longer writes schema-invalid plugin allowlist entries when repairing built-in channels. Fixes #52931. Thanks @vincentkoc.</li>
<li>Telegram/auto-reply: preserve same-chat inbound debounce order without stranding stale busy-session followups, and keep same-key overflow turns ordered when tracked debounce keys are saturated. (#52998) Thanks @osolmaz.</li>
<li>Discord/commands: return an explicit unauthorized reply for privileged native slash commands instead of falling through to Discord's misleading generic completion when auth gates reject the sender. Fixes #53041. Thanks @scoootscooob.</li>
<li>Channels/catalog: let external channel catalogs override shipped fallback metadata and honor overridden npm specs during channel setup, so custom channel catalogs no longer fall back to bundled packages when a channel id matches. (#52988)</li>
<li>Voice-call/Plivo: stabilize Plivo v2 replay keys so webhook retries and replay protection stop colliding on valid follow-up deliveries.</li>
<li>Agents/skills: prefer the active resolved runtime snapshot for embedded skill config and env injection, so <code>skills.entries.<skill>.apiKey</code> SecretRefs resolve correctly during embedded startup instead of failing on raw source config. Fixes #53098. Thanks @vincentkoc.</li>
<li>Agents/subagents: recheck timed-out worker waits against the latest runtime snapshot before sending completion events, so fast-finishing workers stop being reported as timed out when they actually succeeded. Fixes #53106. Thanks @vincentkoc.</li>
<li>Agents/Anthropic: preserve latest assistant thinking and redacted-thinking block ordering during transcript image sanitization so follow-up turns do not trip Anthropic's unmodified-thinking validation. (#52961) Thanks @vincentkoc.</li>
<li>Gateway/probe: stop successful gateway handshakes from timing out as unreachable while post-connect detail RPCs are still loading, so slow devices report a reachable RPC failure instead of a false negative dead gateway. Fixes #52927. Thanks @vincentkoc.</li>
<li>Gateway/supervision: stop lock conflicts from crash-looping under launchd and systemd by keeping the duplicate process in a retry wait instead of exiting as a failure while another healthy gateway still owns the lock. Fixes #52922. Thanks @vincentkoc.</li>
<li>Gateway/auth: require auth for canvas routes and admin scope for agent session reset, so anonymous canvas access and non-admin reset requests fail closed.</li>
<li>Release/install: keep previously released bundled plugins and Control UI assets in published openclaw npm installs, and fail release checks when those shipped artifacts are missing. Thanks @vincentkoc.</li>
</ul>
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
@@ -27,14 +27,34 @@ Status: **extremely alpha**. The app is actively being rebuilt from the ground u
```bash
cd apps/android
./gradlew :app:assembleDebug
./gradlew :app:installDebug
./gradlew :app:testDebugUnitTest
./gradlew :app:assemblePlayDebug
./gradlew :app:installPlayDebug
./gradlew :app:testPlayDebugUnitTest
cd ../..
bun run android:bundle:release
```
`bun run android:bundle:release` auto-bumps Android `versionName`/`versionCode` in `apps/android/app/build.gradle.kts`, then builds a signed release `.aab`.
Third-party debug flavor:
```bash
cd apps/android
./gradlew :app:assembleThirdPartyDebug
./gradlew :app:installThirdPartyDebug
./gradlew :app:testThirdPartyDebugUnitTest
```
`bun run android:bundle:release` auto-bumps Android `versionName`/`versionCode` in `apps/android/app/build.gradle.kts`, then builds two signed release bundles:
- Play build: `apps/android/build/release-bundles/openclaw-<version>-play-release.aab`
@@ -176,6 +196,48 @@ More details: `docs/platforms/android.md`.
-`CAMERA` for `camera.snap` and `camera.clip`
-`RECORD_AUDIO` for `camera.clip` when `includeAudio=true`
## Google Play Restricted Permissions
As of March 19, 2026, these manifest permissions are the main Google Play policy risk for this app:
-`READ_SMS`
-`SEND_SMS`
-`READ_CALL_LOG`
Why these matter:
- Google Play treats SMS and Call Log access as highly restricted. In most cases, Play only allows them for the default SMS app, default Phone app, default Assistant, or a narrow policy exception.
- Review usually involves a `Permissions Declaration Form`, policy justification, and demo video evidence in Play Console.
- If we want a Play-safe build, these should be the first permissions removed behind a dedicated product flavor / variant.
Current OpenClaw Android implication:
- APK / sideload build can keep SMS and Call Log features.
- Google Play build should exclude SMS send/search and Call Log search unless the product is intentionally positioned and approved as a default-handler exception case.
- The repo now ships this split as Android product flavors:
-`play`: removes `READ_SMS`, `SEND_SMS`, and `READ_CALL_LOG`, and hides SMS / Call Log surfaces in onboarding, settings, and advertised node capabilities.
-`thirdParty`: keeps the full permission set and the existing SMS / Call Log functionality.
Policy links:
- [Google Play SMS and Call Log policy](https://support.google.com/googleplay/android-developer/answer/10208820?hl=en)
- [Google Play sensitive permissions policy hub](https://support.google.com/googleplay/android-developer/answer/16558241)
Text("Blank keeps notification events on this device's default notification route. Set a key only to pin forwarding into a different session.",style=mobileCaption1,color=mobileTextSecondary)
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.