Add a bundled Arcee AI provider plugin with ARCEEAI_API_KEY onboarding,
Trinity model catalog (mini, large-preview, large-thinking), and
OpenAI-compatible API support.
- Trinity Large Thinking: 256K context, reasoning enabled
- Trinity Large Preview: 128K context, general-purpose
- Trinity Mini 26B: 128K context, fast and cost-efficient
Undici 8.0 defaults HTTPS clients to negotiate HTTP/2 via ALPN, which is
incompatible with the custom `connect.lookup` callback used for SSRF DNS
pinning. This caused `TypeError: fetch failed` in web_fetch/web_search.
Explicitly set `allowH2: false` on all dispatcher creation paths (Agent,
EnvHttpProxyAgent, ProxyAgent) to restore HTTP/1.1 behavior and keep the
pinned DNS lookup working reliably.
Closes#61738
- Pagination now searches by message seq value instead of using
cursorSeq-1 as array index. After sanitization drops rows, seqs
become sparse and positional indexing breaks cursor traversal.
- SSE unbounded fast path now sanitizes incremental messages through
sanitizeChatHistoryMessages before emitting, so NO_REPLY and
directive messages are suppressed consistently with initial history.
* fix(agents): gate WS text delta emission on valid phase value, not map key existence
When output_item.added arrives without phase metadata, outputItemPhaseById
stores undefined. The previous .has() check returned true for undefined
values, bypassing the buffering gate and leaking commentary as unphased
visible content.
Fix: change .has() to .get() !== undefined on both delta and done handlers.
Fixes#61477
* docs: note WS phase buffering fix (#61954) (thanks @100yenadmin)
* test(agents): cover phaseless WS output_text.done buffering (#61954)
* test(commands): fix session-store import path for tsgo (#61968)
---------
Co-authored-by: Eva <eva@100yen.org>
When users visit the Control UI with ?token=<token>, they see
"device identity required" with no hint about the correct URL format.
This change:
- Detects when token is read from query string vs URL fragment
- Warns via console when ?token= is used
- Shows an inline hint in the overview error area directing users
to use #token=<token> instead
Fixes#54842
- Remove mistakenly committed openclaw-2026-04-03.log
- Add 'has-copy' CSS class to chat bubbles when copy button is present,
so the .chat-bubble.has-copy padding-right rule actually applies
Increase right padding on .chat-bubble.has-copy from 36px to 62px to
accommodate both copy and canvas action buttons without obscuring text.
Fixes#61514
Fixes#61476
Untagged text blocks in mixed assistant messages were forced to undefined
phase when any sibling had an explicit textSignature phase. Now they
correctly inherit the message-level assistantMessagePhase, preventing
commentary leaks during history replay.
Removes the hasExplicitBlockPhase scan — untagged blocks always inherit
m.phase. Blocks with explicit textSignature.phase still use their own.
94/94 tests pass. Regression test added for mixed explicit/untagged blocks.
* fix(msteams): add SSRF validation to file consent upload URL
The uploadToConsentUrl() function previously accepted any URL from the
fileConsent/invoke response without validation. A malicious Teams tenant
user could craft an invoke activity with an attacker-controlled uploadUrl,
causing the bot to PUT file data to arbitrary destinations (SSRF).
This commit adds validateConsentUploadUrl() which enforces:
1. HTTPS-only protocol
2. Hostname must match a strict allowlist of Microsoft/SharePoint
domains (sharepoint.com, graph.microsoft.com, onedrive.com, etc.)
3. DNS resolution check rejects private/reserved IPs (RFC 1918,
loopback, link-local) to prevent DNS rebinding attacks
The CONSENT_UPLOAD_HOST_ALLOWLIST is intentionally narrower than the
existing DEFAULT_MEDIA_HOST_ALLOWLIST, excluding overly broad domains
like blob.core.windows.net and trafficmanager.net that any Azure
customer can create endpoints under.
Includes 47 tests covering IPv4/IPv6 private IP detection, protocol
enforcement, hostname allowlist matching, DNS failure handling, and
end-to-end upload validation.
* fix(msteams): validate all DNS answers for consent uploads
* fix(msteams): restore changelog header
---------
Co-authored-by: Brad Groux <bradgroux@users.noreply.github.com>
Fixes#61377
The provider attribution code only recognized api.x.ai as the xAI-native
endpoint. Some users have api.grok.x.ai configured (or it appears in
certain DNS/config scenarios) which would not resolve as xAI-native,
causing web_search tool failures.
This change adds api.grok.x.ai as an alias for xAI-native endpoint
classification alongside api.x.ai.
When gateway.tls.enabled is true, gateway status probes now target local loopback/tailnet over wss and pass the local TLS fingerprint for localLoopback probes. This avoids false unreachable results for healthy local TLS gateways.
Fixes#61767
Co-authored-by: ThanhNguyxn <thanhnguyentuan2007@gmail.com>
After a SIGUSR1 in-process restart following an npm upgrade from v2026.4.2
to v2026.4.5, the globalThis singleton created by the old code version
lacks the activeTaskWaiters field added in v2026.4.5. resolveGlobalSingleton
returns the stale object as-is, causing notifyActiveTaskWaiters() to call
Array.from(undefined) and crash the gateway in a loop.
Add a schema migration step in getQueueState() that patches the missing
field on legacy singleton objects. Add a regression test that plants a
v2026.4.2-shaped state object and verifies resetAllLanes() and
waitForActiveTasks() succeed without throwing.
Fixes#61905
The plugin loader cache key included runtimeSubagentMode, which is
derived from allowGatewaySubagentBinding. Since different call sites in
the message processing pipeline pass different values for this flag,
each call produced a distinct cache key, triggering redundant
register() calls (40+ in 24 seconds after startup).
runtimeSubagentMode does not affect which plugins are loaded or how
they are configured — it is only metadata stored alongside the active
registry state. Removing it from the cache key lets all call sites
share the same cached registry regardless of their binding mode.
Fixes#61756
When chunks_vec cannot be updated (sqlite-vec extension not loaded),
the memory index now emits an error-level warning instead of silently
reporting success.
Before this change: 'Memory index updated (hull).' was emitted even
when the vector index (chunks_vec) was not updated due to sqlite-vec
being unavailable. This masked silent vector recall degradation.
After this change:
- If vector.enabled=true and vector.available=false: emits
'Memory index WARNING (agentId): chunks_vec not updated — sqlite-vec
unavailable: <reason>. Vector recall degraded.'
- If vector is healthy: emits normal success message unchanged
- Per-file warning also emitted in writeChunks when chunks are written
without vector embeddings
Fixes: HELM-0251 (local dist patch — this makes it update-safe)
Related: HELM-0252 (this PR)
Remote browser profiles can pass HTTP reachability while Browser.getVersion on the CDP websocket is still warming up right after restart. Add one retry in ensureBrowserAvailable for remote CDP profiles and cover it with a regression test.
Fixes#57397
Co-authored-by: ThanhNguyxn <thanhnguyentuan2007@gmail.com>
Loose lists (blank lines between items) produce <li><p>...</p></li> via
markdown-it, causing Element to render list numbers on separate lines
from their content. Fix by setting hidden=true on paragraph tokens
inside list items before rendering, mirroring what markdown-it already
does for tight lists.
Closes#60997. Thanks @gucasbrg.
Co-Authored-By: Claude claude-opus-4-6 <noreply@anthropic.com>
Signed-off-by: Jakub Rusz <jrusz@proton.me>
## Summary
- Problem: `normalizeDirectiveWhitespace` applied whitespace-collapsing regexes globally, including inside fenced code blocks (` ``` ` / `~~~`) and indent-code-blocks (4-space / tab), corrupting indentation in assistant replies that contain code snippets
- Why it matters: Any language where indentation is significant (Python, Go, YAML, etc.) or visually meaningful would render incorrectly after stripping inline directive tags
- What changed: Stash code blocks under a Unicode private-use sentinel (`\uE000`) before normalization, run the existing prose regexes on the masked text, then restore the original blocks verbatim
- What did NOT change: All prose normalization rules are retained as-is (`\r\n`, multi-space collapse, leading blank-line strip, trailing whitespace, 3+ newline fold)
## Change Type
- [x] Bug fix
## Scope
- [ ] Gateway / orchestration
## Root Cause
- Root cause: Prose whitespace regexes were applied to the full text string with no awareness of Markdown code block boundaries
- Missing detection / guardrail: No tests covered indented content inside fenced blocks
- Contributing context: Directive tag stripping (`[[reply_to_current]]`, `[[audio_as_voice]]`) is applied before delivery, making the normalization step a silent corruption point for code-heavy replies
## Regression Test Plan
- Coverage level that should have caught this:
- [x] Unit test
- Target test or file: `src/utils/directive-tags.test.ts`
- Scenario the test should lock in: `parseInlineDirectives` with fenced/indent code blocks must preserve all leading whitespace inside those blocks
- Why this is the smallest reliable guardrail: Pure function with deterministic string in/out; no mocks needed
- If no new test is added, why not: 7 new unit tests added
## User-visible / Behavior Changes
Code blocks in assistant replies containing `[[reply_to_current]]` or `[[audio_as_voice]]` directives now retain correct indentation after the directive is stripped.
## Security Impact
- New permissions/capabilities? No
- Secrets/tokens handling changed? No
- New/changed network calls? No
- Command/tool execution surface changed? No
- Data access scope changed? No
## Compatibility / Migration
- Backward compatible? Yes
- Config/env changes? No
- Migration needed? No
Co-Authored-By: Codemax <codemax@binance.com>
Fixes issue #61358 where isGatewayMessageChannel intermittently rejects valid third-party channel plugins (openclaw-weixin, qqbot).
The pinned registry contains authoritative channel configurations for delivery, so it should be checked first before falling back to the active plugin registry.
The inner `.*\s+` in `(?:[A-Za-z_][A-Za-z0-9_]*=.*\s+)*` creates
catastrophic backtracking because both `.*` and `\s+` can match
whitespace. When the exec tool processes commands with `VAR=value`
assignments followed by whitespace-heavy text (e.g. HTML heredocs),
the regex engine hangs permanently at 100% CPU.
Replace `.*` with `\S*` in all three instances. Shell prefix variable
assignments cannot contain unquoted whitespace in the value, so `\S*`
is semantically correct and eliminates the ambiguity.
Fixes#61881
Apply sanitizeChatHistoryMessages before pagination in the bounded SSE
history refresh path, consistent with the unbounded path. Initialize
rawTranscriptSeq from the raw transcript's last __openclaw.seq value
instead of the sanitized history length, preventing seq drift when
sanitization drops messages.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Bounded/cursor SSE refresh path now sanitizes through
sanitizeChatHistoryMessages before paginating, matching the
unbounded path and initial history load.
- Export DEFAULT_CHAT_HISTORY_TEXT_MAX_CHARS from chat.ts and
import in sessions-history-http.ts instead of duplicating.
The createStreamFn callback hardcoded config.models.providers.ollama.baseUrl,
ignoring the actual provider ID from the context. When multiple Ollama providers
are configured on different ports (e.g. ollama on 11434, ollama2 on 11435), all
requests routed to the first provider's port.
Export resolveConfiguredOllamaProviderConfig from stream.ts and use it with the
ctx.provider parameter to dynamically look up the correct baseUrl per provider.
Closes#61678
- Use `sonnet-4` substring match instead of enumerating `sonnet-4-5`,
`sonnet-4-6` explicitly. This is safe because legacy `claude-3-5-sonnet`
does not contain `sonnet-4`, and it future-proofs for sonnet-4-7+.
- Export `shouldPreserveThinkingBlocks` from provider-replay-helpers.ts
and import it in transcript-policy.ts instead of duplicating the logic.
Addresses review feedback from Greptile.
The shared-helper tests still expected dropThinkingBlocks: true for
claude-sonnet-4-6. Updated to match the new behavior where Sonnet 4.6
preserves thinking blocks.
Claude Opus 4.5+ and Sonnet 4.5+ preserve thinking blocks in model context
by default. Dropping them from prior turns (as was correct for Sonnet 3.7)
breaks Anthropic's prefix-based prompt cache matching, causing cache misses
after every thinking turn.
This change conditions dropThinkingBlocks on the model version:
- Preserve (no drop) for: opus-4.x, sonnet-4.5+, haiku-4.x, and future models
- Drop for: claude-3-7-sonnet and earlier
Fixes#61793
See: https://platform.claude.com/docs/en/build-with-claude/extended-thinking#differences-in-thinking-across-model-versions
* fix(exec ): stop emitting tool updates after session is backgrounded
When an exec session is backgrounded (background: true), the owning
agent run resolves its tool-call promise and may finish. The stdout
handler's emitUpdate() closure, however, kept invoking opts.onUpdate(),
delivering tool_execution_update events to a listener whose active run
had already ended. This surfaced as an unhandled rejection and crashed
the gateway process.
Guard emitUpdate() with a session.backgrounded || session.exited check
so that post-background output is still captured via appendOutput() but
no longer forwarded to the (now-stale) agent-loop callback.
Fixes#61592
* style: trim exec backgrounding comments
* fix: stop emitting post-background exec updates (#61627) (thanks @openperf)
* fix: place exec changelog entry at end of fixes (#61627) (thanks @openperf)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* feat(bedrock-mantle): add IAM credential auth via @aws/bedrock-token-generator
Mantle previously required a manually-created API key (AWS_BEARER_TOKEN_BEDROCK).
This adds automatic bearer token generation from IAM credentials using the
official @aws/bedrock-token-generator package.
Auth priority:
1. Explicit AWS_BEARER_TOKEN_BEDROCK env var (manual API key from Console)
2. IAM credentials via getTokenProvider() → Bearer token (instance roles,
SSO profiles, access keys, EKS IRSA, ECS task roles)
Token is cached in memory (1hr TTL, generated with 2hr validity) and in
process.env.AWS_BEARER_TOKEN_BEDROCK for downstream sync reads.
Falls back gracefully when package is not installed or credentials are
unavailable — Mantle provider simply not registered.
Closes#45152
* fix(bedrock-mantle): harden IAM auth
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix: implement Windows stale gateway process cleanup before restart
findGatewayPidsOnPortSync() returned [] immediately on Windows, causing
cleanStaleGatewayProcessesSync() to skip killing old gateway processes
during self-restart (triggerOpenClawRestart -> schtasks path). This led
to an infinite retry loop: 'gateway already running under schtasks;
waiting 5000ms before retrying startup'.
Changes:
- Extract Windows port/process helpers into shared windows-port-pids.ts
to break the circular import between restart-stale-pids.ts and
gateway-processes.ts, with configurable timeoutMs for poll compliance
- findGatewayPidsOnPortSync: discover + verify Windows gateway PIDs via
readWindowsListeningPidsOnPortSync + readWindowsProcessArgsSync
- pollPortOnceWindows: use short POLL_SPAWN_TIMEOUT_MS (400ms) so a
single slow PowerShell call cannot exceed the 2s polling budget
- terminateStaleProcessesSync: add terminateStaleProcessesWindows using
taskkill.exe (graceful /T first, then /F force-kill)
Fixes the Windows gateway restart infinite loop caused by the schtasks
supervisor detecting a port conflict it cannot resolve.
* fix: tighten windows stale gateway cleanup
* fix: preserve windows restart probe failures
* refactor: unify windows gateway pid verification
* fix: preserve windows argv probe failures
* fix: windows self-restart stale gateway cleanup (#60480) (thanks @arifahmedjoy)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* feat(bedrock): add Bedrock Mantle (OpenAI-compatible) provider
New amazon-bedrock-mantle extension that provides auto-discovery and
authentication for Amazon Bedrock Mantle endpoints.
Mantle (bedrock-mantle.<region>.api.aws) is Amazon Bedrock's OpenAI-
compatible API surface, separate from the existing bedrock-runtime
(ConverseStream) endpoint. It has its own model catalog including
models not available via ConverseStream (e.g. openai.gpt-oss-120b,
mistral.devstral-2-123b).
Extension structure:
- discovery.ts: Model discovery via GET /v1/models (OpenAI format),
bearer token resolution, implicit provider configuration
- register.sync.runtime.ts: Provider registration with catalog,
error classification (rate limits, context overflow)
- openclaw.plugin.json: Plugin manifest, enabledByDefault
Auth support:
- Long-lived Bedrock API key (AWS_BEARER_TOKEN_BEDROCK env var)
created from the AWS Console → used directly as Bearer token
- Pre-generated SigV4-derived tokens (via aws-bedrock-token-generator)
set in AWS_BEARER_TOKEN_BEDROCK → works transparently
Provider config (auto-resolved when AWS_BEARER_TOKEN_BEDROCK is set):
api: "openai-completions"
baseUrl: "https://bedrock-mantle.<region>.api.aws/v1"
auth: "api-key" (bearer token)
Available in 12 regions: us-east-1, us-east-2, us-west-2,
ap-northeast-1, ap-south-1, ap-southeast-3, eu-central-1,
eu-west-1, eu-west-2, eu-south-1, eu-north-1, sa-east-1
Tests: 15 passing (13 discovery + 2 plugin registration)
* chore(bedrock): clarify mantle bearer auth scope
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* feat(bedrock): add inference profile discovery and region injection
Inference profiles (cross-region and application) work with ConverseStream
but require the SDK client region to match the profile region. Without
this, users get "The provided model identifier is invalid" errors when
using cross-region profiles like us.anthropic.claude-sonnet-4-6.
Changes:
1. Inference profile discovery (discovery.ts):
- Call ListInferenceProfiles alongside ListFoundationModels (parallel)
- Inference profiles INHERIT capabilities from their underlying
foundation model (modalities, reasoning, context window, cost)
- resolveBaseModelId() maps profile → foundation model:
"us.anthropic.claude-sonnet-4-6" → "anthropic.claude-sonnet-4-6"
Application ARNs → extract model ID from models[].modelArn
- Graceful degradation if IAM lacks bedrock:ListInferenceProfiles
- Provider filter applies to profiles via underlying model ARNs
2. Region injection (register.sync.runtime.ts):
- Extract region from provider baseUrl or bedrockDiscovery.region
- Pass through to pi-ai options.region in wrapStreamFn
- Ensures SDK client connects to correct regional endpoint
3. Inference profile model detection (anthropic-family-cache-semantics.ts):
- isAnthropicBedrockModel() now recognizes application inference
profile ARNs (arn:aws:bedrock:...:application-inference-profile/*)
4. Tests (discovery.test.ts):
- New: inference profile inheritance test (4 models: 1 foundation +
3 profiles, verifies capability inheritance, inactive filtering)
- New: graceful AccessDeniedException handling test
- Updated: all existing tests for dual-API discovery pattern
Fixes#55642
* fix(bedrock): preserve inference profile model lookup
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
resolveTrackedFacadePluginId triggers config loading (plugin auto-enable,
channel discovery) which can re-enter loadBundledPluginPublicSurfaceModuleSync
for the same module. Because the sentinel was still empty at that point,
re-entrant callers saw undefined exports (e.g. shouldNormalizeGoogleProviderConfig).
Move Object.assign(sentinel, loaded) before the plugin ID resolution so any
re-entrant lookup through the cached sentinel finds the real exports.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The cron tool schema used type arrays (['string','null']), the 'not'
keyword, and 'const' — all unsupported by the OpenAPI 3.0 subset that
Gemini-backed providers (e.g. GitHub Copilot) enforce. This caused
HTTP 400 for every request when cron was enabled.
Replace type arrays with scalar types, remove not/const from
CronFailureAlertSchema, and add 'not' to the Gemini unsupported
keywords list as defense-in-depth.
Fixes#61206
* fix(acpx): store agent session ID when session/load fails
When an ACP agent (e.g. Gemini CLI) rejects the acpx-generated session
ID via session/load and falls back to session/new, the agent-returned
session ID was previously discarded. This caused identity stuck at
pending forever, multi-turn failures, lost completion events, and
persistent reconcile warnings.
- Parse ACP protocol stream in runTurn() to capture agent session IDs
- Flip resolveRuntimeResumeSessionId() to prefer agentSessionId
- Add createIdentityFromHandleEvent() for handle-sourced identity
- Layer handle event identity before status in reconcile
- Add regression tests for load fallback and restart resume
Closes#52182
* ACPX: prefer decoded session ids
* ACPX: refresh runtime handle state from status
---------
Co-authored-by: Wesley <imwyvern@users.noreply.github.com>
* fix(infra): windows-task-restart fallback to startup entry when schtasks task is unregistered
* fix code style problem
* use /min for startup fallback and assert schtasks pre-check in test
* fix: windows restart fallback when scheduled task is unregistered (#58943) (thanks @imechZhangLY)
---------
Co-authored-by: Luyao Zhang <zhangluyao@microsoft.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* plugins: include resolved workspaceDir in provider hook cache keys
resolveProviderPluginsForHooks, resolveProviderPluginsForCatalogHooks, and
resolveProviderRuntimePlugin used the raw params.workspaceDir for cache keys
and plugin-id discovery while resolvePluginProviders already fell back to
the active registry workspace. Resolve workspaceDir once at the top of each
function so cache keys, candidate filtering, and loading all use the same
workspace root.
* fix(plugins): inherit runtime workspace for snapshot loads
* test(gateway): stub runtime registry seam
* fix(plugins): restore workspace fallback after rebase
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(bedrock): stop injecting fake apiKey marker for aws-sdk auth when no env vars exist
When the Bedrock provider uses auth: "aws-sdk" and no AWS environment
variables are set (EC2 instance roles, ECS task roles, etc.),
resolveAwsSdkApiKeyVarName() fell back to "AWS_PROFILE" unconditionally.
This string was injected as apiKey in the provider config during
normalisation, which poisoned the downstream auth resolver — it treated
the marker as a literal key and failed with "No API key found".
The fix:
- resolveAwsSdkApiKeyVarName() now returns undefined (not "AWS_PROFILE")
when no AWS env vars are present
- resolveBedrockConfigApiKey() (extension) gets the same fix
- resolveMissingProviderApiKey() guards both the providerApiKeyResolver
and direct aws-sdk branches: if the resolver returns nothing, the
provider config is returned unchanged (no apiKey injected)
- The aws-sdk credential chain then resolves credentials at request time
via IMDS/ECS task role/etc. as intended
When AWS env vars ARE present (AWS_ACCESS_KEY_ID, AWS_PROFILE,
AWS_BEARER_TOKEN_BEDROCK), the marker is still injected correctly.
Closes#49891Closes#50699Fixes#54274
* test(bedrock): update resolveBedrockConfigApiKey test for undefined return on empty env
The test previously expected "AWS_PROFILE" when no env vars are set.
Now expects undefined (matching the fix), and adds a separate assertion
that AWS_PROFILE is returned when the env var is actually present.
* fix(bedrock): lock aws-sdk env marker behavior
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(google): restore proxy-safe image generation (#59873)
* fix(ssrf): preserve transport policy without pinned dns
* fix(ssrf): use undici fetch for dispatcher requests
* fix(ssrf): type dispatcher fetch path
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(telegram): enable voice-note transcription in DMs
The preflight transcription condition only triggered for group chats
(isGroup && requireMention), so voice notes sent in direct messages
were never transcribed -- they arrived as raw <media:audio> placeholders.
This regression was introduced when the Telegram channel was moved from
src/telegram/ to extensions/telegram/, losing the fix from c15385fc94.
Widen the condition to fire whenever there is audio and no accompanying
text, regardless of chat type. Group-specific guards (requireMention,
disableAudioPreflight, senderAllowedForAudioPreflight) still apply
only in group contexts.
* fix: restore Telegram DM voice-note transcription (#61008) (thanks @manueltarouca)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix: ensure bypassPermissions on custom CLI backend args
When users override cliBackends.claude-cli.args (e.g. to add --verbose
or change --output-format), the override array replaces the default
entirely. The normalization step only re-added --permission-mode
bypassPermissions when the legacy --dangerously-skip-permissions flag
was present — if neither flag existed, it did nothing.
This causes cron and heartbeat runs to silently fail with "exec denied:
Cron runs cannot wait for interactive exec approval" because the CLI
subprocess launches in interactive permission mode.
Fix: always inject --permission-mode bypassPermissions when no explicit
permission-mode flag is found in the resolved args, regardless of
whether the legacy flag was present.
* test(anthropic): add claude-cli permission normalization coverage
* fix(test-utils): include video generation providers
* fix: preserve claude-cli bypassPermissions on custom args (#61114) (thanks @cathrynlavery)
---------
Co-authored-by: Shadow <hi@shadowing.dev>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(telegram): trim menu descriptions before dropping commands
* fix: note Telegram command menu trimming (#61129) (thanks @neeravmakwana)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* UI: improve mobile chat layout
* change .chat-group-messages min-width: from 604 to 602
* UI: fix chat-group-messages overflow in split-view and mobile layouts
* UI: revert chat.css import order in styles.css and components.css
* UI: simplify mobile chat layout overrides in grouped.css
* ui: move .chat and .chat-thread styles to chat/layout.css
* fix: document mobile chat layout improvements
* fix: improve narrow mobile chat width
---------
Co-authored-by: Altay <altay@uinaf.dev>
Add PR limits section explaining:
- 10 open PRs per author cap
- r: too-many-prs label auto-close mechanism
- How to get exception via #clawtributors Discord
Fixes: #38283
* fix(agents): prefer completion wake over polling
* fix(changelog): note completion wake guidance
* fix(agents): qualify quiet exec completion wake
* fix(agents): qualify disabled exec completion wake
* fix(agents): split process polling from control actions
* fix(cron): suppress NO_REPLY sentinel in direct delivery path
* fix: set deliveryAttempted on filtered NO_REPLY to prevent timer fallback
* fix: mark silent NO_REPLY direct deliveries as delivered
* fix(cron): unify silent direct delivery handling
* fix: suppress NO_REPLY direct cron leaks (#45737) (thanks @openperf)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(agents): handle LiveSessionModelSwitchError in subagent execution
Add retry loop for cross-provider model switches in the subagent
command path, mirroring the existing logic in agent-runner-execution.ts.
- Wrap runWithModelFallback in a while(true) loop inside agentCommandInternal
- Catch LiveSessionModelSwitchError and update provider, model,
fallbackProvider, fallbackModel, providerForAuthProfileValidation,
sessionEntry.authProfileOverride, and storedModelOverride before retrying
- Guard storedModelOverride update: only set when the model genuinely
changed (compared before mutation) or a session override already existed
- Reset lifecycleEnded flag so the retried iteration can emit lifecycle events
- Add comprehensive tests covering retry success, error propagation,
lifecycle reset, auth-profile forwarding, and fallback override state
Fixes#57998
* fix(agents): include provider change in storedModelOverride guard
* fix(agents): validate allowlist and clear stale compaction count on live model switch
* fix(agents): remove broken allowlist guard on live model switch
* fix(agents): address security review — bound retry loop, validate allowlist, redact error in lifecycle events
* fix(agents): restore error observability in lifecycle events using err.message
* fix(agents): sanitize log inputs and shallow-copy sessionEntry on live model switch
* fix(agents): enforce allowlist on empty set and sanitize error message
* fix: handle subagent live model switches (#58178) (thanks @openperf)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(agents): classify generic provider errors for failover
Anthropic returns bare 'An unknown error occurred' during API instability
and OpenRouter wraps upstream failures as 'Provider returned error'. Neither
message was recognized by the failover classifier, so the error surfaced
directly to users instead of triggering the configured fallback chain.
Add both patterns to the serverError classifier so they are classified as
transient server errors (timeout) and trigger model failover.
Closes#49706Closes#45834
* fix(agents): scope unknown-error failover by provider
* docs(changelog): note provider-scoped unknown-error failover
---------
Co-authored-by: Aaron Zhu <aaron@Aarons-MacBook-Air.local>
Co-authored-by: Altay <altay@uinaf.dev>
* fix(cli): route skills list output to stdout when --json is active
runSkillsAction used defaultRuntime.log() which goes through console.log.
The --json preAction hook calls routeLogsToStderr(), redirecting console.log
to stderr. Switch to defaultRuntime.writeStdout() which writes directly to
process.stdout, consistent with how other --json commands (e.g. skills search)
already emit their output.
Fixes#57599
* test(cli): add skills JSON stdout regression coverage
* test(cli): refine skills CLI stream coverage
* fix(cli): add changelog entry for skills JSON stdout fix
---------
Co-authored-by: Aftabbs <aftabbs.wwe@gmail.com>
* fix(google-gemini-cli-auth): fix Gemini CLI OAuth failures on Windows
Two issues prevented Gemini CLI OAuth from working on Windows:
1. resolveGeminiCliDirs: the first candidate `dirname(dirname(resolvedPath))`
can resolve to an unrelated ancestor directory (e.g. the nvm root
`C:\Users\<user>\AppData\Local\nvm`) when gemini is installed via nvm.
The subsequent `findFile` recursive search (depth 10) then picks up an
`oauth2.js` from a completely different package (e.g.
`discord-api-types/payloads/v10/oauth2.js`), which naturally does not
contain Google OAuth credentials, causing silent extraction failure.
Fix: validate candidate directories before including them — only keep
candidates that contain a `package.json` or a `node_modules/@google/
gemini-cli-core` subdirectory.
2. resolvePlatform: returns "WINDOWS" on win32, but Google's loadCodeAssist
API rejects it as an invalid Platform enum value (400 INVALID_ARGUMENT),
just like it rejects "LINUX".
Fix: use "PLATFORM_UNSPECIFIED" for all non-macOS platforms.
* test(google-gemini-cli-auth): keep oauth regressions portable
* chore(changelog): add google gemini cli auth fix note
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
The YAML parser's outer loop was exiting the tasks block when it
encountered 'interval:' or 'prompt:' lines, causing only the first
task to be parsed. Added isTaskField check to skip those lines.
Fixes: #3034790131
- Fix: Pass startedAt into resolveHeartbeatRunPrompt
- Fix: Return proper object instead of null for no-tasks-due
- Fix: Add early return when prompt is null
- Fix: Persist timestamps on successful exits
- Fix YAML parsing to capture interval:/prompt: before breaking
- Record task timestamps AFTER successful execution (not before)
- Initialize task state on first run (handle undefined session)
- Skip API call when no tasks due (return null)
- Use startedAt consistently for due-task filtering
Fixes: #3030568439, #3033833124, #3030570872, #3030568408, #3030570872, #3035434022, #3035434368
The heartbeat task batching feature uses heartbeatTaskState to track
last run times for periodic tasks, but this property was missing
from the SessionEntry type, causing TypeScript compilation errors.
- Add parseHeartbeatTasks() to parse YAML-like task definitions
- Add isTaskDue() to check if task interval has elapsed
- Add heartbeatTaskState to session store for tracking last run times
- Modify resolveHeartbeatRunPrompt to build batched prompts for due tasks
- Update task last run times after successful heartbeat execution
Implements openclaw#29570
* fix(cron): prevent agent default model from overriding cron payload model (#58065)
When a cron job specifies a model override via the Advanced settings,
runWithModelFallback could silently fall back to the agent's configured
primary model. This happened because fallbacksOverride was undefined
when neither payload.fallbacks nor per-agent fallbacks were configured,
causing resolveFallbackCandidates to append the agent primary as a
last-resort candidate. A transient failure on the cron-selected model
(rate limit, model-not-found, etc.) would then succeed on the agent
default, making it appear as if the override was ignored entirely.
Fix: when the cron payload carries an explicit model override, ensure
fallbacksOverride is always a defined array (empty when no fallbacks
are configured) so the agent primary is never silently appended.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: use stricter toEqual([]) assertion for fallbacksOverride
Replace toBeDefined() + toBeInstanceOf(Array) with toEqual([])
to catch regressions where the array unexpectedly gains entries.
Addresses review feedback.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: preserve cron override fallback semantics (#58294)
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
When the gateway client reconnects using a stored device token, it was
defaulting to ["operator.admin"] scopes instead of preserving the
previously authorized scopes from the stored token. This caused the
operator device token to be regenerated without operator.read scope,
breaking status/probe/health commands.
This fix:
1. Loads the stored scopes along with the stored token in selectConnectAuth
2. Uses the stored scopes when reconnecting with a valid device token
3. Falls back to explicitly requested scopes or default admin-only scope
when no stored scopes exist
Fixes#46000
- Remove redundant name === 'MiniMax-M*' condition (already matched by startsWith)
- Use !== undefined guard instead of falsy check in deriveWindowLabelFromTimestamps
- Pass chatRemains directly to deriveWindowLabel when available
- Remove JSDoc comment style to match codebase conventions
- Pick the chat model entry (MiniMax-M*) from model_remains instead of using the first BFS candidate, which could be a speech/video/image model with total_count=0.
- Derive window label from start_time/end_time timestamps when window_hours/window_minutes fields are absent; fixes the hardcoded 5h default for 4h windows.
- Include model name in plan label so users can distinguish free-tier coding-plan quota from paid API balance.
Closes#52335
* fix(google-cli): restore gemini json reporting
* fix(google-cli): fall back to stats when usage is empty
* fix(changelog): note gemini cli cache reporting
MiniMax M2.7 returns reasoning_content in OpenAI-style delta chunks
({delta: {content: "", reasoning_content: "..."}}) when thinking is
active, rather than native Anthropic thinking block SSE events. Pi-ai's
Anthropic provider does not handle this format, causing the model's
internal reasoning to appear as visible chat output.
Add createMinimaxThinkingDisabledWrapper that injects
thinking: {type: "disabled"} into the outgoing payload for any MiniMax
anthropic-messages request where thinking is not already explicitly
configured, preventing the provider from generating reasoning_content
deltas during streaming.
Fixes#55739
The VLM image analysis fetch had no timeout, causing sessions to hang
indefinitely when the MiniMax API is slow or unresponsive. Other
vision/model API calls in the codebase already use timeouts. Adds
AbortSignal.timeout(60_000) consistent with image upload workloads.
Fixes#54139
When cron tasks or subagents use browser automation, the browser
processes were not cleaned up after the task completed. This caused
orphaned Chrome processes (PPID=1) to accumulate over time.
Root cause: closeTrackedBrowserTabsForSessions was only called during
session-reset/session-delete (via ensureSessionRuntimeCleanup), but
isolated cron runs and subagent completions never triggered these paths.
Fix: Add browser tab cleanup in two places:
1. server-cron.ts: wrap runCronIsolatedAgentTurn in try/finally to
ensure browser tabs are cleaned up after every cron run.
2. subagent-registry-lifecycle.ts: call closeTrackedBrowserTabsForSessions
when a subagent run completes, before the announce cleanup flow.
Both cleanup calls are best-effort (caught errors) so they never mask
the actual task result or break the completion flow.
Fixes#60104
When `browser stop` is called for an `attachOnly` or remote CDP
profile, `profileState.running` is null (no process was launched), so
`stopRunningBrowser()` returned early without closing the Playwright
CDP connection. This left emulation overrides (prefers-color-scheme,
viewport, etc.) permanently applied until a full gateway restart.
Now call `closePlaywrightBrowserConnectionForProfile()` before
returning for attachOnly and remote CDP profiles, matching the cleanup
behavior already present in `resetProfile()`. Regular profiles that
were never started still return `{ stopped: false }`.
Fixes#60095
Add native MiniMax Search integration via their Coding Plan search API
(POST /v1/coding_plan/search). This brings MiniMax in line with Brave,
Kimi, Grok, Gemini, and other providers that already have bundled web
search support.
- Implement WebSearchProviderPlugin with caching, credential resolution,
and trusted endpoint wrapping
- Support both global (api.minimax.io) and CN (api.minimaxi.com)
endpoints, inferred from explicit region config, model provider base
URL, or minimax-portal OAuth base URL
- Prefer MINIMAX_CODE_PLAN_KEY over MINIMAX_API_KEY in credential
fallback, matching existing repo precedence
- Accept SecretRef objects for webSearch.apiKey (type: [string, object])
- Register in bundled registry, provider-id compat map, and fast-path
plugin id list with full alignment test coverage
- Add unit tests for endpoint/region resolution and edge cases
Closes#47927
Related #11399
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When the liveModelSwitchPending flag is set but the current model already
matches the persisted selection (e.g. the switch was applied as an override
and the current attempt is already using the new model), the flag is now
consumed eagerly via a fire-and-forget clearLiveModelSwitchPending() call.
Without this, the stale flag could persist across fallback iterations and
later cause a spurious LiveSessionModelSwitchError when the model rotates
to a fallback candidate that differs from the persisted selection.
Also expands JSDoc on shouldSwitchToLiveModel to document the stale-flag
clearing and deferral semantics.
Replace the ambiguous comparison-based approach (hasDifferentLiveSessionModelSelection
+ in-memory map EMBEDDED_RUN_MODEL_SWITCH_REQUESTS) with a persisted
`liveModelSwitchPending` flag on SessionEntry.
The root cause: the in-memory map was never populated in production because
requestLiveSessionModelSwitch() was removed in commit 622b91d04e and replaced
with refreshQueuedFollowupSession(). This left the comparison-based detection
as the only path, which could not distinguish user-initiated model switches
(via /model command) from system-initiated fallback rotations.
The fix:
- Add `liveModelSwitchPending?: boolean` to SessionEntry (persisted)
- Set the flag to true ONLY when /model command applies a model override
- New `shouldSwitchToLiveModel()` checks the flag + model mismatch together
- New `clearLiveModelSwitchPending()` resets the flag after consumption
- Replace throw-site logic in run.ts to use the new flag-based functions
- Remove orphaned resolveCurrentLiveSelection helper
Only the /model command sets this flag, so system-initiated fallback rotations
are never mistaken for user-initiated model switches. This restores the
live-switch-during-active-run feature that was accidentally broken.
Fixes#57857, #57760, #58137
When auth choice explicitly sets a preferred provider (e.g., volcengine-api-key or byteplus-api-key), the model picker should always filter by that provider. Previously, it relied on providerIds.includes(preferredProvider), which could be false if the catalog hadn't loaded that provider's models yet due to a race condition between auth choice setup and catalog loading.
This ensures that selecting a provider via auth choice consistently filters the model list to only that provider's models, rather than showing all providers.
MiniMax's usage_percent / usagePercent fields report the *remaining* quota
as a percentage, not the consumed quota. When count fields (prompt_limit /
prompt_remain) are also present, fromCounts already computed the correct
usedPercent and the inverted value was silently ignored. But when only
usage_percent is returned (no count fields), the code treated it as a
used-percent and passed it through unchanged, causing the menu bar to show
"2% left" instead of "98% left".
Move usage_percent and usagePercent from PERCENT_KEYS to a new
REMAINING_PERCENT_KEYS array. deriveUsedPercent now inverts remaining-percent
values to obtain usedPercent, matching the behaviour already validated by the
existing "prefers count-based usage when percent looks inverted" test. Count-
based fromCounts still takes priority over both key groups.
Fixes#60193
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When using custom providers like LM Studio, Ollama, or DashScope,
token counts in /status show as 0 because the agent meta store
does not always have usage data populated for these providers.
Fix: set includeTranscriptUsage: true in both /status command and
the session_status tool. This enables the existing fallback path
that reads usage from the session transcript JSONL file when the
meta store has zero/missing token counts.
The merge logic already guards against overwriting valid data:
- totalTokens: only updated when zero or transcript value is larger
- inputTokens/outputTokens: only filled when zero/missing
- model/contextTokens: only filled when missing
Fixes#54995
When sessions report an already-qualified model id (e.g. ollama/qwen3:30b),
resolveServerChatModelValue was re-qualifying it using modelProvider,
producing incorrect values like openai-codex/qwen3:30b.
Preserve already-qualified model refs as-is before applying provider prefix.
Adds test coverage for qualified model preservation.
Fixes#49839
The Kimi Coding plugin registers with provider ID `kimi` and default
model ID `kimi-code`, making the correct model ref `kimi/kimi-code`.
The docs incorrectly showed `kimi-coding/k2p5` as the provider/model
ref. This is confusing because `kimi-coding` is only a plugin alias,
not the actual provider ID used in config.
Updated all references in:
- docs/concepts/model-providers.md
- docs/providers/moonshot.md
- docs/zh-CN/concepts/model-providers.md
- docs/zh-CN/providers/moonshot.md
Switch DEFAULT_MINIMAX_TTS_BASE_URL from api.minimaxi.com (CN) to
api.minimax.io (global) so international API keys work out of the box.
Add vol and pitch to resolveTalkOverrides for parity with resolveTalkConfig.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add MiniMax as a fourth TTS provider alongside OpenAI, ElevenLabs, and
Microsoft. Registers a SpeechProviderPlugin in the existing minimax
extension with config resolution, directive parsing, and Talk Mode
support. Hex-encoded audio response from the T2A v2 API is decoded to
MP3.
Closes#52720
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove DEFAULT_KIMI_MODEL (moonshot-v1-128k) and align resolveKimiModel
fallback to DEFAULT_KIMI_SEARCH_MODEL (kimi-k2.5). The legacy model
does not support the $web_search builtin_function tool, so env-var-only
users without a configured model would hit the original bug.
Config and Plugin SDK drift detection now compares SHA-256 hashes instead
of full JSON content. The .sha256 files (6 lines total) are tracked in git;
the full JSON baselines are gitignored and generated locally for inspection.
Same CI guarantee, zero repo churn on schema changes.
CLI connections with valid shared auth (token/password) now bypass device
pairing, fixing the chicken-and-egg problem where Docker CLI commands fail
with 'pairing required' (1008) despite sharing the gateway's network
namespace and auth token.
The existing shouldSkipBackendSelfPairing only matched gateway-client/backend
mode. CLI connections use cli/cli mode and were excluded. Additionally,
isLocalDirectRequest produces false negatives in Docker (host networking,
network_mode sharing) even when remoteAddress is 127.0.0.1, so CLI connections
with valid shared auth skip the locality check entirely — the token is the
trust anchor.
Closes#55067
Related: #12210, #23471, #30740
* fix(matrix): migrate room allow aliases to enabled
* test(matrix): keep migration coverage on the channel seam
* chore(config): refresh baselines after matrix alias cleanup
* memory-core: add dreaming promotion flow with weighted thresholds
* docs(memory): mark dreaming as experimental
* memory-core: address dreaming promotion review feedback
* memory-core: harden short-term promotion concurrency
* acpx: make abort-process test timer-independent
* memory-core: simplify dreaming config with mode presets
* memory-core: add /dreaming command and tighten recall tracking
* ui: add Dreams tab with sleeping lobster animation
Adds a new Dreams tab to the gateway UI under the Agent group.
The tab is gated behind the memory-core dreaming config — it only
appears in the sidebar when dreaming.mode is not 'off'.
Features:
- Sleeping vector lobster with breathing animation
- Floating Z's, twinkling starfield, moon glow
- Rotating dream phrase bubble (17 whimsical phrases)
- Memory stats bar (short-term, long-term, promoted)
- Active/idle visual states
- 14 unit tests
* plugins: fix --json stdout pollution from hook runner log
The hook runner initialization message was using log.info() which
writes to stdout via console.log, breaking JSON.parse() in the
Docker smoke test for 'openclaw plugins list --json'. Downgrade to
log.debug() so it only appears when debugging is enabled.
* ui: keep Dreams tab visible when dreaming is off
* tests: fix contracts and stabilize extension shards
* memory-core: harden dreaming recall persistence and locking
* fix: stabilize dreaming PR gates (#60569) (thanks @vignesh07)
* test: fix rebase drift in telegram and plugin guards
* fix(daemon): preserve Windows Task Scheduler settings on reinstall and exit early on failed restart
* fix(daemon): add test coverage for Create/Change paths, fix early exit grace period
* fix(daemon): fix startup-fallback tests for new isRegisteredScheduledTask call
* fix(daemon): report early restart failure accurately
* fix: preserve Windows scheduled task restart/install behavior (#59335) (thanks @tmimmanuel)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix: add enabledByDefault to groq and deepgram media plugin manifests
The groq and deepgram plugin manifests were missing the
enabledByDefault: true flag. Without this flag, both plugins are
treated as bundled-but-disabled-by-default, so resolveRuntimePluginRegistry
loads without them. When buildProviderRegistry later needs to resolve
audio providers, the active registry is used first (short-circuits
the compat path in resolvePluginCapabilityProviders), leaving groq
and deepgram absent from the registry.
This caused 'Media provider not available: groq' errors when users
configured tools.media.audio.models with groq or deepgram, even
with GROQ_API_KEY / DEEPGRAM_API_KEY set correctly.
The fix mirrors the pattern used by other audio/media-only providers
such as mistral, which already has enabledByDefault: true.
Fixes#59875
* fix: enable groq and deepgram bundled media providers by default (#59982) (thanks @yxjsxy)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
- Skip sensitive fields with a note directing users to openclaw config set
or the Web UI (WizardPrompter has no masked input)
- Clear number fields to undefined when input is empty instead of storing 0
- Allow clearing array fields to undefined via empty input
pruneProcessedHistoryImages was stripping image blocks from every
already-answered user turn on each run. Turn N sends image bytes → provider
caches the prefix. Turn N+1 replaces image with text marker → bytes diverge
at that message → cache miss from there onward.
Now only prune images older than 3 assistant turns. Recent history stays
byte-identical so the cached prefix survives, while legacy sessions with
persisted image payloads still get cleaned up.
Wire uiHints from plugin manifests into the TUI wizard so sandbox/tool
plugins get interactive config prompts during openclaw onboard (manual
flow) and openclaw configure --section plugins.
- Add setup.plugin-config.ts: discovers plugins with non-advanced uiHints,
generates type-aware prompts (enum→select, boolean→confirm, array→csv,
string/number→text) from jsonSchema + uiHints metadata.
- Onboard: new step after Skills, before Hooks (skipped in QuickStart).
Only shows plugins with unconfigured fields.
- Configure: new 'plugins' section in the section menu. Shows all
configurable plugins with configured/total field counts.
Closes#60030
* fix(cache): compact newest tool results first to preserve prompt cache prefix
compactExistingToolResultsInPlace iterated front-to-back, replacing the
oldest tool results with placeholders when context exceeded 75%. This
rewrote messages[k] for small k, invalidating the provider prompt cache
from that point onward on every subsequent turn.
Reverse the loop to compact newest-first. The cached prefix stays intact;
the tradeoff is the model loses recent tool output instead of old, which
is acceptable since this guard only fires as an emergency measure past
the 75% threshold.
* fix(cache): compact newest tool results first to preserve prompt cache prefix (#58036) Thanks @bcherny
---------
Co-authored-by: George Zhang <georgezhangtj97@gmail.com>
auth_permanent errors (e.g. API_KEY_INVALID) can be caused by transient
provider outages rather than genuinely revoked credentials. Previously
these used the same 5h-24h billing backoff, which left providers disabled
long after the upstream issue resolved.
Introduce separate authPermanentBackoffMinutes (default: 10) and
authPermanentMaxMinutes (default: 60) config options so auth_permanent
failures recover in minutes rather than hours.
Fixes#56838
Preserve the !author.bot || sender.isPluralKit guard when short-circuiting
wasMentioned on mentionedEveryone, so bot relay messages don't spuriously
trigger mention-gate logic. Add test coverage for the wasMentioned path.
Forward mediaReadFile and mediaAccess through the sendMessageDiscord shortcut
in sendDiscordComponentMessage, so local-file media works correctly when
falling back to classic Discord messages. Add test coverage.
* fix(telegram): enable HTML formatting for model switch messages
The model switch confirmation message was displaying raw Markdown
(**text**) instead of bold formatting because parse_mode was not set.
Changes:
- Add optional extra parameter to editMessageWithButtons for parse_mode
- Change format from Markdown ** to HTML <b> tags
- Pass parse_mode: 'HTML' when editing model switch message
Fixes the issue where model names appeared as **provider/model**
instead of bold text in Telegram.
* fix(telegram): escape HTML entities in model switch confirmation
Add defensive `escapeHtml` helper to sanitize `selection.provider`
and `selection.model` before interpolating them into the HTML
callback message. This prevents potential API rejection (HTTP 400)
if future provider or model names contain `<`, `>`, or `&`.
Addresses review feedback on unescaped HTML interpolation.
* test(telegram): cover HTML model switch confirmation
---------
Co-authored-by: Frank Yang <frank.ekn@gmail.com>
The implementation fix propagates the hydrated cfg to reactMessageDiscord
and removeReactionDiscord. Update test assertions to expect the cfg
property in the options argument using expect.objectContaining to handle
the dynamic session store path.
When extracting `reactMessageDiscord`, it defaulted to reading the raw config (which contains `SecretRef`s) if a hydrated `cfg` was omitted. We now pass the pre-resolved `cfg` context into the reaction options so the plugin SDK resolves the token via memory rather than the raw file.
resolveDiscordThreadStarter only checked content and embeds, returning
null for forwarded messages where the text lives in message_snapshots.
Add a local resolveStarterForwardedText helper that extracts text
directly from the message_snapshots array on the REST response object.
This avoids fragile type casts and keeps the change self-contained
within threading.ts.
Fixes#60129
Fixes#60158
MCP tools with parameter-free schemas may return truly empty objects
`{}` without a `type` field. The existing normalization handled
`{ type: "object" }` → `{ type: "object", properties: {} }` but
missed the truly empty case.
OpenAI gpt-5.4 rejects tool schemas without `type: "object"` and
`properties`, causing HTTP 400 errors:
```
Invalid schema for function 'flux-mcp__get_flux_instance':
In context=(), object schema missing properties.
```
This change catches empty schemas (no type, no properties, no unions)
before the final pass-through and converts them to the required format.
Added test case for parameter-free MCP tool schemas.
* feat(gateway): add skills.search and skills.detail RPC methods
Expose ClawHub search and detail capabilities through the Gateway protocol,
enabling desktop/web clients to browse and inspect skills from the registry.
New RPCs:
- skills.search: search ClawHub skills by query with optional limit
- skills.detail: fetch full detail for a single skill by slug
Both methods delegate to existing agent-layer functions
(searchSkillsFromClawHub, fetchSkillDetailFromClawHub) which wrap
the ClawHub HTTP client. No new external dependencies.
Signed-off-by: samzong <samzong.lu@gmail.com>
* feat(skills): add ClawHub skill search and detail in Control UI
Add skills.search and skills.detail Gateway RPC methods with typed
protocol schemas, AJV validators, and handler implementations. Wire
the new RPCs into the Control UI Skills panel with a debounced search
input, results list, detail dialog, and one-click install from ClawHub.
Gateway:
- SkillsSearchParams/ResultSchema and SkillsDetailParams/ResultSchema
- Handler calls searchClawHubSkills and fetchClawHubSkillDetail directly
- Remove zero-logic fetchSkillDetailFromClawHub wrapper
- 9 handler tests including boundary validation
Control UI:
- searchClawHub, loadClawHubDetail, installFromClawHub controllers
- 300ms debounced search input to avoid 429 rate limits
- Dedicated install busy state (clawhubInstallSlug) with success/error feedback
- Install buttons disabled during install with progress text
- Detail dialog with owner, version, changelog, platform metadata
Part of #43301
Signed-off-by: samzong <samzong.lu@gmail.com>
* fix(skills): guard search and detail responses against stale writes
Signed-off-by: samzong <samzong.lu@gmail.com>
* fix(skills): reset loading flags on query clear and detail close
Signed-off-by: samzong <samzong.lu@gmail.com>
* fix(gateway): register skills.search/detail in read scope and method list
Add skills.search and skills.detail to the operator READ scope group
and the server methods list. Without this, unclassified methods default
to operator.admin, blocking read-only operator sessions.
Also guard the detail loading reset in the finally block by the active
slug to prevent a transient flash when rapidly switching skills.
Signed-off-by: samzong <samzong.lu@gmail.com>
* fix(skills): guard search loading reset by active query
Signed-off-by: samzong <samzong.lu@gmail.com>
* test: cover ClawHub skills UI flow
* fix: clear stale ClawHub search results
---------
Signed-off-by: samzong <samzong.lu@gmail.com>
Co-authored-by: Frank Yang <frank.ekn@gmail.com>
resolveAgentConfig().subagents.allowAgents reads only the per-agent
entry, never falling back to agents.defaults.subagents.allowAgents.
Other subagent defaults like runTimeoutSeconds correctly read from
cfg.agents.defaults.subagents — allowAgents was missed.
Root cause: subagent-spawn.ts:463 and agents-list-tool.ts:49 both
use resolveAgentConfig() which returns only per-agent config without
defaults merging. The same pattern is already established at
subagent-spawn.ts:403 for runTimeoutSeconds.
Fix: add cfg.agents.defaults.subagents.allowAgents as fallback when
per-agent entry doesn't specify allowAgents. Both call sites fixed.
Closes#59938
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: HCL <chenglunhu@gmail.com>
The test previously asserted that a valid snapshot without gateway.mode
blocks startup. After defaulting gateway.mode to 'local' when unset,
the gateway should start successfully in this scenario — update the
test to verify the new expected behavior.
Co-authored-by: Brad Groux <bradgroux@users.noreply.github.com>
After v2026.3.24 introduced a gateway.mode guard, startup fails on
Windows (and other platforms) when the config file exists but doesn't
contain an explicit gateway.mode value. This happens after 'openclaw
onboard' writes a minimal config without gateway settings.
Default to 'local' when the mode is unset, restoring pre-3.24 behavior
where the gateway started without requiring an explicit mode.
Fixes#54801
Co-authored-by: Brad Groux <bradgroux@users.noreply.github.com>
* fix: import CHANNEL_IDS from leaf module to avoid TDZ on init (#48832)
schema.ts and validation.ts imported CHANNEL_IDS from channels/registry.js,
which re-exports from channels/ids.js but also imports plugins/runtime.js.
When the bundler resolves this dependency graph, the re-exported CHANNEL_IDS
can be undefined at the point config/validation.ts evaluates (temporal dead
zone), causing 'CHANNEL_IDS is not iterable' on startup.
Fix: import CHANNEL_IDS directly from channels/ids.js (the leaf module with
zero heavy dependencies) and normalizeChatChannelId from channels/chat-meta.js.
Fixes#48832
* fix: improve WS handshake reliability on slow-startup environments (#48736)
On Windows with large dist bundles (46MB/639 files), heavy synchronous
module loading blocks the event loop during CLI startup, preventing
timely processing of the connect.challenge frame and causing ~80%
handshake timeout failures.
Changes:
- Yield event loop (setImmediate) before starting WS connection in
callGateway to let pending I/O drain after heavy module loading
- Add OPENCLAW_CONNECT_CHALLENGE_TIMEOUT_MS env var override for
client-side connect challenge timeout (server already has
OPENCLAW_HANDSHAKE_TIMEOUT_MS)
- Include diagnostic timing in challenge timeout error messages
(elapsed vs limit) for easier debugging
- Add tests for env var override and resolution logic
---------
Co-authored-by: Brad Groux <bradgroux@users.noreply.github.com>
schema.ts and validation.ts imported CHANNEL_IDS from channels/registry.js,
which re-exports from channels/ids.js but also imports plugins/runtime.js.
When the bundler resolves this dependency graph, the re-exported CHANNEL_IDS
can be undefined at the point config/validation.ts evaluates (temporal dead
zone), causing 'CHANNEL_IDS is not iterable' on startup.
Fix: import CHANNEL_IDS directly from channels/ids.js (the leaf module with
zero heavy dependencies) and normalizeChatChannelId from channels/chat-meta.js.
Fixes#48832
Co-authored-by: Brad Groux <bradgroux@users.noreply.github.com>
* "fix(telegram): surface media placeholder and file_id when download fails"
* fix: unify telegram media placeholder selection
* fix: preserve telegram media context on captioned download failures (#59948) (thanks @v1p0r)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Fix three bugs preventing inline image downloads in Teams 1:1 DM chats: wrong conversation ID format for Graph API, missing media URL extraction, and incorrect content type detection.
Fixes#24797
Thanks @Ted-developer
* test(telegram): add URL construction tests for custom apiRoot
Add comprehensive test cases to verify that file download URLs are correctly
constructed when using a custom apiRoot configuration for local Bot API servers.
Tests validate:
- Document downloads use the custom apiRoot in the constructed URL
- Sticker downloads use the custom apiRoot in the constructed URL
- SSRF policy correctly includes the custom hostname
This ensures issue #59512 (Telegram file downloads with local Bot API) is
properly covered by regression tests.
* refactor(telegram): improve media resolution code quality
Apply KISS and YAGNI principles to reduce code duplication and improve maintainability:
1. Extract media metadata resolution
- Consolidate resolveMediaFileRef(), resolveTelegramFileName(), and
resolveTelegramMimeType() into single resolveMediaMetadata() function
- Returns typed MediaMetadata object with fileRef, fileName, mimeType
- Reduces duplication and improves readability
2. Add logging for apiRoot parsing failures
- Log when custom apiRoot URL parsing fails in buildTelegramMediaSsrfPolicy()
- Helps debug configuration issues with local Bot API servers
3. Fix missing apiRoot in buffered messages
- Add telegramCfg.apiRoot parameter to resolveMedia() calls in
bot-handlers.buffers.ts (lines 150-159, 189)
- Ensures reply media in buffered contexts respects custom apiRoot config
- Fixes inconsistency where runtime handler passed apiRoot but buffers didn't
These changes improve code quality while maintaining backward compatibility and
ensuring issue #59512 (Telegram file downloads with local Bot API) works correctly
in all contexts.
* fix(telegram): resolve bot review issues
Address critical issues identified by Greptile code review:
1. Define telegramCfg in bot-handlers.buffers.ts
- Extract telegramCfg from cfg.channels?.telegram
- Fixes ReferenceError when accessing telegramCfg.apiRoot
- Ensures buffered message handlers can access apiRoot configuration
2. Restore type safety for MediaMetadata.fileRef
- Change from 'unknown' to proper union type
- Preserves type information for downstream file_id access
- Prevents TypeScript strict mode compilation errors
These fixes ensure the PR compiles correctly and handles buffered
media downloads with custom apiRoot configuration.
* fix(telegram): use optional chaining for telegramCfg.apiRoot
TypeScript strict mode requires optional chaining when accessing
properties on potentially undefined objects. Changed telegramCfg.apiRoot
to telegramCfg?.apiRoot to handle cases where telegramCfg is undefined.
Fixes TypeScript errors:
- TS18048: 'telegramCfg' is possibly 'undefined' (line 160)
- TS18048: 'telegramCfg' is possibly 'undefined' (line 191)
* fix(telegram): add missing optional chaining on line 191
Complete the fix for telegramCfg optional chaining.
Previous commit only fixed line 160, but line 191 also needs
the same fix to prevent TS18048 error.
* fix: cover buffered Telegram apiRoot downloads (#59544) (thanks @SARAMALI15792)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Fix stale lock files from crashed gateway processes blocking new invocations on Windows/macOS. Detect PID recycling to avoid false positive lock conflicts, and add startup progress indicator.
Thanks @TonyDerek-dot
When a thread reply's turn context is revoked and falls back to proactive messaging, the normalized conversation ID lost the thread suffix, causing replies to land in the channel root instead of the original thread.
Reconstructs the threaded conversation ID (`;messageid=<activityId>`) for channel conversations in the proactive fallback path, while correctly leaving group chat conversations flat.
Fixes#27189
Thanks @hyojin
getActionAvailabilityState in createApproverRestrictedNativeApprovalAdapter
was gating on both hasApprovers AND isNativeDeliveryEnabled, causing
Telegram exec approvals to report "not allowed" when
channels.telegram.execApprovals.target was configured but
execApprovals.enabled was not explicitly true. The availability check
should only depend on whether approvers exist; native delivery mode is
a routing concern handled downstream.
After the node early-return, narrow workdir back to string via
resolvedWorkdir for gateway/sandbox paths. Update
buildExecApprovalPendingToolResult and buildApprovalPendingMessage
to accept string | undefined for cwd since node execution may omit it.
When exec runs with host=node and no explicit cwd is provided, the
gateway was injecting its own process.cwd() as the default working
directory. In cross-platform setups (e.g. Linux gateway + Windows node),
this gateway-local path does not exist on the node, causing
"SYSTEM_RUN_DENIED: approval requires an existing canonical cwd".
This change detects when no explicit workdir was provided (neither via
the tool call params.workdir nor via agent defaults.cwd) and passes
undefined instead of the gateway cwd. This lets the remote node use its
own default working directory.
Changes:
- bash-tools.exec.ts: Track whether workdir was explicitly provided;
when host=node and no explicit workdir, pass undefined instead of
gateway process.cwd()
- bash-tools.exec-host-node.ts: Accept workdir as string | undefined;
only send cwd to system.run.prepare when defined
- bash-tools.exec-approval-request.ts: Accept workdir as
string | undefined in HostExecApprovalParams
Fixes#58934
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fixes#52576 — the accent/theme color was not applied to the Settings
page title, breadcrumb, section headings, or theme card labels. Changed
four CSS rules from var(--text-strong) to var(--accent) so they reflect
the selected theme consistently.
* fix(google): separate oauth state from pkce verifier
* fix(google): drop unused oauth callback state arg
* docs(changelog): add #59116 google oauth state fix
---------
Co-authored-by: Jacob Tomlinson <jtomlinson@nvidia.com>
Install a Playwright route handler before `page.goto()` so navigations
to private/internal IPs are intercepted and aborted mid-redirect instead
of being checked post-hoc after the request already reached the internal
host. Blocked targets are permanently marked and rejected for subsequent
tool calls.
Thanks @pgondhi987
When a skill constructs a compound command via a shell wrapper
(e.g. `sh -c "cat SKILL.md && gog-wrapper calendar events"`),
the allowlist check was comparing `/bin/sh` instead of the actual
target binaries, causing the entire command to be silently rejected.
This adds recursive inline command evaluation that:
- Detects chain operators (&&, ||, ;) in the -c payload
- Parses each sub-command independently via analyzeShellCommand
- Evaluates every sub-command against the allowlist
- Preserves per-sub-command segmentSatisfiedBy for accurate tracking
- Limits recursion depth to 3 to prevent abuse
- Skips recursion on Windows (no POSIX shell semantics)
Closes#57377
Co-authored-by: WZBbiao <wangzhenbiao326@gmail.com>
Reject invalid diffs viewerBaseUrl values during manifest config validation,
not later during plugin registration.
Keep runtime normalization intact and add manifest-level coverage so bad
protocols and query/hash values fail fast.
Keeps untrusted workspace channel metadata from overriding setup/login resolution for built-in channels. Workspace channel entries are only eligible during setup when the plugin is already explicitly trusted in config.
- Track discovered origin on channel catalog entries and add a setup-time catalog lookup that excludes workspace discoveries when needed
- Add resolver regression coverage for untrusted shadowing and trusted workspace overrides
Thanks @mappel-nv
* fix(agents): pin subagent gateway calls to admin scope to prevent scope-upgrade pairing failures
callSubagentGateway forwards params to callGateway without explicit scopes,
so callGatewayLeastPrivilege negotiates the minimum scope per method
independently. The first connection pairs the device at a lower tier and
every subsequent higher-tier call triggers a scope-upgrade handshake that
headless gateway-client connections cannot complete interactively
(close 1008 "pairing required").
Pin callSubagentGateway to operator.admin so the device is paired at the
ceiling scope on the very first (silent, local-loopback) handshake, avoiding
any subsequent scope-upgrade negotiation entirely.
Fixes#59428
* fix: pin admin-only subagent gateway scopes (#59555) (thanks @openperf)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(doc):update wecom doc and qq
doc
* Update CHANGELOG with recent changes and enhancements
Added various updates including new features, improvements, and documentation changes across multiple components.
* fix(qqbot): lazy-load silk-wasm to avoid hard failure when package is missing
Replace the static top-level import with a cached dynamic import helper.
If silk-wasm is unavailable the plugin loads normally; voice encode/decode
degrades gracefully instead of crashing the module at load time.
* fix(qqbot): store in-flight Promise in loadSilkWasm to prevent duplicate imports
Concurrent cold-start calls to loadSilkWasm() before the first import()
resolves would each fire a separate dynamic import. Storing the Promise
instead of the resolved value (matching the detectFfmpeg pattern in
platform.ts) ensures all concurrent callers await the same import,
keeping the codebase consistent and avoiding redundant parallel loads.
* QQBot: add changelog for silk-wasm lazy load
* QQBot: move changelog entry for PR #58829
---------
Co-authored-by: sliverp <870080352@qq.com>
Co-authored-by: Sliverp <38134380+sliverp@users.noreply.github.com>
Relative paths like "inbox/receipt.png" were resolved against
process.cwd() instead of the agent's workspaceDir, causing the
allowlist check to fail with "Local media path is not under an
allowed directory". This matches how the read tool already behaves.
Fixes#57215
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(gateway ): allow silent role upgrades for local loopback clients
When a local loopback client connects with a role not covered by
existing device tokens, listEffectivePairedDeviceRoles incorrectly
returns an empty role set for devices whose tokens map is an empty
object. This triggers a role-upgrade pairing request that
shouldAllowSilentLocalPairing rejects because it does not recognise
the role-upgrade reason.
Fix listEffectivePairedDeviceRoles to fall back to legacy role fields
when the tokens map has no entries, and extend
shouldAllowSilentLocalPairing to accept role-upgrade for local
clients.
Fixes#59045
* fix: restore local loopback role upgrades (#59092) (thanks @openperf)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Replaces String(err) with the existing formatUnknownError() utility across
the msteams extension to prevent [object Object] appearing in error logs
when non-Error objects are caught (e.g., Axios errors, Bot Framework SDK
error objects).
Fixes#53910
thanks @bradgroux
When a streamed response exceeds TEAMS_MAX_CHARS, the stream sets streamFailed=true and finalizes. Previously, hasContent returned false when streamFailed was true, causing preparePayload to pass through the full payload for block delivery, duplicating already-streamed text.
Now tracks streamed length and strips the already-delivered prefix from fallback payloads.
Fixes#58601
thanks @bradgroux
* fix(bluebubbles): add enrichGroupParticipantsFromContacts to core Zod schema
The field was added to the extension config schema in #54984 but not
synced to the core strict Zod validator, causing config validation to
reject the key at startup with 'Unrecognized key'.
* test(config): add BlueBubbles schema regression coverage
* fix(bluebubbles): accept enrichGroupParticipantsFromContacts config
---------
Co-authored-by: Chris Zhang <chris@ChrisdeMac-mini.local>
Co-authored-by: Altay <altay@uinaf.dev>
Add provider-specific error patterns for AWS Bedrock, Ollama, Mistral,
Cohere, DeepSeek, Together AI, and Cloudflare Workers AI. These providers
return errors in non-standard formats that the generic classifiers miss,
causing incorrect failover behavior (e.g., context overflow misclassified
as format error, ThrottlingException not recognized as rate limit).
Wire provider patterns into isContextOverflowError() and
classifyFailoverReason() as catch-all layers after generic classifiers.
* feat(gateway): make chat history max chars configurable
* fix(gateway): address review feedback
* docs(changelog): note configurable chat history limits
Slash commands like /model and /new were silently ignored when the inbound
message body included metadata prefix blocks (Conversation info, Sender info,
timestamps) injected by buildInboundUserContextPrefix. The command detection
functions (hasControlCommand, isControlCommandMessage, parseSendPolicyCommand)
now call stripInboundMetadata before normalizeCommandBody so embedded slash
commands are correctly recognized.
* fix: escalate to model fallback after rate-limit profile rotation cap
Per-model rate limits (e.g. Anthropic Sonnet-only quotas) are not
relieved by rotating auth profiles — if all profiles share the same
model quota, cycling between them loops forever without falling back
to the next model in the configured fallbacks chain.
Apply the same rotation-cap pattern introduced for overloaded_error
(#58348) to rate_limit errors:
- Add `rateLimitedProfileRotations` to auth.cooldowns config (default: 1)
- After N profile rotations on a rate_limit error, throw FailoverError
to trigger cross-provider model fallback
- Add `resolveRateLimitProfileRotationLimit` helper following the same
pattern as `resolveOverloadProfileRotationLimit`
Fixes#58572
* fix: cap prompt-side rate-limit failover (#58707) (thanks @Forgely3D)
* fix: restore latest-main gates for #58707
---------
Co-authored-by: Ember (Forgely3D) <ember@forgely.co>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(memory): prefer --mask over --glob for qmd collection pattern flag
qmd 2.0.1 silently ignores the --glob flag when creating collections,
causing all patterns (e.g. MEMORY.md, memory.md) to fall back to the
default **/*.md glob. This leads to collection conflicts when multiple
collections target the same workspace directory with different patterns.
The existing flag negotiation logic in addCollection() tries --glob
first (when collectionPatternFlag is null), and since qmd accepts the
flag without error, OpenClaw never falls back to --mask. The result is
that memory-root-{agent} gets created with **/*.md instead of MEMORY.md,
and memory-alt-{agent} fails with a duplicate path+pattern conflict.
Fix: default collectionPatternFlag to '--mask' so the working flag is
tried first. The fallback to --glob is preserved for older qmd versions
that may not support --mask.
* docs(changelog): note qmd collection flag fix
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(telegram): preserve content type for local Bot API media files
* fix: preserve Telegram local Bot API MIME types (#54603) (thanks @jzakirov)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Remove stray `%20` (URL-encoded space) from the StreamWeasels username-to-ID
converter link, which caused a 404 when clicked.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fixes#58409 - Heartbeat system causes silent session reset leading to user data loss.
The issue occurred when automated system events (heartbeat, cron-event, exec-event)
triggered the session initialization logic, which evaluated session freshness based on
idle/daily reset policies. Stale sessions were reset, causing complete context loss.
Changes:
- Detect system event providers (heartbeat, cron-event, exec-event) in initSessionState
- Force freshEntry=true for system events to skip reset policy evaluation
- Add comprehensive test coverage for heartbeat no-reset behavior
This ensures automated check-ins preserve session continuity and never cause
accidental data loss.
* routing: support wildcard peer bindings (peer.id="*") for multi-agent routing
Bindings with `peer: { kind: "direct", id: "*" }` were treated as a literal
peer ID "*" instead of a wildcard. This caused the binding to be indexed
exclusively in the byPeer map under key "direct:*", which never matches
actual incoming peer IDs like "direct:12345678". The binding silently fell
through to the default agent ("main"), breaking multi-agent setups that use
wildcard peer constraints to route all DMs on a named account to a specific
agent.
Add a "wildcard-kind" peer constraint state that restricts on chat type
(direct/group/channel) without requiring an exact peer ID match. Wildcard
peer bindings now fall through to the byAccount/byChannel index tiers and
correctly match via matchesBindingScope with kind-only filtering.
Resolves#58546
Made-with: Cursor
* routing: add dedicated binding.peer.wildcard tier for clarity
Address Greptile feedback: wildcard-peer bindings now report
matchedBy: "binding.peer.wildcard" instead of "binding.account",
making logs/debugging clearer for operators.
- Add byPeerWildcard index bucket
- Add binding.peer.wildcard tier between peer.parent and guild+roles
- Update tests to expect the new matchedBy value
Made-with: Cursor
The 10-second connection timeout in OpenAIRealtimeSTTSession.doConnect()
was never cleared on success or teardown, leaking a timer on every
connection and accumulating stale timers across reconnect cycles.
Store the timeout handle and clear it in both the open handler and
close(), matching the existing clearTimeout pattern in
waitForTranscript().
Co-authored-by: Sharoon Sharif <ssharif@Hosanna.local>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: prevent infinite retry loop when live session model switch blocks failover (#58466)
* fix: remove unused resolveOllamaBaseUrlForRun import after rebase
* feat(macos): add "Trigger Talk Mode" option to Voice Wake settings
When enabled, detecting a wake phrase activates Talk Mode (full voice
conversation: STT -> LLM -> TTS playback) instead of sending a text
message to the chat. Enables hands-free voice assistant UX.
Implementation:
- Constants.swift: new `openclaw.voiceWakeTriggersTalkMode` defaults key
- AppState.swift: new property with UserDefaults persistence + runtime
refresh on change so the toggle takes effect immediately
- VoiceWakeSettings.swift: "Trigger Talk Mode" toggle under Voice Wake,
disabled when Voice Wake is off
- VoiceWakeRuntime.swift: `beginCapture` checks `triggersTalkMode` and
activates Talk Mode directly, skipping the capture/overlay/forward
flow. Pauses the wake listener via `pauseForPushToTalk()` to prevent
two audio pipelines competing on the mic.
- TalkModeController.swift: resumes voice wake listener when Talk Mode
exits by calling `VoiceWakeRuntime.refresh`, mirroring PTT teardown.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address review feedback
- TalkModeController: move VoiceWakeRuntime.refresh() after
TalkModeRuntime.setEnabled(false) completes, preventing mic
contention race where wake listener restarts before Talk Mode
finishes tearing down its audio engine (P1)
- VoiceWakeRuntime: remove redundant haltRecognitionPipeline()
before pauseForPushToTalk() which already calls it via stop() (P2)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: resume wake listener based on enabled state, not toggle value
Check swabbleEnabled instead of voiceWakeTriggersTalkMode when deciding
whether to resume the wake listener after Talk Mode exits. This ensures
the paused listener resumes even if the user toggled "Trigger Talk Mode"
off during an active session. (P2)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: play trigger chime on Talk Mode activation + send chime before TTS
- VoiceWakeRuntime: play the configured trigger chime in the
triggersTalkMode branch before pausing the wake listener. The early
return was skipping the chime that plays in the normal capture flow.
- TalkModeRuntime: play the Voice Wake "send" chime before TTS playback
starts, giving audible feedback that the AI is about to respond. Talk
Mode never used this chime despite it being configurable in settings.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: move send chime to transcript finalization (on send, not on reply)
The send chime now plays when the user's speech is finalized and about
to be sent to the AI, not when the TTS response starts. This matches
the semantics of "send sound" -- it confirms your input was captured.
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(browser): prefer cdpPort over stale WebSocket cdpUrl for attach-only profiles
* fix(browser): preserve profile host when dropping stale devtools WS path
* fix(media): resolve relative MEDIA: paths against agent workspace dir
* fix(agents): remove stale ollama compat import
* fix(media): preserve workspace dir in outbound access
Instead of exponential backoff guesses on Codex 429, probe the WHAM
usage API to determine real availability and write accurate cooldowns.
- Burst/concurrency contention: 15s circuit-break
- Genuine rate limit: proportional to real reset time (capped 2-4h)
- Expired token: 12h cooldown
- Dead account: 24h cooldown
- Probe failure: 30s fail-open
This prevents cascade lockouts when multiple agents share a pool of
Codex profiles, and avoids wasted retries on genuinely exhausted
profiles.
Closes#26329, relates to #1815, #1522, #23996, #54060
Co-authored-by: ryanngit <ryanngit@users.noreply.github.com>
config.patch unconditionally writes the config file and sends SIGUSR1
even when diffConfigPaths detects zero changed paths. This causes a
full gateway restart (~10s downtime, all SSE/WebSocket connections
dropped) on every control-plane config.patch call, even when the
config is identical — e.g. a model hot-apply that doesn't change any
gateway.* paths.
Fix: when changedPaths is empty, return early with `noop: true`
without writing the file or scheduling SIGUSR1. The validated config
is still returned so the caller knows the current state.
This lets control-plane clients safely call config.patch for
idempotent updates without triggering unnecessary restarts.
Add toolsAllow field to cron agent-turn payloads, enabling users to
restrict which tool schemas are sent to the model for a given cron job.
When --tools is set:
- Only listed tools are included in the provider request
- promptMode is forced to 'minimal' (strips skills catalog, reply tags,
heartbeat, messaging, docs, memory, model aliases, silent replies)
- Dramatically reduces input tokens for small local models (~16K to ~800)
CLI surface:
- openclaw cron add --tools exec,read,write
- openclaw cron edit <id> --tools exec
- openclaw cron edit <id> --clear-tools (remove allow-list)
Closes#58435
Co-authored-by: andyk-ms <andyk-ms@users.noreply.github.com>
Allow setting provider params (e.g. cacheRetention) once at the
agents.defaults level instead of repeating them per-model. The merge
order is now: defaults.params -> defaults.models[key].params ->
list[agentId].params.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
resolveTrustedHttpOperatorScopes() returns [] when the x-openclaw-scopes
header is absent, even for trusted requests (--auth none). This causes
403 "missing scope: operator.write" on /v1/chat/completions.
Root cause: src/gateway/http-utils.ts:138-140. PR #57783 (f0af18672)
replaced the old resolveGatewayRequestedOperatorScopes which had an
explicit fallback to CLI_DEFAULT_OPERATOR_SCOPES when no header was
present. The new function treats absent header the same as empty header
— both return [].
Fix: distinguish absent header (undefined → return defaults) from empty
header ("" → return []). Trusted clients without an explicit scope
header get the default operator scopes, matching pre-#57783 behavior.
Closes#58357
Signed-off-by: HCL <chenglunhu@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Fix: Add ollama to NON_PI_NATIVE_MODEL_PROVIDERS to ensure correct model selection
* Add test coverage for ollama provider to ensure models are merged correctly
* Fix test case for ollama provider by adding required model properties
* Changelog: add Ollama model picker fix
* Changelog: move Ollama fix entry to section tail
---------
Co-authored-by: Bruce MacDonald <brucewmacdonald@gmail.com>
Strip `reasoning.effort: "none"` from OpenAI-compatible payloads so GPT-5 models do not receive the unsupported value when thinking is off. Cover both the shared payload wrapper path and the websocket `response.create` builder with regression tests.
Regeneration-Prompt: |
Fix the OpenAI request-building bug where thinking set to off still forwards
a reasoning payload with effort "none". GPT-5 mini rejects that value with a
400, so disabled reasoning must be represented by omitting the reasoning
parameter entirely. Keep the change additive: sanitize OpenAI-compatible
payloads in OpenClaw’s wrapper layer, make the websocket request builder stop
emitting a reasoning block for "none", and add focused regression tests for
both code paths.
Tools with no parameters produce { type: "object" } schemas without a
properties field. The OpenAI Responses API rejects these, silently
crashing entire sessions.
Add properties: {} injection in normalizeToolParameters() and
convertTools() to ensure all object-type schemas include a properties
field.
Closes#58246
- Fix CWE-209: use static safe message instead of raw provider error text
- Fix CWE-117: sanitize provider/model in logs via sanitizeForLog
- Hide CLI hints from external channels via shouldSurfaceToControlUi
- Move overload cap check before advanceAuthProfile to save setup latency
- Export MAX_LIVE_SWITCH_RETRIES as module-level constant
- Use exact toBe() assertions in tests
- Correct failover decision label to fallback_model
- Add MAX_LIVE_SWITCH_RETRIES=2 guard in agent-runner-execution.ts
- Add MAX_OVERLOAD_PROFILE_ROTATIONS=1 cap in run.ts for overloaded errors
- Return kind:final with user-visible error on retry exhaustion
- Escalate to cross-provider fallback instead of exhausting same-provider profiles
Fixes#58348
Pin axios through pnpm overrides and collapse the lockfile to a single
1.13.6 resolution.
This avoids accidental adoption of the compromised axios releases called
out in the ongoing supply chain attack reports while upstream guidance
settles.
fromSurface: true + captureBeyondViewport: true triggers a Chromium compositor
bug where cross-origin image textures are lost when extending the capture
surface. Switch to fromSurface: false to use the software rendering path.
For full-page captures, temporarily expand the viewport via
Emulation.setDeviceMetricsOverride, preserving the current mobile/DPR/screen
state during capture and restoring it afterward so pre-existing device
emulation is not lost.
Made-with: Cursor
Co-authored-by: hakunaliu <hakunaliu@tencent.com>
* feat: add QQ Bot channel extension
* fix(qqbot): add setupWizard to runtime plugin for onboard re-entry
* fix: fix review
* fix: fix review
* chore: sync lockfile and config-docs baseline for qqbot extension
* refactor: 移除图床服务器相关代码
* fix
* docs: 新增 QQ Bot 插件文档并修正链接路径
* refactor: remove credential backup functionality and update setup logic
- Deleted the credential backup module to streamline the codebase.
- Updated the setup surface to handle client secrets more robustly, allowing for configured secret inputs.
- Simplified slash commands by removing unused hot upgrade compatibility checks and related functions.
- Adjusted types to use SecretInput for client secrets in QQBot configuration.
- Modified bundled plugin metadata to allow additional properties in the config schema.
* feat: 添加本地媒体路径解析功能,修正 QQBot 媒体路径处理
* feat: 添加本地媒体路径解析功能,修正 QQBot 媒体路径处理
* feat: remove qqbot-media and qqbot-remind skills, add tests for config and setup
- Deleted the qqbot-media and qqbot-remind skills documentation files.
- Added unit tests for qqbot configuration and setup processes, ensuring proper handling of SecretRef-backed credentials and account configurations.
- Implemented tests for local media path remapping, verifying correct resolution of media file paths.
- Removed obsolete channel and remind tools, streamlining the codebase.
* feat: 更新 QQBot 配置模式,添加音频格式和账户定义
* feat: 添加 QQBot 频道管理和定时提醒技能,更新媒体路径解析功能
* fix
* feat: 添加 /bot-upgrade 指令以查看 QQBot 插件升级指引
* feat: update reminder and qq channel skills
* feat: 更新remind工具投递目标地址格式
* feat: Refactor QQBot payload handling and improve code documentation
- Simplified and clarified the structure of payload interfaces for Cron reminders and media messages.
- Enhanced the parsing function to provide clearer error messages and improved validation.
- Updated platform utility functions for better cross-platform compatibility and clearer documentation.
- Improved text parsing utilities for better readability and consistency in emoji representation.
- Optimized upload cache management with clearer comments and reduced redundancy.
- Integrated QQBot plugin into the bundled channel plugins and updated metadata for installation.
* OK apps/macos/Sources/OpenClaw/HostEnvSecurityPolicy.generated.swift
> openclaw@2026.3.26 check:bundled-channel-config-metadata /Users/yuehuali/code/PR/openclaw
> node --import tsx scripts/generate-bundled-channel-config-metadata.ts --check
[bundled-channel-config-metadata] stale generated output at src/config/bundled-channel-config-metadata.generated.ts
ELIFECYCLE Command failed with exit code 1.
ELIFECYCLE Command failed with exit code 1.
* feat: 添加 QQBot 渠道配置及相关账户设置
* fix(qqbot): resolve 14 high-priority bugs from PR #52986 review
DM routing (7 fixes):
- #1: DM slash-command replies use sendDmMessage(guildId) instead of sendC2CMessage(senderId)
- #2: DM qualifiedTarget uses qqbot:dm:${guildId} instead of qqbot:c2c:${senderId}
- #3: sendTextChunks adds DM branch
- #4: sendMarkdownReply adds DM branch for text and Base64 images
- #5: parseAndSendMediaTags maps DM to targetType:dm + guildId
- #6: sendTextToTarget DM branch uses sendDmMessage; MessageTarget adds guildId field
- #7: handleImage/Audio/Video/FilePayload add DM branches
Other high-priority fixes:
- #8: Fix sendC2CVoiceMessage/sendGroupVoiceMessage parameter misalignment
- #9: broadcastMessage uses groupOpenid instead of member_openid for group users
- #10: Unify KnownUser storage - proactive.ts delegates to known-users.ts
- #11: Remove invalid recordKnownUser calls for guild/DM users
- #12: sendGroupMessage uses sendAndNotify to trigger onMessageSent hook
- #13: sendPhoto channel unsupported returns error field
- #14: sendTextAfterMedia adds channel and dm branches
Type fixes:
- DeliverEventContext adds guildId field
- MediaTargetContext.targetType adds dm variant
- sendPlainTextReply imgMediaTarget adds DM branch
* fix(qqbot): resolve 2 blockers + 7 medium-priority bugs from PR #52986 review
Blocker-1: Remove unused dmPolicy config knob
- dmPolicy was declared in schema/types/plugin.json but never consumed at runtime
- Removed from config-schema.ts, types.ts, and openclaw.plugin.json
- allowFrom remains active (already wired into framework command-auth)
Blocker-2: Gate sensitive slash commands with allowFrom authorization
- SlashCommand interface adds requireAuth?: boolean
- SlashCommandContext adds commandAuthorized: boolean
- /bot-logs set to requireAuth: true (reads local log files)
- matchSlashCommand rejects unauthorized senders for requireAuth commands
- trySlashCommandOrEnqueue computes commandAuthorized from allowFrom config
Medium-priority fixes:
- #15: Strip non-HTTP/non-local markdown image tags to prevent path leakage
- #16: applyQQBotAccountConfig clears clientSecret when setting clientSecretFile and vice versa
- #17: getAdminMarkerFile sanitizes accountId to prevent path traversal
- #18: URGENT_COMMANDS uses exact match instead of startsWith prefix match
- #19: isCronExpression validates each token starts with a cron-valid character
- #20: --token format validation rejects malformed input without colon separator
- #21: resolveDefaultQQBotAccountId checks QQBOT_APP_ID environment variable
* test(qqbot): add focused tests for slash command authorization path
- Unauthorized sender rejected for /bot-logs (requireAuth: true)
- Authorized sender allowed for /bot-logs
- Non-requireAuth commands (/bot-ping, /bot-help, /bot-version) work for all senders
- Unknown slash commands return null (passthrough)
- Non-slash messages return null
- Usage query (/bot-logs ?) also gated by auth check
* fix(qqbot): align global TTS fallback with framework config resolution
- Extract isGlobalTTSAvailable to utils/audio-convert.ts, mirroring core
resolveTtsConfig logic: check auto !== 'off', fall back to legacy
enabled boolean, default to off when neither is set.
- Add pre-check in reply-dispatcher before calling globalTextToSpeech to
avoid unnecessary TTS calls and noisy error logs when TTS is not
configured.
- Remove inline as any casts; use OpenClawConfig type throughout.
- Refactor handleAudioPayload into flat early-return structure with
unified send path (plugin TTS → global fallback → send).
* fix(qqbot): break ESM circular dependency causing multi-account startup crash
The bundled gateway chunk had a circular static import on the channel
chunk (gateway -> outbound-deliver -> channel, while channel dynamically
imports gateway). When two accounts start concurrently via Promise.all,
the first dynamic import triggers module graph evaluation; the circular
reference causes api exports (including runDiagnostics) to resolve as
undefined before the module finishes evaluating.
Fix: extract chunkText and TEXT_CHUNK_LIMIT from channel.ts into a new
text-utils.ts leaf module. outbound-deliver.ts now imports from
text-utils.ts, breaking the cycle. channel.ts re-exports for backward
compatibility.
* fix(qqbot): serialize gateway module import to prevent multi-account startup race
When multiple accounts start concurrently via Promise.all, each calls
await import('./gateway.js') independently. Due to ESM circular
dependencies in the bundled output, the first import can resolve
transitive exports as undefined before module evaluation completes.
Fix: cache the dynamic import promise in a module-level variable so all
concurrent startAccount calls share the same import, ensuring the
gateway module is fully evaluated before any account uses it.
* refactor(qqbot): remove startup greeting logic
Remove getStartupGreetingPlan and related startup greeting delivery:
- Delete startup-greeting.ts (greeting plan, marker persistence)
- Delete admin-resolver.ts (admin resolution, greeting dispatch)
- Remove startup greeting calls from gateway READY/RESUMED handlers
- Remove isFirstReadyGlobal flag and adminCtx
* fix(qqbot): skip octal escape decoding for Windows local paths
Windows paths like C:\Users\1\file.txt contain backslash-digit sequences
that were incorrectly matched as octal escape sequences and decoded,
corrupting the file path. Detect Windows local paths (drive letter or UNC
prefix) and skip the octal decoding step for them.
* fix bot issue
* feat: 支持 TTS 自动开关并清理配置中的 clientSecretFile
* docs: 添加 QQBot 配置和消息处理的设计说明
* rebase
* fix(qqbot): align slash-command auth with shared command-auth model
Route requireAuth:true slash commands (e.g. /bot-logs) through the
framework's api.registerCommand() so resolveCommandAuthorization()
applies commands.allowFrom.qqbot precedence and qqbot: prefix
normalization before any handler runs.
- slash-commands.ts: registerCommand() now auto-routes by requireAuth
into two maps (commands / frameworkCommands); getFrameworkCommands()
exports the auth-required set for framework registration; bot-help
lists both maps
- index.ts: registerFull() iterates getFrameworkCommands() and calls
api.registerCommand() for each; handler derives msgType from ctx.from,
sends file attachments via sendDocument, supports multi-account via
ctx.accountId
- gateway.ts (inbound): replace raw allowFrom string comparison with
qqbotPlugin.config.formatAllowFrom() to strip qqbot: prefix and
uppercase before matching event.senderId
- gateway.ts (pre-dispatch): remove stale auth computation; commandAuthorized
is true (requireAuth:true commands never reach matchSlashCommand)
- command-auth.test.ts: add regression tests for qqbot: prefix
normalization in the inbound commandAuthorized computation
- slash-commands.test.ts: update /bot-logs tests to expect null
(command routed to framework, not in local registry)
* rebase and solve conflict
* fix(qqbot): preserve mixed env setup credentials
---------
Co-authored-by: yuehuali <yuehuali@tencent.com>
Co-authored-by: walli <walli@tencent.com>
Co-authored-by: WideLee <limkuan24@gmail.com>
Co-authored-by: Frank Yang <frank.ekn@gmail.com>
- install/docker.md: link to podman, clawdock, updating, config
- install/node.md: link to overview, updating, getting-started
- install/updating.md: link to overview, doctor, migrating
- help/troubleshooting.md: link to FAQ, gateway/channel/automation troubleshooting, doctor
- network.md: add Core model prose (loopback-first, canvas host, remote access)
from the 22-line network-model.md stub
- network-model.md: add redirect note pointing to /network#core-model
- bridge-protocol.md: replace scattered deprecation notes with prominent
<Warning> callout at the top
- building-plugins.md, manifest.md: link to architecture, SDK, channel/provider plugins
- control-ui.md, tui.md: link to sibling web interfaces and CLI
Add cross-linking Related sections to tool pages that were dead ends:
- exec, exec-approvals, browser, pdf, skills, lobster
Each page now links to 2-4 related topics for navigation continuity.
Add cross-linking Related sections to concept pages that were dead ends:
- model-providers, models, context, context-engine, agent-workspace,
architecture, messages, streaming, compaction, oauth
Each page now links to 3-4 related topics for navigation continuity.
* feat(telegram): add child thread-binding placement via createForumTopic
Enable ACP subagent spawn on Telegram by adding "child" placement
support to the thread-bindings adapter. When a child binding is
requested, the adapter creates a new forum topic via the Telegram
Bot API and binds the subagent session to it using the canonical
chatId:topic:topicId conversation ID format.
When the ACP spawn context provides only a topic ID (not a full
group chat ID), the adapter resolves the group from the configured
Telegram groups in openclaw.json.
This mirrors the Discord adapter's child placement behavior
(thread creation + session binding) and unblocks the orchestrator
pattern on Telegram forum-enabled groups.
Closes#5737
Ref #23414
* fix(telegram): return null with warning instead of silent group fallback for bare topic IDs in child bind
* telegram: fix ACP child thread spawn with group chat ID from agentGroupId
* telegram: scope agentGroupId substitution to telegram channel only
* Telegram: fix forum topic replies routing to root chat instead of topic thread
* fix: clean up dead guard in child bind + add explicit threadId override test
- Simplify bare-topic-ID guards in thread-bindings.ts: split into
separate !chatId and !chatId.startsWith("-") checks, removing
unreachable second condition
- Add regression test confirming explicit turnSourceThreadId overrides
session lastThreadId on same channel
* fix: guard threadId fallback against shared-session race
Codex review P1: when turnSourceTo differs from the session's stored
to, the session threadId may belong to a different chat/topic. Only
fall back to context.threadId when the destination also matches.
* fix(telegram): enable ACP spawn from forum topics without thread binding
extractExplicitGroupId returned topic-qualified IDs (-100...:topic:1264)
instead of bare group chat IDs, breaking agentGroupId resolution.
agentGroupId was also never wired in the inline actions path.
For Telegram forum topics, skip thread binding entirely — the delivery
plan already routes correctly via requester origin (to + threadId).
Creating new forum topics per child session is unnecessary; output goes
back to the same topic the user asked from.
* fix(acp): bind Telegram forum sessions to current topic
* fix: restore Telegram forum-topic routing (#56060) (thanks @one27001)
---------
Co-authored-by: openclaw <mgabrie.dev@gmail.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* feat(plugins): support multi-kind plugins for dual slot ownership
* fix: address review feedback on multi-kind plugin support
- Use sorted normalizeKinds() for kind-mismatch comparison in loader.ts
(fixes order-sensitive JSON.stringify for arrays)
- Derive slot-to-kind reverse mapping from SLOT_BY_KIND in slots.ts
(removes hardcoded ternary that would break for future slot types)
- Use shared hasKind() helper in config-state.ts instead of inline logic
* fix: don't disable dual-kind plugin that still owns another slot
When a new plugin takes over one slot, a dual-kind plugin that still
owns the other slot must not be disabled — otherwise context engine
resolution fails at runtime.
* fix: exempt dual-kind plugins from memory slot disablement
A plugin with kind: ["memory", "context-engine"] must stay enabled even
when it loses the memory slot, so its context engine role can still load.
* fix: address remaining review feedback
- Pass manifest kind (not hardcoded "memory") in early memory gating
- Extract kindsEqual() helper for DRY kind comparison in loader.ts
- Narrow slotKeyForPluginKind back to single PluginKind with JSDoc
- Reject empty array in parsePluginKind
- Add kindsEqual tests
* fix: use toSorted() instead of sort() per lint rules
* plugins: include default slot ownership in disable checks and gate dual-kind memory registration
* fix: handle LiveSessionModelSwitchError in cron isolated sessions
The main agent runner catches LiveSessionModelSwitchError and retries
with the requested model, but cron isolated sessions hit this error
and fail immediately. This extends the retry to cover cron execution.
When a cron job with `sessionTarget: 'isolated'` specifies a `model`
different from the agent's primary, the embedded runner throws
LiveSessionModelSwitchError (because the session initialized with the
wrong model). The fix wraps the initial runPrompt call in a retry loop
that catches this error, updates provider/model state, and re-runs —
mirroring the existing retry logic in agent-runner-execution.ts.
Fixes#57206
* fix: carry auth profile through cron model retry
* fix: complete cron isolated model-switch retry (#57972) (thanks @issaba1)
---------
Co-authored-by: Isaac Saba <isaacsaba@Isaacs-Mac-mini.local>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(telegram): allow RFC 2544 benchmark IPs in media download SSRF policy (#57452)
Telegram CDN file servers may resolve to IPs in the RFC 2544 benchmark range (198.18.0.0/15). The SSRF policy blocked these downloads while Discord and Slack correctly allowed them. Set allowRfc2544BenchmarkRange to true to match other channel plugins.
* fix: note Telegram media RFC2544 CDN downloads (#57624) (thanks @MoerAI)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(infra/net): route through env proxy in STRICT mode while preserving DNS pinning
When HTTP_PROXY/HTTPS_PROXY env vars are configured, the SSRF guard's
pinned dispatcher connects directly to the DNS-resolved IP, bypassing the
proxy. This fails in environments where direct outbound connections are
blocked (OpenShell sandboxes, Docker containers, corporate networks).
Use `createPinnedDispatcher` with `mode: "env-proxy"` when
`hasEnvHttpProxyConfigured()` returns true. This preserves DNS-pinning
(the resolved IP is threaded into the connect option via
`EnvHttpProxyAgent`) while routing through the proxy.
- Uses `hasEnvHttpProxyConfigured()` (not `hasProxyEnvConfigured()`) to
avoid the ALL_PROXY edge case where EnvHttpProxyAgent ignores ALL_PROXY
- Preserves STRICT mode's anti-DNS-rebinding guarantee
- TRUSTED_ENV_PROXY remains the explicit opt-in for unpinned proxy routing
- No change when proxy env vars are not set
Fixes#47598, #49948, #32947, #46306
Related: #45248
* test(infra): stabilize fetch guard proxy assertions
* fix: respect hostname-scoped proxy bypass (#50650) (thanks @kkav004)
---------
Co-authored-by: Kiryl Kavalenka <kiryl.kavalenka@whiparound.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Reduce prep and merge friction in the PR wrapper by keeping rebases explicit, reusing doc-only gate results, and making review output terminal-first.
Also add clearer baseline-noise guidance for unrelated local gate failures plus worktree listing and cleanup helpers.
* feat(gateway): implement claim check pattern to prevent OOM on large attachments
* fix: sanitize mediaId, refine trimEnd, remove warn log, add threshold and absolute path
* fix: enforce maxBytes before decoding and use dynamic path from saveMediaBuffer
* fix: enforce absolute maxBytes limit before Buffer allocation and preserve file extensions
* fix: align saveMediaBuffer arguments and satisfy oxfmt linter
* chore: strictly enforce linting rules (curly braces, unused vars, and error typing)
* fix: restrict offload to mainstream mimes to avoid extension-loss bug in store.ts for BMP/TIFF
* fix: restrict offload to mainstream mimes to bypass store.ts extension-loss bug
* chore: document bmp/tiff exclusion from offload whitelist in MIME_TO_EXT
* feat: implement agent-side resolver for opaque media URIs and finalize contract
* fix: support unicode media URIs and allow consecutive dots in safe IDs based on Codex review
* fix(gateway): enforce strict fail-fast for oversized media to prevent OOM bypass
* refactor(gateway): harden media offload with performance and security optimizations
This update refines the Claim Check pattern with industrial-grade guards:
- Performance: Implemented sampled Base64 validation for large payloads (>4KB) to prevent event loop blocking.
- Security: Added null-byte (\u0000) detection and reinforced path traversal guards.
- I18n: Updated media-uri regex to a blacklist-based character class for Unicode/Chinese filename support, with oxlint bypass for intentional control regex.
- Robustness: Enhanced error diagnostics with JSON-serialized IDs.
* fix: add HEIC/HEIF to offload allowlist and pass maxBytes to saveMediaBuffer
* fix(gateway): clean up offloaded media files on attachment parse failure
Address Codex review feedback: track saved media IDs and implement best-effort cleanup via deleteMediaBuffer if subsequent attachments fail validation, preventing orphaned files on disk.
* fix(gateway): enforce full base64 validation to prevent whitespace padding bypass
Address Codex review feedback: remove early return in isValidBase64 so padded payloads cannot bypass offload thresholds and reintroduce memory pressure. Updated related comments.
* fix(gateway): preserve offloaded media metadata and fix validation error mapping
Address Codex review feedback:
- Add \offloadedRefs\ to \ParsedMessageWithImages\ to expose structured metadata for offloaded attachments, preventing transcript media loss.
- Move \erifyDecodedSize\ outside the storage try-catch block to correctly surface client base64 validation failures as 4xx errors instead of 5xx \MediaOffloadError\.
- Add JSDoc TODOs indicating that upstream callers (chat.ts, agent.ts, server-node-events.ts) must explicitly pass the \supportsImages\ flag.
* fix(agents): explicitly allow media store dir when loading offloaded images
Address Codex review feedback: Pass getMediaDir() to loadWebMedia's localRoots for media-uri refs to prevent legacy path resolution mismatches from silently dropping large attachments.
* fix(gateway): resolve attachment offload regressions and error mapping
Address Codex review feedback:
- Pass \supportsImages\ dynamically in \chat.ts\ and \gent.ts\ based on model catalog, and explicitly in \server-node-events.ts\.
- Persist \offloadedRefs\ into the transcript pipeline in \chat.ts\ to preserve media metadata for >2MB attachments.
- Correctly map \MediaOffloadError\ to 5xx (UNAVAILABLE) to differentiate server storage faults from 4xx client validation errors.
* fix(gateway): dynamically compute supportsImages for overrides and node events
Address follow-up Codex review feedback:
- Use effective model (including overrides) to compute \supportsImages\ in \gent.ts\.
- Move session load earlier in \server-node-events.ts\ to dynamically compute \supportsImages\ rather than hardcoding true.
* fix(gateway): resolve capability edge cases reported by codex
Address final Codex edge cases:
- Refactor \gent.ts\ to compute \supportsImages\ even when no session key is present, ensuring text-only override requests without sessions safely drop attachments.
- Update catalog lookups in \chat.ts\, \gent.ts\, and \server-node-events.ts\ to strictly match both \id\ and \provider\ to prevent cross-provider model collisions.
* fix(agents): restore before_install hook for skill installs
Restore the plugin scanner security hook that was accidentally dropped during merge conflict resolution.
* fix: resolve attachment pathing, defer parsing after auth gates, and clean up node-event mocks
* fix: resolve syntax errors in test-env, fix missing helper imports, and optimize parsing sequence in node events
* fix(gateway): re-enforce message length limit after attachment parsing
Adds a secondary check to ensure the 20,000-char cap remains effective even after media markers are appended during the offload flow.
* fix(gateway): prevent dropping valid small images and clean up orphaned media on size rejection
* fix(gateway): share attachment image capability checks
* fix(gateway): preserve mixed attachment order
* fix: fail closed on unknown image capability (#55513) (thanks @Syysean)
* fix: classify offloaded attachment refs explicitly (#55513) (thanks @Syysean)
---------
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(slack): restore table block mode seam
Restore the shared markdown/config seam needed for Slack Block Kit table support, while coercing non-Slack block mode back to code.
* fix(slack): narrow table block seam defaults
Keep Slack table block mode opt-in in this seam-only PR, clamp collected placeholder offsets, and align fallback-table rendering with Slack block limits.
* fix(slack): bound table fallback rendering
Avoid spread-based maxima and bound Slack table fallback rendering by row, column, cell-width, and total-output limits to prevent resource exhaustion.
* fix(slack): keep block mode inactive in seam PR
Keep markdown table block mode schema-valid but runtime-resolved to code until the Slack send path is wired to emit table attachments.
* fix(slack): normalize configured block mode safely
Accept configured markdown table block mode at parse time, then normalize it back to code during runtime resolution so seam-only branches do not drop table content.
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>
**Important:** Always wait for the Telegram verification message before proceeding. If the agent doesn't respond, troubleshoot the gateway or model configuration before pushing.
Upstream updates may introduce Swift 6.2 / macOS 26 SDK incompatibilities. Use analyze-mode for systematic debugging:
### Analyze-Mode Investigation
```bash
# Gather context with parallel agents
morph-mcp_warpgrep_codebase_search search_string="Find deprecated FileManager.default and Thread.isMainThread usages in Swift files"repo_path="/Volumes/Main SSD/Developer/clawdis"
morph-mcp_warpgrep_codebase_search search_string="Locate Peekaboo submodule and macOS app Swift files with concurrency issues"repo_path="/Volumes/Main SSD/Developer/clawdis"
Common issue: `fetch.preconnect` type mismatch. Fix by using `FetchLike` type instead of `typeof fetch`.
### macOS App Crashes on Launch
Usually resource bundle mismatch. Full rebuild required:
```bash
cd apps/macos && rm -rf .build .swiftpm
./scripts/restart-mac.sh
```
### Patch Failures
```bash
# Check patch status
pnpm install 2>&1| grep -i patch
# If patches fail, they may need updating for new dep versions
# Check patches/ directory against package.json patchedDependencies
```
### Swift 6.2 / macOS 26 SDK Build Failures
**Symptoms:** Build fails with deprecation warnings about `FileManager.default` or `Thread.isMainThread`
**Search-Mode Investigation:**
```bash
# Exhaustive search for deprecated APIs
morph-mcp_warpgrep_codebase_search search_string="Find all Swift files using deprecated FileManager.default or Thread.isMainThread"repo_path="/Volumes/Main SSD/Developer/clawdis"
@@ -16,17 +16,28 @@ Use this skill for Parallels guest workflows and smoke interpretation. Do not lo
- 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.
- If `main` is moving under active multi-agent work, prefer a detached worktree pinned to one commit for long Parallels suites. The smoke scripts now verify the packed tgz commit instead of live `git rev-parse HEAD`, but a pinned worktree still avoids noisy rebuild/version drift during reruns.
- For `openclaw update --channel dev` lanes, remember the guest clones GitHub `main`, not your local worktree. If a local fix exists but the rerun still fails inside the cloned dev checkout, do not treat that as disproof of the fix until the branch has been pushed.
- 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`.
- 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. Treat any Ubuntu guest with major version `>= 24` as acceptable when the exact default VM is missing, preferring the closest version match. On Peter's current host today, missing `Ubuntu 24.04.3 ARM64` should fall back to `Ubuntu 25.10`.
- On macOS same-guest update checks, restart the gateway after the npm upgrade before `gateway status` / `agent`; launchd can otherwise report a loaded service while the old process has exited and the fresh process is not RPC-ready yet.
- 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.
- The Windows same-guest update helper should write stage markers to its log before long steps like tgz download and `npm install -g` so the outer progress monitor does not sit on `waiting for first log line` during healthy but quiet installs.
- 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.
- The npm-update wrapper now prints per-lane progress from the nested log files. If a lane still looks stuck, inspect the nested logs in `runDir` first (`macos-fresh.log`, `windows-fresh.log`, `linux-fresh.log`, `macos-update.log`, `windows-update.log`, `linux-update.log`) instead of assuming the outer wrapper hung.
- If the wrapper fails a lane, read the auto-dumped tail first, then the full nested lane log under `/tmp/openclaw-parallels-npm-update.*`.
## CLI invocation footgun
@@ -35,11 +46,20 @@ Use this skill for Parallels guest workflows and smoke interpretation. Do not lo
- Default upgrade coverage on macOS should now include: fresh snapshot -> site installer pinned to the latest stable tag -> `openclaw update --channel dev` on the guest. Treat this as part of the default Tahoe regression plan, not an optional side quest.
-`parallels-macos-smoke.sh --mode upgrade` should run that release-to-dev lane by default. Keep the older host-tgz upgrade path only when the caller explicitly passes `--target-package-spec`.
- Because the default upgrade lane no longer needs a host tgz, skip `npm pack` + host HTTP server startup for `--mode upgrade` unless `--target-package-spec` is set. Keep the pack/server path for `fresh` and `both`.
- If that release-to-dev lane fails with `reason=preflight-no-good-commit` and repeated `sh: pnpm: command not found` tails from `preflight build`, treat it as an updater regression first. The fix belongs in the git/dev updater bootstrap path, not in Parallels retry logic.
- Until the public stable train includes that updater bootstrap fix, the macOS release-to-dev lane may seed a temporary guest-local `pnpm` shim immediately before `openclaw update --channel dev`. Keep that workaround scoped to the smoke harness and remove it once the latest stable no longer needs it.
- In Tahoe `prlctl exec --current-user` runs, prefer explicit `node .../openclaw.mjs ...` invocations for the release->dev handoff itself and for post-update verification. The shebanged global `openclaw` wrapper can fail with `env: node: No such file or directory`, and self-updating through the wrapper is a weaker lane than invoking the entrypoint under a fixed `node`.
- 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.
-`parallels-macos-smoke.sh` now retries `snapshot-switch` once after force-stopping a stuck running/suspended guest. If Tahoe still times out after that recovery path, then treat it as a real Parallels/host issue and rerun manually.
- 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.
- If a packaged install regresses with `500` on `/`, `/healthz`, or `__openclaw/control-ui-config.json` after `fresh.install-main` or `upgrade.install-main`, suspect bundled plugin runtime deps resolving from the package root `node_modules` rather than `dist/extensions/*/node_modules`. Repro quickly with a real `npm pack`/global install lane before blaming dashboard auth or Safari.
-`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.
@@ -48,12 +68,28 @@ Use this skill for Parallels guest workflows and smoke interpretation. Do not lo
- Use the snapshot closest to `pre-openclaw-native-e2e-2026-03-12`.
- Default upgrade coverage on Windows should now include: fresh snapshot -> site installer pinned to the requested stable tag -> `openclaw update --channel dev` on the guest. Keep the older host-tgz upgrade path only when the caller explicitly passes `--target-package-spec`.
- Optional exact npm-tag baseline on Windows: `bash scripts/e2e/parallels-windows-smoke.sh --mode upgrade --target-package-spec openclaw@<tag> --json`. That lane installs the published npm tarball as baseline, then runs `openclaw update --channel dev`.
- Optional forward-fix Windows validation: `bash scripts/e2e/parallels-windows-smoke.sh --mode upgrade --upgrade-from-packed-main --json`. That lane installs the packed current-main npm tgz as baseline, then runs `openclaw update --channel dev`.
- 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.
- Current Windows Node installs expose `corepack` as a `.cmd` shim. If a release-to-dev lane sees `corepack` on PATH but `openclaw update --channel dev` still behaves as if corepack is missing, treat that as an exec-shim regression first.
- If an exact published-tag Windows lane fails during preflight with `npm run build` and `'pnpm' is not recognized`, remember that the guest is still executing the old published updater. Validate the fix with `--upgrade-from-packed-main`, then wait for the next tagged npm release before expecting the historical tag lane to pass.
- 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.
- If a Windows retry sees the VM become `suspended` or `stopped`, resume/start it before the next `prlctl exec`; otherwise the second attempt just repeats the same `rc=255`.
- 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.
- When those Windows global installs stay quiet, the useful progress often lives in the guest npm debug log, not the helper phase log. The smoke script now streams incremental `npm-cache/_logs/*-debug-0.log` deltas into the phase log during long baseline/package installs; read those lines before assuming the lane is stalled.
- The Windows baseline-package helpers now auto-dump the latest guest `npm-cache/_logs/*-debug-0.log` tail on timeout or nonzero completion. Read that tail in the phase log before opening a second guest shell.
- The same incremental npm-debug streaming also applies to `--upgrade-from-packed-main` / packaged-install baseline phases. A phase log that still says only `install.start`, `install.download-tgz`, `install.install-tgz` can still be healthy if the streamed npm-debug section shows registry fetches or bundled-plugin postinstall work.
- Fresh Windows tgz install phases should also use the background PowerShell runner plus done-file/log-drain pattern; do not rely on one long-lived `prlctl exec ... powershell ... npm install -g` transport for package installs.
- Windows release-to-dev helpers should log `where pnpm` before and after the update and require `where pnpm` to succeed post-update. That proves the updater installed or enabled `pnpm` itself instead of depending on a smoke-only bootstrap.
- 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 daemon-health reachability should use a hello-only gateway probe and a longer per-probe timeout than the default local attach path; full health RPCs are too eager during initial startup on current main.
- 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 standalone Windows upgrade smoke lane should stop the managed gateway after `upgrade.install-main` and before `upgrade.onboard-ref`. Restarting before onboard can leave the old process alive on the pre-onboard token while onboard rewrites `~/.openclaw/openclaw.json`, which then fails `gateway-health` with `unauthorized: gateway token mismatch`.
- If standalone Windows upgrade fails with a gateway token mismatch but `pnpm test:parallels:npm-update` passes, trust the mismatch as a standalone ref-onboard ordering bug first; the npm-update helper does not re-run ref-mode onboard on the same guest.
- 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.
@@ -61,13 +97,14 @@ Use this skill for Parallels guest workflows and smoke interpretation. Do not lo
- 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`.
- If that exact VM is missing on the host, any Ubuntu guest with major version `>= 24` is acceptable; prefer the closest versioned 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.
-The Linux smoke now falls back to a manual `setsid openclaw gateway run --bind loopback --port 18789 --force` launch with `HOME=/root` and the provider secret exported, then verifies `gateway status --deep --require-rpc` when available.
- The Linux manual gateway launch should wait for `gateway status --deep --require-rpc` inside the `gateway-start` phase; otherwise the first status probe can race the background bind and fail a healthy lane.
- If Linux gateway bring-up fails, inspect `/tmp/openclaw-parallels-linux-gateway.log` in the guest phase logs first; the common failure mode is a missing provider secret in the launched gateway environment.
description: Run, watch, debug, and extend OpenClaw QA testing with qa-lab and qa-channel. Use when Codex needs to execute the repo-backed QA suite, inspect live QA artifacts, debug failing scenarios, add new QA scenarios, or explain the OpenClaw QA workflow. Prefer the live OpenAI lane with regular openai/gpt-5.4 in fast mode; do not use gpt-5.4-pro or gpt-5.4-mini unless the user explicitly overrides that policy.
---
# OpenClaw QA Testing
Use this skill for `qa-lab` / `qa-channel` work. Repo-local QA only.
## Read first
-`docs/concepts/qa-e2e-automation.md`
-`docs/help/testing.md`
-`docs/channels/qa-channel.md`
-`qa/QA_KICKOFF_TASK.md`
-`qa/seed-scenarios.json`
-`extensions/qa-lab/src/suite.ts`
## Model policy
- Live OpenAI lane: `openai/gpt-5.4`
- Fast mode: on
- Do not use:
-`openai/gpt-5.4-pro`
-`openai/gpt-5.4-mini`
- Only change model policy if the user explicitly asks.
## Default workflow
1. Read the seed plan and current suite implementation.
short_description:"Run and debug qa-lab and qa-channel scenarios"
default_prompt:"Use $openclaw-qa-testing to run or extend the OpenClaw QA suite with qa-lab and qa-channel, using regular openai/gpt-5.4 in fast mode for live OpenAI runs."
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.
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, triage likely transformed-module retention versus likely runtime 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.
Use this skill for test-memory investigations. Do not guess from RSS alone when heap snapshots are available. Treat snapshot-name deltas as triage evidence, not proof, until retainers or dominators support the call.
## Workflow
@@ -14,19 +14,23 @@ Use this skill for test-memory investigations. Do not guess from RSS alone when
- 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.
- Before you analyze snapshots, identify the real lane names from `[test-parallel] start ...` lines or `pnpm test --plan`. Do not assume a single `unit-fast` lane; local plans often split into `unit-fast-batch-*`.
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.
- Compare snapshots from the same PID inside the real lane directory such as `.tmp/heapsnap/unit-fast-batch-2/`.
- Use `.agents/skills/openclaw-test-heap-leaks/scripts/heapsnapshot-delta.mjs` to compare either two files directly or the earliest/latest pair per PID in one lane directory.
- If the helper suggests transformed-module retention, confirm the top entries in DevTools retainers/dominators before calling it solved.
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 Vite/Vitest transformed source strings, `Module`, `system / Context`, bytecode, descriptor arrays, or property maps, treat it as likely 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.
- If the names are ambiguous, stop short of a confident label and inspect retainers/dominators in DevTools for the top deltas.
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`.
- For likely retained transformed-module growth in shared workers:
-Prefer timing and hotspot-driven scheduling fixes first. Check whether the file is already represented in `test/fixtures/test-timings.unit.json` and whether `scripts/test-update-memory-hotspots.mjs` should refresh the measured hotspot manifest before hand-editing behavior overrides.
- Move hotspot files out of the real shared lane by updating `test/fixtures/test-parallel.behavior.json` only when timing-driven peeling is insufficient.
- 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:
@@ -40,24 +44,24 @@ Use this skill for test-memory investigations. Do not guess from RSS alone when
## 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.
- Do not call everything a leak. In this repo, large `unit-fast` or `unit-fast-batch-*` 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.
- When the same retained object families grow across multiple intervals in the same worker PID, trust the snapshots over intuition, then confirm ambiguous calls with retainer evidence.
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.
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. If the names alone do not settle it, open the same snapshot pair in DevTools and inspect retainers/dominators for the top rows before declaring root cause.
## Output Expectations
@@ -66,6 +70,6 @@ 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.
- Whether the issue is a likely real leak or likely shared-worker retained module growth, plus whether retainers/dominators confirmed it.
- The concrete fix or impact-reduction patch.
- What you verified, and what snapshot overhead prevented you from verifying.
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:
@@ -20,6 +22,19 @@ 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.
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:
@@ -33,19 +35,17 @@ Describe the problem and fix in 2–5 bullets:
- Related #
- [ ] This PR fixes a bug or regression
## Root Cause / Regression History (if applicable)
## Root Cause (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:
-Contributing context (if known):
## 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`.
For bug fixes or regressions, name the smallest reliable test coverage that should catch this. Otherwise write `N/A`.
- Coverage level that should have caught this:
- [ ] Unit test
@@ -63,6 +63,18 @@ For bug fixes or regressions, name the smallest reliable test coverage that shou
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`)
@@ -127,12 +139,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) {
// `bad-barnacle` exempts PRs that Barnacle incorrectly marked dirty.
if (labelSet.has(dirtyLabel) && !labelSet.has(badBarnacleLabel)) {
if (labelSet.has(badBarnacleLabel)) {
core.info(`Skipping PR auto-response checks for #${pullRequest.number} because ${badBarnacleLabel} is present.`);
if printf '%s\n' "$changed_files" | grep -Eq '^(ui/src/i18n/locales/en\.ts|ui/src/i18n/lib/types\.ts|ui/src/i18n/lib/registry\.ts|scripts/control-ui-i18n\.ts|\.github/workflows/control-ui-locale-refresh\.yml)$'; then
echo "This workflow no longer builds, signs, notarizes, or uploads macOS assets."
echo "This workflow validates the public release handoff and still builds JS artifacts needed for release checks."
echo "It does not sign, notarize, or upload 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 "- Run \`openclaw/releases-private/.github/workflows/openclaw-macos-validate.yml\` with tag \`${RELEASE_TAG}\` and wait for the private mac validation lane to pass."
echo "- Run \`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml\` with tag \`${RELEASE_TAG}\` and \`preflight_only=true\` for the full private mac preflight."
echo "- For the real publish path, run the same private mac publish workflow from \`main\` with the successful private preflight \`preflight_run_id\` so it promotes the prepared artifacts instead of rebuilding them."
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 `~/...`.
- 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.
## Project Structure & Module Organization
@@ -9,17 +9,71 @@
- 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/`.
- Nomenclature: use "plugin" / "plugins" in docs, UI, changelogs, and contributor guidance. `extensions/*` remains the internal directory/package path 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`, `extensions/<id>` by default, 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 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`).
- Invariant: core must stay extension-agnostic. Adding a bundled or third-party extension should not require unrelated core edits just to teach core that the extension exists.
- 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`.
- Rule: do not add hardcoded bundled extension/provider/channel/capability id lists, maps, or named special cases in core when a manifest, capability, registry, or plugin-owned contract can express the same behavior.
- Rule: extension-owned compatibility behavior belongs to the owning extension. Core may orchestrate generic doctor/config flows, but extension-specific legacy repairs, detection rules, onboarding, auth detection, and provider defaults should live in plugin-owned contracts.
- Rule: for legacy config specifically, prefer doctor-owned repair paths over startup/load-time core migrations. Do not add new plugin-specific legacy migration logic to shared core/runtime surfaces when `openclaw doctor --fix` can own it.
- Rule: when a test is asserting extension-specific behavior, keep that coverage in the owning extension when feasible. Core tests should assert generic contracts and registry/capability behavior, not extension internals.
- Refactor trigger: if you encounter core code or tests that name a specific extension/provider/channel for extension-owned behavior, refactor toward a generic registry/capability/plugin-owned seam instead of adding another special case.
- 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: protocol changes are contract changes. Prefer additive evolution; incompatible changes require explicit versioning, docs, and client/codegen follow-through.
- Config contract boundary:
- Canonical public config lives in exported config types, zod/schema surfaces, schema help/labels, generated config metadata, config baselines, and any user-facing gateway/config payloads. Keep those surfaces aligned.
- When a legacy config key is retired from the public contract, remove it from every public config surface above. Keep backward compatibility only through raw-config migration/doctor seams unless explicit product policy says otherwise.
- Do not reintroduce removed legacy aliases into public types/schema/help/baselines “for convenience”. If old configs still need to load, handle that in `legacy.migrations.*`, config ingest, or `openclaw doctor --fix`.
-`hooks.internal.entries` is the canonical public hook config model. `hooks.internal.handlers` is compatibility-only input and must not be re-exposed in public schema/help/baseline surfaces.
- Bundled plugin contract boundary:
- Public docs: `docs/plugins/architecture.md`, `docs/plugins/manifest.md`, `docs/plugins/sdk-overview.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.
- If a core test is asserting extension-specific behavior instead of a generic contract, move it to the owning extension package.
## Docs Linking (Mintlify)
@@ -34,16 +88,24 @@
- 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”.
## Docs i18n (zh-CN)
## Docs i18n (generated publish locales)
-`docs/zh-CN/**` is generated; do not edit unless the user explicitly asks.
-Pipeline: update English docs → adjust glossary (`docs/.i18n/glossary.zh-CN.json`) → run `scripts/docs-i18n` → apply targeted fixes only if instructed.
-Foreign-language docs are not maintained in this repo. The generated publish output lives in the separate `openclaw/docs` repo (often cloned locally as the sibling `openclaw-docs` directory); do not add or edit localized docs under `docs/<locale>/**` here.
-Those localized docs are autogenerated. Treat this repo's English docs plus glossary files as the source of truth, and let the publish/translation pipeline update `openclaw/docs`.
- Pipeline: update English docs here → adjust the matching `docs/.i18n/glossary.<locale>.json` entries → let the publish-repo sync + `scripts/docs-i18n` run in `openclaw/docs` / local `openclaw-docs` clone → apply targeted fixes only if instructed.
- Before rerunning `scripts/docs-i18n`, add glossary entries for any new technical terms, page titles, or short nav labels that must stay in English or use a fixed translation (for example `Doctor` or `Polls`).
-`pnpm docs:check-i18n-glossary` enforces glossary coverage for changed English doc titles and short internal doc labels before translation reruns.
- Translation memory lives in generated`docs/.i18n/*.tm.jsonl`files in the publish repo.
- See `docs/.i18n/README.md`.
- The pipeline can be slow/inefficient; if it’s dragging, ping @jospalmbier on Discord instead of hacking around it.
## Control UI i18n (generated in repo)
- Control UI foreign-language locale bundles are generated in this repo; do not hand-edit `ui/src/i18n/locales/*.ts` for non-English locales or `ui/src/i18n/.i18n/*` unless a targeted generated-output fix is explicitly requested.
- Source of truth is `ui/src/i18n/locales/en.ts` plus the generator/runtime wiring in `scripts/control-ui-i18n.ts`, `ui/src/i18n/lib/types.ts`, and `ui/src/i18n/lib/registry.ts`.
- Pipeline: update English control UI strings and locale wiring here → run `pnpm ui:i18n:sync` (or let `Control UI Locale Refresh` do it) → commit the regenerated locale bundles and `.i18n` metadata.
- If the control UI locale outputs drift, regenerate them; do not manually translate or hand-maintain the generated locale files by default.
## exe.dev VM ops (general)
- Access: stable path is `ssh exe.dev` then `ssh vm-name` (assume SSH key already set).
- 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`.
@@ -69,30 +132,59 @@
- Type-check/build: `pnpm build`
- TypeScript checks: `pnpm tsgo`
- Lint/format: `pnpm check`
- Local agent/dev shells default to host-aware `OPENCLAW_LOCAL_CHECK=1` behavior for `pnpm tsgo` and `pnpm lint`; set `OPENCLAW_LOCAL_CHECK_MODE=throttled` to force the lower-memory profile, `OPENCLAW_LOCAL_CHECK_MODE=full` to keep lock-only behavior, or `OPENCLAW_LOCAL_CHECK=0` in CI/shared runs.
- 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.
- Generated baseline artifacts live together under `docs/.generated/`.
- Generated baseline drift detection uses SHA-256 hash files under `docs/.generated/` (`.sha256` files tracked in git; full JSON baselines are gitignored, generated locally for inspection).
- 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.
- If you change config schema/help or the public Plugin SDK surface, run the matching gen command and commit the updated `.sha256` hash file. 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.
-Preferred landing bar for pushes to `main`: `pnpm check` and `pnpm test`, with a green result when feasible.
-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 commit or push 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.
- 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.
## Prompt Cache Stability
- Treat prompt-cache stability as correctness/perf-critical, not cosmetic.
- Any code that assembles model or tool payloads from maps, sets, registries, plugin lists, MCP catalogs, filesystem reads, or network results must make ordering deterministic before building the request.
- Do not rewrite older transcript/history bytes on every turn unless you intentionally want to invalidate the cached prefix. Legacy cleanup, pruning, normalization, and migration logic should preserve recent prompt bytes when possible.
- If truncation or compaction is required, prefer mutating newest or tail content first so the cached prefix stays byte-identical for as long as possible.
- For cache-sensitive changes, require a regression test that proves turn-to-turn prefix stability or deterministic request assembly; helper-local tests alone are not enough.
- Never add `@ts-nocheck` and do not disable `no-explicit-any`; fix root causes and update Oxlint/Oxfmt config only when required.
- 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 `extensions/<id>/**`, do not use relative imports/exports that resolve outside that same`extensions/<id>` 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 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.
@@ -116,12 +208,23 @@
- 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.
- Test performance guardrail: do not put `vi.resetModules()` plus `await import(...)` in `beforeEach`/per-test loops for heavy modules unless module state truly requires it. Prefer static imports or one-time `beforeAll` imports, then reset mocks/runtime state directly.
- Test performance guardrail: if a test file uses stable `vi.mock(...)` hoists or other static module mocks, do not pair them with `vi.resetModules()` and a fresh `await import(...)` in every `beforeEach`. Import the heavy module once in `beforeAll`, then reset/prime mocks in `beforeEach` so Browser/Matrix-style hotspot tests do not pay the module graph cost per case.
- Test performance guardrail: inside an extension package, prefer a thin local seam (`./api.ts`, `./runtime-api.ts`, or a narrower local `*.runtime-api.ts`) over direct `openclaw/plugin-sdk/*` imports for internal production code. Keep local seams curated and lightweight; only reach for direct `plugin-sdk/*` imports when you are crossing a real package boundary or when no suitable local seam exists yet.
- Test performance guardrail: keep expensive runtime fallback work such as snapshotting, migration, installs, or bootstrap behind dedicated `*.runtime.ts` boundaries so tests can mock the seam instead of accidentally invoking real work.
- Test performance guardrail: for import-only/runtime-wrapper tests, keep the wrapper lazy. Do not eagerly load heavy verification/bootstrap/runtime modules at module top level if the exported function can import them on demand.
- Test performance guardrail: prefer explicit mock factories over `importOriginal()` for broad modules. Reserve `importOriginal()` for narrow modules where partial-real behavior is genuinely needed.
- Test performance guardrail: do not partial-mock broad `openclaw/plugin-sdk/*` barrels in hot tests. Add a plugin-local `*.runtime.ts` seam and mock that seam instead.
- Test performance guardrail: when production code already accepts `deps`, callbacks, or runtime injection, use that seam in tests before adding module-level mocks.
- Test performance guardrail: prefer narrow public SDK subpaths such as `models-provider-runtime`, `skill-commands-runtime`, and `reply-dispatch-runtime` over older broad helper barrels when both expose the needed helper.
- Test performance guardrail: treat import-dominated test time as a boundary bug. Refactor the import surface before adding more cases to the slow file.
- 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.
- For targeted/local debugging, use the native root-project entrypoint: `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 the repo's default config/profile/pool routing.
- Do not set test workers above 16; tried already.
-Do not reintroduce Vitest VM pools by default without fresh green evidence on current `main`; keep CI on `forks`.
- 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.
-Vitest now defaults to native root-project `threads`, with hard `forks` exceptions for `gateway`, `agents`, and `commands`. Keep new pool changes explicit and justified; use `OPENCLAW_VITEST_POOL=forks` for full local fork debugging.
- If local Vitest runs cause memory pressure, the default worker budget now derives from host capabilities (CPU, memory band, current load). For a conservative explicit override during land/gate runs, use `OPENCLAW_VITEST_MAX_WORKERS=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.
@@ -177,6 +280,8 @@
- "Bump version everywhere" means all version locations above **except**`appcast.xml` (only touch appcast when cutting a new macOS Sparkle release).
- **Restart apps:** “restart iOS/Android apps” means rebuild (recompile/install) and relaunch, not just kill/launch.
- **Device checks:** before testing, verify connected real devices (iOS/Android) before reaching for simulators/emulators.
- Mobile pairing: `ws://` (cleartext) is allowed for private LAN addresses (RFC 1918, link-local, mDNS `.local`) and loopback. Private LAN hosts typically lack PKI-backed identity, so requiring TLS there adds complexity without meaningful security gain. `wss://` is required for Tailscale and public endpoints.
- Security report scope: reports that treat cleartext `ws://` mobile pairing over private LAN as a vulnerability are out of scope unless they demonstrate a trust-boundary bypass beyond passive network observation on the same LAN.
- 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).
@@ -191,11 +296,12 @@
- 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.
-Carbon: prefer latest published beta over stable when possible; do not switch to stable casually.
- 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.
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.
We cap at **10 open PRs per author**. If you exceed this, the `r: too-many-prs` label is added and your PR is auto-closed. This is a hard limit.
For coordinated change sets that genuinely need more than 10 PRs, join the **#clawtributors** channel in Discord and talk to maintainers first.
## Before You PR
- Test locally with your OpenClaw instance
@@ -94,6 +100,7 @@ Welcome to the lobster tank! 🦞
-`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.
@@ -158,7 +165,10 @@ We are currently prioritizing:
- **Skills**: For skill contributions, head to [ClawHub](https://clawhub.ai/) — the community hub for OpenClaw skills.
- **Performance**: Optimizing token usage and compaction logic.
Check the [GitHub Issues](https://github.com/openclaw/openclaw/issues) for "good first issue" labels!
Check the [GitHub Issues](https://github.com/openclaw/openclaw/issues) for
["good first issue"](https://github.com/openclaw/openclaw/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
labels. If none are open, pick a small docs or bug issue and leave a quick comment saying
**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.
@@ -32,9 +32,58 @@ New install? Start here: [Getting started](https://docs.openclaw.ai/start/gettin
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 +175,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 +199,7 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies.
@@ -56,8 +56,12 @@ These are frequently reported but are typically closed with no code change:
- Authorized user-triggered local actions presented as privilege escalation. Example: an allowlisted/owner sender running `/export-session /absolute/path.html` to write on the host. In this trust model, authorized user actions are trusted host actions unless you demonstrate an auth/sandbox/boundary bypass.
- Reports that only show a malicious plugin executing privileged actions after a trusted operator installs/enables it.
- Reports that assume per-user multi-tenant authorization on a shared gateway host/config.
- Reports that only show quoted/replied/thread/forwarded supplemental context from non-allowlisted senders being visible to the model, without demonstrating an auth, policy, approval, or sandbox boundary bypass.
- Reports that treat the Gateway HTTP compatibility endpoints (`POST /v1/chat/completions`, `POST /v1/responses`) as if they implemented scoped operator auth (`operator.write` vs `operator.admin`). These endpoints authenticate the shared Gateway bearer secret/password and are documented full operator-access surfaces, not per-user/per-scope boundaries.
- Reports that assume `x-openclaw-scopes` can reduce or redefine shared-secret bearer auth on the OpenAI-compatible HTTP endpoints. For shared-secret auth (`gateway.auth.mode="token"` or `"password"`), those endpoints ignore narrower bearer-declared scopes and restore the full default operator scope set plus owner semantics.
- Reports that treat `POST /tools/invoke` under shared-secret bearer auth (`gateway.auth.mode="token"` or `"password"`) as a narrower per-request/per-scope authorization surface. That endpoint is designed as the same trusted-operator HTTP boundary: shared-secret bearer auth is full operator access there, narrower `x-openclaw-scopes` values do not reduce that path, and owner-only tool policy follows the shared-secret operator contract.
- Reports that only show differences in heuristic detection/parity (for example obfuscation-pattern detection on one exec path but not another, such as `node.invoke -> system.run` parity gaps) without demonstrating bypass of auth, approvals, allowlist enforcement, sandboxing, or other documented trust boundaries.
- Reports that only show an ACP tool can indirectly execute, mutate, orchestrate sessions, or reach another tool/runtime without demonstrating bypass of ACP prompt/approval, allowlist enforcement, sandboxing, or another documented trust boundary. ACP silent approval is intentionally limited to narrow readonly classes; parity-only indirect-command findings are hardening, not vulnerabilities.
- ReDoS/DoS claims that require trusted operator configuration input (for example catastrophic regex in `sessionFilter` or `logging.redactPatterns`) without a trust-boundary bypass.
- Archive/install extraction claims that require pre-existing local filesystem priming in trusted state (for example planting symlink/hardlink aliases under destination directories such as skills/tools paths) without showing an untrusted path that can create/control that primitive.
- Reports that depend on replacing or rewriting an already-approved executable path on a trusted host (same-path inode/content swap) without showing an untrusted path to perform that write.
@@ -93,7 +97,15 @@ When patching a GHSA via `gh api`, include `X-GitHub-Api-Version: 2022-11-28` (o
OpenClaw does **not** model one gateway as a multi-tenant, adversarial user boundary.
- Authenticated Gateway callers are treated as trusted operators for that gateway instance.
-The HTTP compatibility endpoints (`POST /v1/chat/completions`, `POST /v1/responses`) are in that same trusted-operator bucket. Passing Gateway bearer auth there is equivalent to operator access for that gateway; they do not implement a narrower`operator.write` vs `operator.admin`trust split.
-Direct localhost/loopback Control UI and Gateway WebSocket sessions authenticated with the shared gateway secret (`token` / `password`) are in that same trusted-operator bucket. Local auto-paired device sessions on that path are expected to retain full localhost operator capability; they do not create a separate`operator.write` vs `operator.admin`security boundary.
- The HTTP compatibility endpoints (`POST /v1/chat/completions`, `POST /v1/responses`) and direct tool endpoint (`POST /tools/invoke`) are in that same trusted-operator bucket. Passing Gateway bearer auth there is equivalent to operator access for that gateway; they do not implement a narrower `operator.write` vs `operator.admin` trust split.
- Concretely, on the OpenAI-compatible HTTP surface:
- shared-secret bearer auth (`token` / `password`) authenticates possession of the gateway operator secret
- those requests receive the full default operator scope set (`operator.admin`, `operator.read`, `operator.write`, `operator.approvals`, `operator.pairing`)
- chat-turn endpoints (`/v1/chat/completions`, `/v1/responses`) also treat those shared-secret callers as owner senders for owner-only tool policy
-`POST /tools/invoke` follows that same shared-secret rule and also treats those callers as owner senders for owner-only tool policy
- narrower `x-openclaw-scopes` headers are ignored for that shared-secret path
- only identity-bearing HTTP modes (for example trusted proxy auth or `gateway.auth.mode="none"` on private ingress) honor declared per-request operator scopes
- Session identifiers (`sessionKey`, session IDs, labels) are routing controls, not per-user authorization boundaries.
- If one operator can view data from another operator on the same gateway, that is expected in this trust model.
- OpenClaw can technically run multiple gateway instances on one machine, but recommended operations are clean separation by trust boundary.
@@ -101,7 +113,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.
@@ -129,6 +141,7 @@ Plugins/extensions are part of OpenClaw's trusted computing base for a gateway.
- Any report whose only claim is that an operator-enabled `dangerous*`/`dangerously*` config option weakens defaults (these are explicit break-glass tradeoffs by design)
- Reports that depend on trusted operator-supplied configuration values to trigger availability impact (for example custom regex patterns). These may still be fixed as defense-in-depth hardening, but are not security-boundary bypasses.
- Reports whose only claim is heuristic/parity drift in command-risk detection (for example obfuscation-pattern checks) across exec surfaces, without a demonstrated trust-boundary bypass. These are hardening-only findings and are not vulnerabilities; triage may close them as `invalid`/`no-action` or track them separately as low/informational hardening.
- Reports whose only claim is that an ACP-exposed tool can indirectly execute commands, mutate host state, or reach another privileged tool/runtime without demonstrating a bypass of ACP prompt/approval, allowlist enforcement, sandboxing, or another documented trust boundary. These are hardening-only findings, not vulnerabilities.
- Reports whose only claim is that exec approvals do not semantically model every interpreter/runtime loader form, subcommand, flag combination, package script, or transitive module/config import. Exec approvals bind exact request context and best-effort direct local file operands; they are not a complete semantic model of everything a runtime may load.
- Exposed secrets that are third-party/user-controlled credentials (not OpenClaw-owned and not granting access to OpenClaw-operated infrastructure/services) without demonstrated OpenClaw impact
- Reports whose only claim is host-side exec when sandbox runtime is disabled/unavailable (documented default behavior in the trusted-operator model), without a boundary bypass.
@@ -156,6 +169,24 @@ OpenClaw's security model is "personal assistant" (one trusted operator, potenti
- For company-shared setups, use a dedicated machine/VM/container and dedicated accounts; avoid mixing personal data on that runtime.
- If that host/browser profile is logged into personal accounts (for example Apple/Google/personal password manager), you have collapsed the boundary and increased personal-data exposure risk.
## Context Visibility and Allowlists
OpenClaw distinguishes:
- **Trigger authorization**: who can trigger the agent (`dmPolicy`, `groupPolicy`, allowlists, mention gates)
- **Context visibility**: what supplemental context is provided to the model (reply body, quoted text, thread history, forwarded metadata)
In current releases, allowlists primarily gate triggering and owner-style command access. They do not guarantee universal supplemental-context redaction across every channel/surface.
Current channel behavior is not fully uniform:
- some channels already filter parts of supplemental context by sender allowlist
- other channels still pass supplemental context as received
Reports that only show supplemental-context visibility differences are typically hardening/consistency findings unless they also demonstrate a documented boundary bypass (auth, policy, approvals, sandbox, or equivalent).
Hardening roadmap may add explicit visibility modes (for example `all`, `allowlist`, `allowlist_quote`) so operators can opt into stricter context filtering with predictable tradeoffs.
## Agent and Model Assumptions
- The model/agent is **not** a trusted principal. Assume prompt/content injection can manipulate behavior.
<li>Config: remove legacy public config aliases such as <code>talk.voiceId</code> / <code>talk.apiKey</code>, <code>agents.*.sandbox.perSession</code>, <code>browser.ssrfPolicy.allowPrivateNetwork</code>, <code>hooks.internal.handlers</code>, and channel/group/room <code>allow</code> toggles in favor of the canonical public paths and <code>enabled</code>, while keeping load-time compatibility and <code>openclaw doctor --fix</code> migration support for existing configs. (#60726) Thanks @vincentkoc.</li>
</ul>
<h3>Changes</h3>
<ul>
<li>Agents/video generation: add the built-in <code>video_generate</code> tool so agents can create videos through configured providers and return the generated media directly in the reply.</li>
<li>Agents/music generation: ignore unsupported optional hints such as <code>durationSeconds</code> with a warning instead of hard-failing requests on providers like Google Lyria.</li>
<li>Providers/ComfyUI: add a bundled <code>comfy</code> workflow media plugin for local ComfyUI and Comfy Cloud workflows, including shared <code>image_generate</code>, <code>video_generate</code>, and workflow-backed <code>music_generate</code> support, with prompt injection, optional reference-image upload, live tests, and output download.</li>
<li>Tools/music generation: add the built-in <code>music_generate</code> tool with bundled Google (Lyria) and MiniMax providers plus workflow-backed Comfy support, including async task tracking and follow-up delivery of finished audio.</li>
<li>Providers: add bundled Qwen, Fireworks AI, and StepFun providers, plus MiniMax TTS, Ollama Web Search, and MiniMax Search integrations for chat, speech, and search workflows. (#60032, #55921, #59318, #54648)</li>
<li>Providers/Amazon Bedrock: add bundled Mantle support plus inference-profile discovery and automatic request-region injection so Bedrock-hosted Claude, GPT-OSS, Qwen, Kimi, GLM, and similar routes work with less manual setup. (#61296, #61299) Thanks @wirjo.</li>
<li>Control UI/multilingual: add localized control UI support for Simplified Chinese, Traditional Chinese, Brazilian Portuguese, German, Spanish, Japanese, Korean, French, Turkish, Indonesian, Polish, and Ukrainian. Thanks @vincentkoc.</li>
<li>Plugins: add plugin-config TUI prompts to guided onboarding/setup flows, and add <code>openclaw plugins install --force</code> so existing plugin and hook-pack targets can be replaced without using the dangerous-code override flag. (#60590, #60544)</li>
<li>Control UI/skills: add ClawHub search, detail, and install flows directly in the Skills panel. (#60134) Thanks @samzong.</li>
<li>iOS/exec approvals: add generic APNs approval notifications that open an in-app exec approval modal, fetch command details only after authenticated operator reconnect, and clear stale notification state when the approval resolves. (#60239) Thanks @ngutman.</li>
<li>Matrix/exec approvals: add Matrix-native exec approval prompts with account-scoped approvers, channel-or-DM delivery, and room-thread aware resolution handling. (#58635) Thanks @gumadeiras.</li>
<li>Channels/context visibility: add configurable <code>contextVisibility</code> per channel (<code>all</code>, <code>allowlist</code>, <code>allowlist_quote</code>) so supplemental quote, thread, and fetched history context can be filtered by sender allowlists instead of always passing through as received.</li>
<li>Providers/request overrides: add shared model and media request transport overrides across OpenAI-, Anthropic-, Google-, and compatible provider paths, including headers, auth, proxy, and TLS controls. (#60200)</li>
<li>Providers/OpenAI: add forward-compat <code>openai-codex/gpt-5.4-mini</code>, an opt-in GPT personality, and provider-owned GPT-5 prompt contributions so Codex/GPT runs stay cache-stable and compatible with bundled catalog lag.</li>
<li>Agents/Claude CLI: expose OpenClaw tools to background Claude CLI runs through a loopback MCP bridge and switch bundled runs to stdin + <code>stream-json</code> partial-message streaming so prompts stop riding argv, long replies show live progress, and final session/usage metadata still land cleanly. (#35676) Thanks @mylukin.</li>
<li>ACPX/runtime: embed the ACP runtime directly in the bundled <code>acpx</code> plugin, remove the extra external ACP CLI hop, harden live ACP session binding and reuse, and add a generic <code>reply_dispatch</code> hook so bundled plugins like ACPX can own reply interception without hardcoded ACP paths in core auto-reply routing. (#61319)</li>
<li>Agents/progress: add experimental structured plan updates and structured execution item events so compatible UIs can show clearer step-by-step progress during long-running runs.</li>
<li>Providers/Anthropic: remove the Claude CLI backend and setup-token from new onboarding, keep existing configured legacy profiles runnable, and have <code>openclaw doctor</code> repair or remove stale <code>anthropic:claude-cli</code> state during migration.</li>
<li>Tools/video generation: add bundled xAI (<code>grok-imagine-video</code>), Alibaba Model Studio Wan, and Runway video providers, plus live-test/default model wiring for all three.</li>
<li>Memory/search: add Amazon Bedrock embeddings for Titan, Cohere, Nova, and TwelveLabs models, with AWS credential-chain auto-detection for <code>provider: "auto"</code> and provider-specific dimension controls. Thanks @wirjo.</li>
<li>Providers/Amazon Bedrock Mantle: generate bearer tokens from the AWS credential chain so Mantle auto-discovery can use IAM auth without manually exporting <code>AWS_BEARER_TOKEN_BEDROCK</code>. Thanks @wirjo.</li>
<li>Memory/dreaming (experimental): add weighted short-term recall promotion, a <code>/dreaming</code> command, Dreams UI, multilingual conceptual tagging, and doctor/status repair support, while refactoring dreaming from competing modes into three cooperative phases (light, deep, REM) with independent schedules and recovery behavior so durable memory promotion can run in the background with less manual setup. (#60569, #60697) Thanks @vignesh07.</li>
<li>Memory/dreaming: add configurable aging controls (<code>recencyHalfLifeDays</code>, <code>maxAgeDays</code>) plus optional verbose logging so operators can tune recall decay and inspect promotion decisions more easily.</li>
<li>Memory/dreaming: add REM preview tooling (<code>openclaw memory rem-harness</code>, <code>promote-explain</code>), surface possible lasting truths during REM staging, and make deep promotion replay-safe so reruns reconcile instead of duplicating <code>MEMORY.md</code> entries.</li>
<li>Memory/dreaming: write dreaming trail content to top-level <code>dreams.md</code> instead of daily memory notes, update <code>/dreaming</code> help text to point there, and keep <code>dreams.md</code> available for explicit reads without pulling it into default recall. Thanks @davemorin.</li>
<li>Memory/dreaming: add the Dream Diary surface in Dreams, simplify user-facing dreaming config to <code>enabled</code> plus optional <code>frequency</code>, treat phases as implementation detail in docs/UI, and keep the lobster animation visible above diary content. Thanks @vignesh07.</li>
<li>Prompt caching: keep prompt prefixes more reusable across transport fallback, deterministic MCP tool ordering, compaction, embedded image history, normalized system-prompt fingerprints, <code>openclaw status --verbose</code> cache diagnostics, and the removal of duplicate in-band tool inventories from agent system prompts so follow-up turns hit cache more reliably. (#58036, #58037, #58038, #59054, #60603, #60691) Thanks @bcherny and @vincentkoc.</li>
<li>Agents/cache: diagnostics: add prompt-cache break diagnostics, trace live cache scenarios through embedded runner paths, and show cache reuse explicitly in <code>openclaw status --verbose</code>. Thanks @vincentkoc.</li>
<li>Agents/cache: stabilize cache-relevant system prompt fingerprints by normalizing equivalent structured prompt whitespace, line endings, hook-added system context, and runtime capability ordering so semantically unchanged prompts reuse KV/cache more reliably. Thanks @vincentkoc.</li>
<li>Agents/tool prompts: remove the duplicate in-band tool inventory from agent system prompts so tool-calling models rely on the structured tool definitions as the single source of truth, improving prompt stability and reducing stale tool guidance.</li>
<li>Config/schema: enrich the exported <code>openclaw config schema</code> JSON Schema with field titles and descriptions so editors, agents, and other schema consumers receive the same config help metadata. (#60067) Thanks @solavrc.</li>
<li>Providers/CLI: remove bundled CLI text-provider backends and the <code>agents.defaults.cliBackends</code> surface, while keeping ACP harness sessions and Gemini media understanding on the native bundled providers.</li>
<li>Matrix/exec approvals: clarify unavailable-approval replies so Matrix no longer claims chat approvals are unsupported when native exec approvals are merely unconfigured. (#61424) Thanks @gumadeiras.</li>
<li>Docs/IRC: replace public IRC hostname examples with <code>irc.example.com</code> and recommend private servers for bot coordination while listing common public networks for intentional use.</li>
<li>Memory/dreaming: group nearby daily-note lines into short coherent chunks before staging them for dreaming, so one-off context from recent notes reaches REM/deep with better evidence and less line-level noise.</li>
<li>Memory/dreaming: drop generic date/day headings from daily-note chunk prefixes while keeping meaningful section labels, so staged snippets stay cleaner and more reusable. (#61597) Thanks @mbelinky.</li>
<li>Plugins/Lobster: run bundled Lobster workflows in process instead of spawning the external CLI, reducing transport overhead and unblocking native runtime integration. (#61523) Thanks @mbelinky.</li>
<li>Plugins/Lobster: harden managed resume validation so invalid TaskFlow resume calls fail earlier, and memoize embedded runtime loading per runner while keeping failed loads retryable. (#61566) Thanks @mbelinky.</li>
</ul>
<h3>Fixes</h3>
<ul>
<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>
<li>Security: preserve restrictive plugin-only tool allowlists, require owner access for <code>/allowlist add</code> and <code>/allowlist remove</code>, fail closed when <code>before_tool_call</code> hooks crash, block browser SSRF redirect bypasses earlier, and keep non-interactive auth-choice inference scoped to bundled and already-trusted plugins. (#58476, #59836, #59822, #58771, #59120) Thanks @eleqtrizit and @pgondhi987.</li>
<li>Providers/OpenAI: make GPT-5 and Codex runs act sooner with lower-verbosity defaults, visible progress during tool work, and a one-shot retry when a turn only narrates the plan instead of taking action.</li>
<li>Providers/OpenAI and reply delivery: preserve native <code>reasoning.effort: "none"</code> and strict schemas where supported, add GPT-5.4 assistant <code>phase</code> metadata across replay and the Gateway <code>/v1/responses</code> layer, and keep commentary buffered until <code>final_answer</code> so web chat, session previews, embedded replies, and Telegram partials stop leaking planning text. Fixes #59150, #59643, #61282.</li>
<li>Telegram: fix current-model checks in the model picker, HTML-format non-default <code>/model</code> confirmations, explicit topic replies, persisted reaction ownership across restarts, caption-media placeholder and <code>file_id</code> preservation on download failure, and upgraded-install inbound image reads. (#60384, #60042, #59634, #59207, #59948, #59971) Thanks @sfuminya, @GitZhangChi, @dashhuang, @samzong, @v1p0r, and @neeravmakwana.</li>
<li>Telegram: restore DM voice-note preflight transcription so direct-message audio stops arriving as raw <code><media:audio></code> placeholders. (#61008) Thanks @manueltarouca.</li>
<li>Telegram/reasoning: only create a Telegram reasoning preview lane when the session is explicitly <code>reasoning:stream</code>, so hidden <code><think></code> traces from streamed replies stop surfacing as chat previews on normal sessions. Thanks @vincentkoc.</li>
<li>Telegram/native command menu: trim long menu descriptions before dropping commands so sub-100 command sets can still fit Telegram's payload budget and keep more <code>/</code> entries visible. (#61129) Thanks @neeravmakwana.</li>
<li>Discord: keep REST, webhook, and monitor traffic on the configured proxy, preserve component-only media sends, honor <code>@everyone</code> and <code>@here</code> mention gates, keep ACK reactions on the active account, and split voice connect/playback timeouts so auto-join is more reliable. (#57465, #60361, #60345) Thanks @geekhuashan.</li>
<li>Discord/reply tags: strip leaked <code>[[reply_to_current]]</code> control tags from preview text and honor explicit reply-tag threading during final delivery, so Discord replies stay attached to the triggering message instead of printing reply metadata into chat.</li>
<li>Discord/replies: replace the unshipped <code>replyToOnlyWhenBatched</code> flag with <code>replyToMode: "batched"</code> so native reply references only attach on debounced multi-message turns while explicit reply tags still work.</li>
<li>Discord/image generation: include the real generated <code>MEDIA:</code> paths intool output, avoid duplicate plain-output media requeueing, and persist volatile workspace-generated media into durable outbound media before final reply delivery so generated image replies stop pointing at missing local files.</li>
<li>Slack: route live DM replies back to the concrete inbound DM channel while keeping persisted routing metadata user-scoped, so normal assistant replies stop disappearing when pairing and system messages still arrive. (#59030) Thanks @afurm.</li>
<li>WhatsApp: restore <code>channels.whatsapp.blockStreaming</code> and reset watchdog timeouts after reconnect so quiet chats stop falling into reconnect loops. (#60007, #60069) Thanks @MonkeyLeeT and @mcaxtr.</li>
<li>Android/Talk Mode: cancel in-flight <code>talk.speak</code> playback when speech is explicitly stopped, and restore spoken replies on both node-scoped and gateway-backed sessions by keeping reply routing and embedded transport overrides aligned with the current playback path. (#60306, #61164, #61214)</li>
<li>Voice-call/OpenAI: pass full plugin config into realtime transcription provider resolution so streaming calls can discover the bundled OpenAI realtime transcription provider again. Fixes #60936. Thanks @sliekens and @vincentkoc.</li>
<li>Matrix/exec approvals: anchor seeded approval reactions to the primary Matrix prompt event, resolve them from event metadata instead of prompt text, and clean up chunked approval prompts correctly. (#60931) Thanks @gumadeiras.</li>
<li>Matrix: recover more reliably when secret storage or recovery keys are missing by recreating secret storage during repair and backup reset, hold crypto snapshot locks during persistence, and surface explicit too-large attachment markers. (#59846, #59851, #60599, #60289) Thanks @al3mart, @emonty, and @efe-arv.</li>
<li>Matrix/DM sessions: add <code>channels.matrix.dm.sessionScope</code>, shared-session collision notices, and aligned outbound session reuse so separate Matrix DM rooms can keep distinct context when configured. (#61373) Thanks @gumadeiras.</li>
<li>Matrix: move legacy top-level <code>avatarUrl</code> into the default account during multi-account promotion and keep env-backed account setup avatar config persisted. (#61437) Thanks @gumadeiras.</li>
<li>MS Teams: download inline DM images via Graph API and preserve channel reply threading in proactive fallback. (#52212, #55198) Thanks @Ted-developer and @hyojin.</li>
<li>MS Teams: replace the deprecated Teams SDK HttpPlugin stub with <code>httpServerAdapter</code> so recurring gateway deprecation warnings stop firing and the Express 5 compatibility workaround stays on the supported SDK path. (#60939) Thanks @coolramukaka-sys.</li>
<li>Control UI/chat: add a per-session thinking-level picker in the chat header and mobile chat settings, and keep the browser bundle on UI-local thinking/session-key helpers so Safari no longer crashes on Node-only imports before rendering chat controls.</li>
<li>Sandbox/SSH: reject hardlinked files during cross-device rename fallback so EXDEV file copies preserve the same pinned file-boundary checks as direct reads.</li>
<li>Control UI: keep Stop visible during tool-only execution, preserve pending-send busy state, and clear stale ClawHub search results as soon as the query changes. (#54528, #59800, #60267) Thanks @chziyue and @frankekn.</li>
<li>Control UI/avatar: honor <code>ui.assistant.avatar</code> when serving <code>/avatar/:agentId</code> so Appearance UI avatar paths stop falling back to initials placeholders. (#60778) Thanks @hannasdev.</li>
<li>Control UI/cron: highlight the Cron refresh button while refresh is in flight so the page's loading state stays visible even when prior data remains on screen. (#60394) Thanks @coder-zhuzm.</li>
<li>Control UI/Overview: prevent gateway access token/password visibility toggle buttons from overlapping their inputs at narrow widths. (#56924) Thanks @bbddbb1.</li>
<li>Auto-reply: unify reply lifecycle ownership across preflight compaction, session rotation, CLI-backed runs, and gateway restart handling so <code>/stop</code> and same-session overlap checks target the right active turn and restart-interrupted turns return the restart notice instead of being silently dropped. (#61267) Thanks @dutifulbob.</li>
<li>Reply delivery: prevent duplicate block replies on <code>text_end</code> channels so providers that emit explicit text-end boundaries no longer double-send the same final message. (#61530)</li>
<li>Gateway/startup: default <code>gateway.mode</code> to <code>local</code> when unset, detect PID recycling in gateway lock files on Windows and macOS, and show startup progress so healthy restarts stop getting blocked by stale locks. (#54801, #60085, #59843) Thanks @BradGroux and @TonyDerek-dot.</li>
<li>Gateway/macOS: let launchd <code>KeepAlive</code> own in-process gateway restarts again, adding a short supervised-exit delay so rapid restarts avoid launchd crash-loop unloads while <code>openclaw gateway restart</code> still reports real LaunchAgent errors synchronously.</li>
<li>Gateway/macOS: re-bootstrap the LaunchAgent if <code>launchctl kickstart -k</code> unloads it during restart so failed restarts do not leave the gateway unmanaged until manual repair.</li>
<li>Gateway/macOS: recover installed-but-unloaded LaunchAgents during <code>openclaw gateway start</code> and <code>restart</code>, while still preferring live unmanaged gateways during restart recovery. (#43766) Thanks @HenryC-3.</li>
<li>Gateway/Windows scheduled tasks: preserve Task Scheduler settings on reinstall, fail loudly when <code>/Run</code> does not start, and report fast failed restarts accurately instead of pretending they timed out after 60 seconds. (#59335) Thanks @tmimmanuel.</li>
<li>Windows/restart: fall back to the installed Startup-entry launcher when the scheduled task was never registered, so <code>/restart</code> can relaunch the gateway on Windows setups where <code>schtasks</code> install fell back during onboarding. (#58943) Thanks @imechZhangLY.</li>
<li>Windows/restart: clean up stale gateway listeners before Windows self-restart and treat listener and argv probe failures as inconclusive, so scheduled-task relaunch no longer falls into an <code>EADDRINUSE</code> retry loop. (#60480) Thanks @arifahmedjoy.</li>
<li>Update/npm: prefer the npm binary that owns the installed global OpenClaw prefix so mixed Homebrew-plus-nvm setups update the right install. (#60153) Thanks @jayeshp19.</li>
<li>Agents/music and video generation: add <code>tools.media.asyncCompletion.directSend</code> as an opt-in direct-delivery path for finished async media tasks, while keeping the legacy requester-session wake/model-delivery flow as the default.</li>
<li>CLI/skills JSON: route <code>skills list --json</code>, <code>skills info --json</code>, and <code>skills check --json</code> output to stdout instead of stderr so machine-readable consumers receive JSON on the expected stream again. (#60914; fixes #57599; landed from contributor PR #57611 by @Aftabbs) Thanks @Aftabbs.</li>
<li>CLI/Commander: preserve Commander-computed exit codes for argument and help-error paths, and cover the user-argv parse mode in the regression tests so invalid CLI invocations no longer report success when exits are intercepted. (#60923) Thanks @Linux2010.</li>
<li>Cron: replay interrupted recurring jobs on the first gateway restart instead of waiting for a second restart. (#60583) Thanks @joelnishanth.</li>
<li>Cron: send failure notifications through the job's primary delivery channel using the same session context as successful delivery when no explicit <code>failureDestination</code> is configured. (#60622) Thanks @artwalker.</li>
<li>Exec/remote skills: stop advertising <code>exec host=node</code> when the current exec policy cannot route to a node, and clarify blocked exec-host override errors with both the requested host and allowed config path.</li>
<li>Agents/Claude CLI/security: clear inherited Claude Code config-root and plugin-root env overrides like <code>CLAUDE_CONFIG_DIR</code> and <code>CLAUDE_CODE_PLUGIN_*</code>, so OpenClaw-launched Claude CLI runs cannot be silently pointed at an alternate Claude config/plugin tree with different hooks, plugins, or auth context. Thanks @vincentkoc.</li>
<li>Agents/Claude CLI/security: clear inherited Claude Code provider-routing and managed-auth env overrides, and mark OpenClaw-launched Claude CLI runs as host-managed, so Claude CLI backdoor sessions cannot be silently redirected to proxy, Bedrock, Vertex, Foundry, or parent-managed token contexts. Thanks @vincentkoc.</li>
<li>Agents/Claude CLI/security: force host-managed Claude CLI backdoor runs to <code>--setting-sources user</code>, even under custom backend arg overrides, so repo-local <code>.claude</code> project/local settings, hooks, and plugin discovery do not silently execute inside non-interactive OpenClaw sessions. Thanks @vincentkoc.</li>
<li>Agents/Claude CLI: treat malformed bare <code>--permission-mode</code> backend overrides as missing and fail safe back to <code>bypassPermissions</code>, so custom <code>cliBackends.claude-cli.args</code> security config cannot accidentally consume the next flag as a bogus permission mode. Thanks @vincentkoc.</li>
<li>Gateway/device pairing: require non-admin paired-device sessions to manage only their own device for token rotate/revoke and paired-device removal, blocking cross-device token theft inside pairing-scoped sessions. (#50627) Thanks @coygeek.</li>
<li>Gateway/plugin routes: keep gateway-auth plugin runtime routes on write-only fallback scopes unless a trusted-proxy caller explicitly declares narrower <code>x-openclaw-scopes</code>, so plugin HTTP handlers no longer mint admin-level runtime scopes on missing or untrusted HTTP scope headers. (#59815) Thanks @pgondhi987.</li>
<li>Build/types: fix the Node <code>createRequire(...)</code> helper typing so provider-runtime lazy loads compile cleanly again and <code>pnpm build</code> no longer fails in the Pi embedded provider error-pattern path.</li>
<li>Gateway/security: scope loopback browser-origin auth throttling by normalized origin so one localhost Control UI tab cannot lock out a different localhost browser origin after repeated auth failures.</li>
<li>Gateway/auth: serialize async shared-secret auth attempts per client so concurrent Tailscale-capable failures cannot overrun the intended auth rate-limit budget. Thanks @Telecaster2147.</li>
<li>Device pairing/security: keep non-operator device scope checks bound to the requested role prefix so bootstrap verification cannot redeem <code>operator.*</code> scopes through <code>node</code> auth. (#57258) Thanks @jlapenna.</li>
<li>Device pairing: reject rotating device tokens into roles that were never approved during pairing, and keep reconnect role checks bounded to the paired device's approved role set. (#60462) Thanks @eleqtrizit.</li>
<li>Gateway/device auth: reuse cached device-token scopes only for cached-token reconnects, while keeping explicit <code>deviceToken</code> scope requests and empty-cache fallbacks intact so reconnects preserve <code>operator.read</code> without breaking explicit auth flows. (#46032) Thanks @caicongyang.</li>
<li>Mobile pairing/security: fail closed for internal <code>/pair</code> setup-code issuance, cleanup, and approval paths when gateway pairing scopes are missing, and keep approval-time requested-scope enforcement on the internal command path. (#55996) Thanks @coygeek.</li>
<li>Mobile pairing/bootstrap: keep QR bootstrap handoff tokens bounded to the mobile-safe contract so node handoff stays unscoped and operator handoff drops mixed <code>node.*</code>, <code>operator.admin</code>, and <code>operator.pairing</code> scopes.</li>
<li>Mobile pairing/Android: tighten secure endpoint handling so Tailscale and public remote setup reject cleartext endpoints, private LAN pairing still works, merged-role approvals mint both node and operator device tokens, and bootstrap tokens survive node auto-pair until operator approval finishes. (#60128, #60208, #60221) Thanks @obviyus.</li>
<li>Android/canvas security: require exact normalized A2UI URL matches before forwarding canvas bridge actions, rejecting query mismatches and descendant paths while still allowing fragment-only A2UI navigation.</li>
<li>Synology Chat/security: default low-level HTTPS helper TLS verification to on so helper/API defaults match the shipped safe account default, and only explicit <code>allowInsecureSsl: true</code> opts out.</li>
<li>Synology Chat/security: route webhook token comparison through the shared constant-time secret helper for consistency with other bundled plugins.</li>
<li>Plugins/marketplace: block remote marketplace symlink escapes without breaking ordinary local marketplace install paths. (#60556) Thanks @eleqtrizit.</li>
<li>Telegram/local Bot API: honor <code>channels.telegram.apiRoot</code> for buffered media downloads, add <code>channels.telegram.network.dangerouslyAllowPrivateNetwork</code> for trusted fake-IP setups, and require <code>channels.telegram.trustedLocalFileRoots</code> before reading absolute Bot API <code>file_path</code> values. (#59544, #60705) Thanks @SARAMALI15792 and @obviyus.</li>
<li>Outbound/sanitizer: strip leaked <code><tool_call></code>, <code><function_calls></code>, and model special tokens from shared user-visible assistant text, including truncated tool-call streams, so internal scaffolding no longer bleeds into replies across surfaces. (#60619) Thanks @oliviareid-svg.</li>
<li>Agents/errors: surface an explicit disk-full message when local session or transcript writes fail with <code>ENOSPC</code>/<code>disk full</code>, so those runs stop degrading into opaque <code>NO_REPLY</code>-style failures. Thanks @vincentkoc.</li>
<li>Exec approvals: remove heuristic command-obfuscation gating from host exec so gateway and node runs rely on explicit policy, allowlist, and strict inline-eval rules only.</li>
<li>Agents/tool results: cap live tool-result persistence and overflow-recovery truncation at 40k characters so oversized tool output stays bounded without discarding recent context entirely.</li>
<li>Discord/video replies: split text-plus-video deliveries into a text reply followed by a media-only send, and let live provider auth checks honor manifest-declared API key env vars like <code>MODELSTUDIO_API_KEY</code>.</li>
<li>Config/All Settings: keep the raw config view intact when sensitive fields are blank instead of corrupting or dropping the rendered snapshot. (#28214) Thanks @solodmd.</li>
<li>Plugin SDK/facades: back-fill bundled plugin facade sentinels before plugin-id tracking re-enters config loading, so CLI/provider startup no longer crashes with <code>shouldNormalizeGoogleProviderConfig is not a function</code> or other empty-facade reads during bundled plugin re-entry. Thanks @adam91holt.</li>
<li>Plugins/facades: back-fill facade sentinels before tracked-plugin resolution re-enters config loading, so facade exports stay defined during circular provider normalization. (#61180) Thanks @adam91holt.</li>
<li>QA lab: restore typed mock OpenAI gateway config wiring so QA-lab config helpers compile cleanly again and <code>pnpm check</code> / <code>pnpm build</code> stay green.</li>
<li>Discord/image generation: include the real generated <code>MEDIA:</code> paths in tool output and avoid duplicate plain-output media requeueing so Discord image replies stop pointing at missing local files.</li>
<li>Slack: route live DM replies back to the concrete inbound DM channel while keeping persisted routing metadata user-scoped, so normal assistant replies stop disappearing when pairing and system messages still arrive. (#59030) Thanks @afurm.</li>
<li>Discord/reply tags: strip leaked <code>[[reply_to_current]]</code> control tags from preview text and honor explicit reply-tag threading during final delivery, so Discord replies stay attached to the triggering message instead of printing reply metadata into chat.</li>
<li>Telegram: fix current-model checks in the model picker, HTML-format non-default <code>/model</code> confirmations, explicit topic replies, persisted reaction ownership across restarts, caption-media placeholder and <code>file_id</code> preservation on download failure, and upgraded-install inbound image reads. (#60384, #60042, #59634, #59207, #59948, #59971) Thanks @sfuminya, @GitZhangChi, @dashhuang, @samzong, @v1p0r, and @neeravmakwana.</li>
<li>Telegram: restore DM voice-note preflight transcription so direct-message audio stops arriving as raw <code><media:audio></code> placeholders. (#61008) Thanks @manueltarouca.</li>
<li>Telegram/reasoning: only create a Telegram reasoning preview lane when the session is explicitly <code>reasoning:stream</code>, so hidden <code><think></code> traces from streamed replies stop surfacing as chat previews on normal sessions. Thanks @vincentkoc.</li>
<li>Telegram/native command menu: trim long menu descriptions before dropping commands so sub-100 command sets can still fit Telegram's payload budget and keep more <code>/</code> entries visible. (#61129) Thanks @neeravmakwana.</li>
<li>Feishu/reasoning: only expose streamed reasoning previews when the session is explicitly <code>reasoning:stream</code>, so hidden reasoning traces do not surface on normal streaming sessions. Thanks @vincentkoc.</li>
<li>Discord: keep REST, webhook, and monitor traffic on the configured proxy, preserve component-only media sends, honor <code>@everyone</code> and <code>@here</code> mention gates, keep ACK reactions on the active account, and split voice connect/playback timeouts so auto-join is more reliable. (#57465, #60361, #60345) Thanks @geekhuashan.</li>
<li>WhatsApp: restore <code>channels.whatsapp.blockStreaming</code> and reset watchdog timeouts after reconnect so quiet chats stop falling into reconnect loops. (#60007, #60069) Thanks @MonkeyLeeT and @mcaxtr.</li>
<li>Memory: keep <code>memory-core</code> builtin embedding registration on the already-registered path so selecting <code>memory-core</code> no longer recurses through plugin discovery and crashes during startup. (#61402) Thanks @ngutman.</li>
<li>Agents/tool results: keep large <code>read</code> outputs visible longer, preserve the latest <code>read</code> output when older tool output can absorb the overflow budget, and fall back to Pi's normal overflow compaction/retry path before replacing a fresh <code>read</code> with a compacted stub. Thanks @vincentkoc.</li>
<li>Memory/QMD: prefer modern <code>qmd collection add --glob</code>, accept newer single-line JSON hit metadata while keeping legacy line fields, refresh QMD docs/doctor install guidance and model-override guidance, and keep older QMD releases working. Thanks @vincentkoc.</li>
<li>MS Teams: download inline DM images via Graph API and preserve channel reply threading in proactive fallback. (#52212, #55198) Thanks @Ted-developer and @hyojin.</li>
<li>MS Teams: replace the deprecated Teams SDK HttpPlugin stub with <code>httpServerAdapter</code> so recurring gateway deprecation warnings stop firing and the Express 5 compatibility workaround stays on the supported SDK path. (#60939) Thanks @coolramukaka-sys.</li>
<li>Matrix/exec approvals: anchor seeded approval reactions to the primary Matrix prompt event, resolve them from event metadata instead of prompt text, and clean up chunked approval prompts correctly. (#60931) Thanks @gumadeiras.</li>
<li>Matrix: recover more reliably when secret storage or recovery keys are missing by recreating secret storage during repair and backup reset, hold crypto snapshot locks during persistence, and surface explicit too-large attachment markers. (#59846, #59851, #60599, #60289) Thanks @al3mart, @emonty, and @efe-arv.</li>
<li>Android/Talk Mode: cancel in-flight <code>talk.speak</code> playback when speech is explicitly stopped, so stale replies stop starting after barge-in or manual stop. (#61164) Thanks @obviyus.</li>
<li>Android/Talk Mode: restore spoken assistant replies on node-scoped sessions by keeping reply routing synced to the resolved node session key and pausing mic capture during reply playback. (#60306) Thanks @MKV21.</li>
<li>Android/Talk Mode: restore voice replies on gateway-backed talk mode sessions by updating embedded runner transport overrides to the current agent transport API. (#61214) Thanks @obviyus.</li>
<li>Voice-call/OpenAI: pass full plugin config into realtime transcription provider resolution so streaming calls can discover the bundled OpenAI realtime transcription provider again. Fixes #60936. Thanks @sliekens and @vincentkoc.</li>
<li>Control UI/chat: add a per-session thinking-level picker in the chat header and mobile chat settings, and keep the browser bundle on UI-local thinking/session-key helpers so Safari no longer crashes on Node-only imports before rendering chat controls.</li>
<li>Control UI: keep Stop visible during tool-only execution, preserve pending-send busy state, and clear stale ClawHub search results as soon as the query changes. (#54528, #59800, #60267) Thanks @chziyue and @frankekn.</li>
<li>Control UI/avatar: honor <code>ui.assistant.avatar</code> when serving <code>/avatar/:agentId</code> so Appearance UI avatar paths stop falling back to initials placeholders. (#60778) Thanks @hannasdev.</li>
<li>Control UI/cron: highlight the Cron refresh button while refresh is in flight so the page's loading state stays visible even when prior data remains on screen. (#60394) Thanks @coder-zhuzm.</li>
<li>Control UI/Overview: prevent gateway access token/password visibility toggle buttons from overlapping their inputs at narrow widths. (#56924) Thanks @bbddbb1.</li>
<li>CLI/skills JSON: route <code>skills list --json</code>, <code>skills info --json</code>, and <code>skills check --json</code> output to stdout instead of stderr so machine-readable consumers receive JSON on the expected stream again. (#60914; fixes #57599; landed from contributor PR #57611 by @Aftabbs) Thanks @Aftabbs.</li>
<li>CLI/Commander: preserve Commander-computed exit codes for argument and help-error paths, and cover the user-argv parse mode in the regression tests so invalid CLI invocations no longer report success when exits are intercepted. (#60923) Thanks @Linux2010.</li>
<li>Cron: replay interrupted recurring jobs on the first gateway restart instead of waiting for a second restart. (#60583) Thanks @joelnishanth.</li>
<li>Cron: send failure notifications through the job's primary delivery channel using the same session context as successful delivery when no explicit <code>failureDestination</code> is configured. (#60622) Thanks @artwalker.</li>
<li>Live model switching: only treat explicit user-driven model changes as pending live switches, so fallback rotation, heartbeat overrides, and compaction no longer trip <code>LiveSessionModelSwitchError</code> before making an API call. (#60266) Thanks @kiranvk-2011.</li>
<li>Exec approvals: reuse durable exact-command <code>allow-always</code> approvals in allowlist mode so identical reruns stop prompting, and tighten Windows interpreter/path approval handling so wrapper and malformed-path cases fail closed more consistently. (#59880, #59780, #58040, #59182) Thanks @luoyanglang, @SnowSky1, and @pgondhi987.</li>
<li>Node exec approvals: keep node-host <code>system.run</code> approvals bound to the prepared execution plan across async forwarding, so mutable script operands still get approval-time binding and drift revalidation instead of dropping back to unbound execution.</li>
<li>Agents/exec approvals: let <code>exec-approvals.json</code> agent security override stricter gateway tool defaults so approved subagents can use <code>security: “full”</code> without falling back to allowlist enforcement again. (#60310) Thanks @lml2468.</li>
<li>Agents/exec: restore <code>host=node</code> routing for node-pinned and <code>host=auto</code> sessions, while still blocking sandboxed <code>auto</code> sessions from jumping to gateway. (#60788) Thanks @openperf.</li>
<li>Exec/heartbeat: use the canonical <code>exec-event</code> wake reason for <code>notifyOnExit</code> so background exec completions still trigger follow-up turns when <code>HEARTBEAT.md</code> is empty or comments-only. (#41479) Thanks @rstar327.</li>
<li>Heartbeat: skip wake delivery when the target session lane is already busy so the pending event is retried instead of getting drained too early. (#40526) Thanks @lucky7323.</li>
<li>Group chats/agent prompts: tell models to minimize empty lines and use normal chat-style spacing so group replies avoid document-style blank-line formatting.</li>
<li>Providers/OpenAI GPT: treat short approval turns like <code>ok do it</code> and <code>go ahead</code> as immediate action turns, and trim overly memo-like GPT-5 chat confirmations so OpenAI replies stay shorter and more conversational by default.</li>
<li>Providers/OpenAI Codex: split native <code>contextWindow</code> from runtime <code>contextTokens</code>, keep the default effective cap at <code>272000</code>, and expose a per-model <code>contextTokens</code> override on <code>models.providers.*.models[]</code>.</li>
<li>Providers/OpenAI-compatible WS: compute fallback token totals from normalized usage when providers omit or zero <code>total_tokens</code>, so DashScope-compatible sessions stop storing zero totals after alias normalization. (#54940) Thanks @lyfuci.</li>
<li>Agents/OpenAI: mark Claude-compatible file tool schemas as <code>additionalProperties: false</code> so direct OpenAI GPT-5 routes stop rejecting the <code>read</code> tool with invalid strict-schema errors.</li>
<li>Agents/OpenAI: fall back to <code>strict: false</code> for native OpenAI tool calls when a tool schema is not strict-compatible, and normalize empty-object tool schemas to include <code>required: []</code>, so direct GPT-5 routes stop failing with invalid strict-schema errors like missing <code>path</code> in <code>required</code>.</li>
<li>Agents/GPT: add explicit work-item lifecycle events for embedded runs, use them to surface real progress more reliably, and stop counting tool-started turns as planning-only retries.</li>
<li>Plugins/OpenAI: enable <code>gpt-image-1</code> reference-image edits through <code>/images/edits</code> multipart uploads, and stop inferring unsupported resolution overrides when no explicit <code>size</code> or <code>resolution</code> is provided.</li>
<li>Agents/replay: remove the malformed assistant-content canonicalization repair from replay history sanitization instead of extending that legacy repair path into replay validation.</li>
<li>Plugins/OpenAI: tune the OpenAI prompt overlay for live-chat cadence so GPT replies stay shorter, more human, and less wall-of-text by default.</li>
<li>Providers/compat: stop forcing OpenAI-only defaults on proxy and custom OpenAI-compatible routes, preserve native vendor-specific reasoning/tool/streaming behavior across Anthropic-compatible, Moonshot, Mistral, ModelStudio, OpenRouter, xAI, and Z.ai endpoints, and route GitHub Copilot Claude models through Anthropic Messages instead of OpenAI Responses.</li>
<li>Providers/GitHub Copilot: send IDE identity headers on runtime model requests and GitHub token exchange so IDE-authenticated Copilot runs stop failing with missing <code>Editor-Version</code>. (#60641) Thanks @VACInc and @vincentkoc.</li>
<li>Providers/OpenRouter failover: classify <code>403 “Key limit exceeded”</code> spending-limit responses as billing so model fallback continues instead of stopping on generic auth. (#59892) Thanks @rockcent.</li>
<li>Providers/Anthropic: keep <code>claude-cli/*</code> auth on live Claude CLI credentials at runtime, avoid persisting stale bearer-token profiles, and suppress macOS Keychain prompts during non-interactive Claude CLI setup. (#61234) Thanks @darkamenosa.</li>
<li>Providers/Anthropic: when Claude CLI auth becomes the default, write a real <code>claude-cli</code> auth profile so local and gateway agent runs can use Claude CLI immediately without missing-API-key failures. Thanks @vincentkoc.</li>
<li>Providers/Anthropic Vertex: honor <code>cacheRetention: “long”</code> with the real 1-hour prompt-cache TTL on Vertex AI endpoints, and default <code>anthropic-vertex</code> cache retention like direct Anthropic. (#60888) Thanks @affsantos.</li>
<li>Agents/Anthropic: preserve native <code>toolu_*</code> replay ids on direct Anthropic and Anthropic Vertex paths so cache-sensitive history stops rewriting known-valid Anthropic tool-use ids. (#52612)</li>
<li>Providers/Google: add model-level <code>cacheRetention</code> support for direct Gemini system prompts by creating, reusing, and refreshing <code>cachedContents</code> automatically on Google AI Studio runs. (#51372) Thanks @rafaelmariano-glitch.</li>
<li>Google Gemini CLI auth: detect bundled npm installs by scanning packaged bundle files for the Gemini OAuth client config, so <code>npm install -g @google/gemini-cli</code> layouts work again. (#60486) Thanks @wzfmini01.</li>
<li>Google Gemini CLI auth: detect personal OAuth mode from local Gemini settings and skip Code Assist project discovery for those logins, so personal Google accounts stop failing with <code>loadCodeAssist 400 Bad Request</code>. (#49226) Thanks @bobworrall.</li>
<li>Google Gemini CLI auth: improve OAuth credential discovery across Windows nvm and Homebrew libexec installs, and align Code Assist metadata so Gemini login stops failing on packaged CLI layouts. (#40729) Thanks @hughcube.</li>
<li>Google Gemini CLI models: add forward-compat support for stable <code>gemini-2.5-*</code> model ids by letting the bundled CLI provider clone them from Google templates, so <code>gemini-2.5-flash-lite</code> and related configured models stop showing up as missing. (#35274) Thanks @mySebbe.</li>
<li>Google image generation: disable pinned DNS for Gemini image requests and honor explicit <code>pinDns</code> overrides in shared provider HTTP helpers so proxy-backed image generation works again. (#59873) Thanks @luoyanglang.</li>
<li>Providers/Microsoft Foundry: preserve explicit image capability on normalized Foundry deployments, repair stale GPT/o-series text-only model metadata across gateway and runtime paths, and keep unknown fallback models from borrowing unrelated image support.</li>
<li>Providers/Model Studio: preserve native streaming usage reporting for DashScope-compatible endpoints even when they are configured under a generic provider key, so streamed token totals stop sticking at zero. (#52395) Thanks @IVY-AI-gif.</li>
<li>Providers/Z.AI: preserve explicitly registered <code>glm-5-*</code> variants like <code>glm-5-turbo</code> instead of intercepting them with the generic GLM-5 forward-compat shim. (#48185) Thanks @haoyu-haoyu.</li>
<li>Amazon Bedrock/aws-sdk auth: stop injecting the fake <code>AWS_PROFILE</code> apiKey marker when no AWS auth env vars exist, so instance-role and other default-chain setups keep working without poisoning provider config. (#61194) Thanks @wirjo.</li>
<li>Agents/Kimi tool-call repair: preserve tool arguments that were already present on streamed tool calls when later malformed deltas fail reevaluation, while still dropping stale repair-only state before <code>toolcall_end</code>.</li>
<li>Plugins/Kimi Coding: parse tagged tool calls and keep Anthropic-native tool payloads so Kimi coding endpoints execute tools instead of echoing raw markup. (#60051, #60391) Thanks @obviyus and @Eric-Guo.</li>
<li>Media understanding: auto-register image-capable config providers for vision routing, so custom GLM-style provider ids with image models stop failing with “no media-understanding provider registered”. (#51418) Thanks @xydt-610.</li>
<li>Plugins/media understanding: enable bundled Groq and Deepgram providers by default so configured transcription models work without extra plugin activation config. (#59982) Thanks @yxjsxy.</li>
<li>MiniMax/pricing: keep bundled MiniMax highspeed pricing distinct in provider catalogs and preserve the lower M2.5 cache-read pricing when onboarding older MiniMax models. (#54214) Thanks @octo-patch.</li>
<li>MiniMax: advertise image input on bundled <code>MiniMax-M2.7</code> and <code>MiniMax-M2.7-highspeed</code> model definitions so image-capable flows can route through the M2.7 family correctly. (#54843) Thanks @MerlinMiao88888888.</li>
<li>Models/MiniMax: honor <code>MINIMAX_API_HOST</code> for implicit bundled MiniMax provider catalogs so China-hosted API-key setups pick <code>api.minimaxi.com/anthropic</code> without manual provider config. (#34524) Thanks @caiqinghua.</li>
<li>Usage/MiniMax: invert remaining-style <code>usage_percent</code> fields when MiniMax reports only remaining percentage data, so usage bars stop showing nearly-full remaining quota as nearly-exhausted usage. (#60254) Thanks @jwchmodx.</li>
<li>Usage/MiniMax: let usage snapshots treat <code>minimax-portal</code> and MiniMax CN aliases as the same MiniMax quota surface, and prefer stored MiniMax OAuth before falling back to Coding Plan keys.</li>
<li>Usage/MiniMax: prefer the chat-model <code>model_remains</code> entry and derive Coding Plan window labels from MiniMax interval timestamps so MiniMax usage snapshots stop picking zero-budget media rows and misreporting 4h windows as <code>5h</code>. (#52349) Thanks @IVY-AI-gif.</li>
<li>Model picker/providers: treat bundled BytePlus and Volcengine plan aliases as their native providers during setup, and expose their bundled standard/coding catalogs before auth so setup can suggest the right models. (#58819) Thanks @Luckymingxuan.</li>
<li>Tools/web_search (Kimi): when <code>tools.web.search.kimi.baseUrl</code> is unset, inherit native Moonshot chat <code>baseUrl</code> (<code>.ai</code> / <code>.cn</code>) so China console keys authenticate on the same host as chat. Fixes #44851. (#56769) Thanks @tonga54.</li>
<li>Agents/Claude CLI: keep non-interactive <code>--permission-mode bypassPermissions</code> when custom <code>cliBackends.claude-cli.args</code> override defaults, including fallback resolution before the runtime plugin registry is active, so cron and heartbeat Claude CLI runs do not regress to interactive approval mode. (#61114) Thanks @cathrynlavery and @thewilloftheshadow.</li>
<li>Agents/Claude CLI: persist explicit <code>openclaw agent --session-id</code> runs under a stable session key so follow-ups can reuse the stored CLI binding and resume the same underlying Claude session.</li>
<li>Agents/Claude CLI: persist routed Claude session bindings, rotate them on <code>/new</code> and <code>/reset</code>, and keep live Claude CLI model switches moving across the configured Claude family so resumed sessions follow the real active thread and model. Thanks @vincentkoc.</li>
<li>Agents/CLI backends: invalidate stored CLI session reuse when local CLI login state or the selected auth profile credential changes, so relogin and token rotation stop resuming stale sessions.</li>
<li>Agents/Claude CLI/images: reuse stable hydrated image file paths and preserve shared media extensions like HEIC when passing image refs to local CLI runs, so Claude CLI image prompts stop thrashing KV cache prefixes and oddball image formats do not fall back to <code>.bin</code>. Thanks @vincentkoc.</li>
<li>Agents/compaction: keep assistant tool calls and displaced tool results in the same compaction chunk so strict summarization providers stop rejecting orphaned tool pairs. (#58849) Thanks @openperf.</li>
<li>Agents/failover: scope Anthropic <code>An unknown error occurred</code> failover matching by provider so generic internal unknown-error text no longer triggers retryable timeout fallback. (#59325) Thanks @aaron-he-zhu.</li>
<li>Agents/subagents: honor allowlist validation, auth-profile handoff, and session override state when a subagent retries after <code>LiveSessionModelSwitchError</code>. (#58178) Thanks @openperf.</li>
<li>Agents/runtime: make default subagent allowlists, inherited skills/workspaces, and duplicate session-id resolution behave more predictably, and include value-shape hints in missing-parameter tool errors. (#59944, #59992, #59858, #55317) Thanks @hclsys, @gumadeiras, @joelnishanth, and @priyansh19.</li>
<li>Agents/pairing: merge completion announce delivery context with the requester session fallback so missing <code>to</code> still reaches the original channel, and include <code>operator.talk.secrets</code> in CLI default operator scopes for node-role device pairing approvals. (#56481) Thanks @maxpetrusenko.</li>
<li>Agents/scheduling: steer background-now work toward automatic completion wake and treat <code>process</code> polling as on-demand inspection or intervention instead of default completion handling. (#60877) Thanks @vincentkoc.</li>
<li>Agents/skills: skip <code>.git</code> and <code>node_modules</code> when mirroring skills into sandbox workspaces so read-only sandboxes do not copy repo history or dependency trees. (#61090) Thanks @joelnishanth.</li>
<li>ACP/agents: inherit the target agent workspace for cross-agent ACP spawns and fall back safely when the inherited workspace no longer exists. (#58438) Thanks @zssggle-rgb.</li>
<li>ACPX/Windows: preserve backslashes and absolute <code>.exe</code> paths in Claude CLI parsing, and fail fast on wrapper-script targets with guidance to use <code>cmd.exe /c</code>, <code>powershell.exe -File</code>, or <code>node <script></code>. (#60689) Thanks @steipete.</li>
<li>Auth/failover: persist selected fallback overrides before retrying, shorten <code>auth_permanent</code> lockouts, and refresh websocket/shared-auth sessions only when real auth changes occur so retries and secret rotations behave predictably. (#60404, #60323, #60387) Thanks @extrasmall0 and @mappel-nv.</li>
<li>Gateway/channels: pin the initial startup channel registry before later plugin-registry churn so configured channels stay visible and <code>channels.status</code> stops falling back to empty <code>channelOrder</code> / <code>channels</code> payloads after runtime plugin loads.</li>
<li>Prompt caching: order stable workspace project-context files before <code>HEARTBEAT.md</code> and keep <code>HEARTBEAT.md</code> below the system-prompt cache boundary so heartbeat churn does not invalidate the stable project-context prefix. (#58979) Thanks @yozu and @vincentkoc.</li>
<li>Prompt caching: route Codex Responses and Anthropic Vertex through boundary-aware cache shaping, and report the actual outbound system prompt in cache traces so cache reuse and misses line up with what providers really receive. Thanks @vincentkoc.</li>
<li>Agents/cache: preserve the full 3-turn prompt-cache image window across tool loops, keep colliding bundled MCP tool definitions deterministic, and reapply Anthropic Vertex cache shaping after payload hook replacements so KV/cache reuse stays stable. Thanks @vincentkoc.</li>
<li>Status/cache: restore <code>cacheRead</code> and <code>cacheWrite</code> in transcript fallback so <code>/status</code> keeps showing cache hit percentages when session logs are the only complete usage source. (#59247) Thanks @stuartsy.</li>
<li>Status/usage: let <code>/status</code> and <code>session_status</code> fall back to transcript token totals when the session meta store stayed at zero, so LM Studio, Ollama, DashScope, and similar OpenAI-compatible providers stop showing <code>Context: 0/...</code>. (#55041) Thanks @jjjojoj.</li>
<li>Mattermost/config schema: accept <code>groups.*.requireMention</code> again so existing Mattermost configs no longer fail strict validation after upgrade. (#58271) Thanks @MoerAI.</li>
<li>Doctor/config: compare normalized <code>talk</code> configs by deep structural equality instead of key-order-sensitive serialization so <code>openclaw doctor --fix</code> stops repeatedly reporting/applying no-op <code>talk.provider/providers</code> normalization. (#59911) Thanks @ejames-dev.</li>
<li>Anthropic CLI onboarding: rewrite migrated fallback model refs during non-interactive Claude CLI setup too, so onboarding and scripted setup no longer keep stale <code>anthropic/*</code> fallbacks after switching the primary model to <code>claude-cli/*</code>. Thanks @vincentkoc.</li>
<li>Models/Anthropic CLI auth: replace migrated <code>agents.defaults.models</code> allowlists when <code>openclaw models auth login --provider anthropic --method cli --set-default</code> switches to <code>claude-cli/*</code>, so stale <code>anthropic/*</code> entries do not linger beside the migrated Claude CLI defaults. Thanks @vincentkoc.</li>
<li>Doctor/Claude CLI: add dedicated Claude CLI health checks so <code>openclaw doctor</code> can spot missing local installs or broken auth before agent runs fail. Thanks @vincentkoc.</li>
<li>Plugins/auth-choice: apply provider-owned auth config patches without recursively preserving replaced default-model maps, so Anthropic Claude CLI and similar migrations can intentionally swap model allowlists during onboarding and setup instead of accumulating stale entries. Thanks @vincentkoc.</li>
<li>Plugins/onboarding: write dotted plugin uiHint paths like Brave <code>webSearch.mode</code> as nested plugin config so <code>llm-context</code> setup stops failing validation. (#61159) Thanks @obviyus.</li>
<li>Plugins/install: preserve unsafe override flags across linked plugin and hook-pack probes so local <code>--link</code> installs honor the documented override behavior. (#60624) Thanks @JerrettDavis.</li>
<li>Plugins/cache: inherit the active gateway workspace for provider, web-search, and web-fetch snapshot loads when callers omit <code>workspaceDir</code>, so compatible plugin registries and snapshot caches stop missing on gateway-owned runtime paths. (#61138) Thanks @jzakirov.</li>
<li>Plugin SDK/context engines: export the missing context-engine result and subagent lifecycle types from <code>openclaw/plugin-sdk</code> so context engine plugins can type <code>ContextEngine</code> implementations without local workarounds. (#61251) Thanks @DaevMithran.</li>
<li>Tasks/maintenance: reconcile stale cron and chat-backed CLI task rows against live cron-job and agent-run ownership instead of treating any persisted session key as proof that the task is still running. (#60310) Thanks @lml2468.</li>
<li>Plugins: suppress trust-warning noise during non-activating snapshot and CLI metadata loads. (#61427) Thanks @gumadeiras.</li>
<li>Agents/video generation: accept <code>agents.defaults.videoGenerationModel</code> in strict config validation and <code>openclaw config set/get</code>, so gateways using <code>video_generate</code> no longer fail to boot after enabling a video model.</li>
<li>Matrix/streaming: add a quiet preview mode for streamed Matrix replies, keep legacy <code>partial</code> preview-first behavior, and finalize quiet media captions correctly so previews stop notifying early without dropping final text semantics. (#61450) Thanks @gumadeiras.</li>
<li>Gateway/shutdown: bound websocket-server shutdown even when no tracked clients remain, so gateway restarts stop hanging until the watchdog kills the process. (#61565) Thanks @mbelinky.</li>
<li>Control UI/multilingual: localize the remaining shared channel, instances, nodes, and gateway-confirmation strings so the dashboard stops mixing translated UI with hardcoded English labels. Thanks @vincentkoc.</li>
<li>Discord/media: raise the default inbound and outbound media cap to <code>100MB</code> so Discord matches Telegram more closely and larger attachments stop failing on the old low default.</li>
<li>Matrix: keep direct transport requests on the pinned dispatcher by routing them through undici runtime fetch, so Matrix clients resume syncing on newer runtimes without dropping the validated address binding. (#61595) Thanks @gumadeiras.</li>
<li>Plugins/facades: resolve globally installed bundled-plugin runtime facades from registry roots so bundled channels like LINE still boot when the winning plugin install lives under the global extensions directory with an encoded scoped folder name. (#61297) Thanks @openperf.</li>
</ul>
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
<li>Plugins/xAI: move <code>x_search</code> settings from the legacy core <code>tools.web.x_search.*</code> path to the plugin-owned <code>plugins.entries.xai.config.xSearch.*</code> path, standardize <code>x_search</code> auth on <code>plugins.entries.xai.config.webSearch.apiKey</code> / <code>XAI_API_KEY</code>, and migrate legacy config with <code>openclaw doctor --fix</code>. (#59674) Thanks @vincentkoc.</li>
<li>Plugins/web fetch: move Firecrawl <code>web_fetch</code> config from the legacy core <code>tools.web.fetch.firecrawl.*</code> path to the plugin-owned <code>plugins.entries.firecrawl.config.webFetch.*</code> path, route <code>web_fetch</code> fallback through the new fetch-provider boundary instead of a Firecrawl-only core branch, and migrate legacy config with <code>openclaw doctor --fix</code>. (#59465) Thanks @vincentkoc.</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>Tasks/Task Flow: restore the core Task Flow substrate with managed-vs-mirrored sync modes, durable flow state/revision tracking, and <code>openclaw flows</code> inspection/recovery primitives so background orchestration can persist and be operated separately from plugin authoring layers. (#58930) Thanks @mbelinky.</li>
<li>Tasks/Task Flow: add managed child task spawning plus sticky cancel intent, so external orchestrators can stop scheduling immediately and let parent Task Flows settle to <code>cancelled</code> once active child tasks finish. (#59610) Thanks @mbelinky.</li>
<li>Plugins/Task Flow: add a bound <code>api.runtime.taskFlow</code> seam so plugins and trusted authoring layers can create and drive managed Task Flows from host-resolved OpenClaw context without passing owner identifiers on each call. (#59622) Thanks @mbelinky.</li>
<li>Android/assistant: add assistant-role entrypoints plus Google Assistant App Actions metadata so Android can launch OpenClaw from the assistant trigger and hand prompts into the chat composer. (#59596) Thanks @obviyus.</li>
<li>Exec defaults: make gateway/node host exec default to YOLO mode by requesting <code>security=full</code> with <code>ask=off</code>, and align host approval-file fallbacks plus docs/doctor reporting with that no-prompt default.</li>
<li>Providers/runtime: add provider-owned replay hook surfaces for transcript policy, replay cleanup, and reasoning-mode dispatch. (#59143) Thanks @jalehman.</li>
<li>Plugins/hooks: add <code>before_agent_reply</code> so plugins can short-circuit the LLM with synthetic replies after inline actions. (#20067) Thanks @JoshuaLelon.</li>
<li>Channels/session routing: move provider-specific session conversation grammar into plugin-owned session-key surfaces, preserving Telegram topic routing and Feishu scoped inheritance across bootstrap, model override, restart, and tool-policy paths.</li>
<li>Feishu/comments: add a dedicated Drive comment-event flow with comment-thread context resolution, in-thread replies, and <code>feishu_drive</code> comment actions for document collaboration workflows. (#58497) Thanks @wittam-01.</li>
<li>Matrix/plugin: emit spec-compliant <code>m.mentions</code> metadata across text sends, media captions, edits, poll fallback text, and action-driven edits so Matrix mentions notify reliably in clients like Element. (#59323) Thanks @gumadeiras.</li>
<li>Diffs: add plugin-owned <code>viewerBaseUrl</code> so viewer links can use a stable proxy/public origin without passing <code>baseUrl</code> on every tool call. (#59341) Related #59227. Thanks @gumadeiras.</li>
<li>Agents/compaction: resolve <code>agents.defaults.compaction.model</code> consistently for manual <code>/compact</code> and other context-engine compaction paths, so engine-owned compaction uses the configured override model across runtime entrypoints. (#56710) Thanks @oliviareid-svg.</li>
<li>Agents/compaction: add <code>agents.defaults.compaction.notifyUser</code> so the <code>🧹 Compacting context...</code> start notice is opt-in instead of always being shown. (#54251) Thanks @oguricap0327.</li>
<li>WhatsApp/reactions: add <code>reactionLevel</code> guidance for agent reactions. Thanks @mcaxtr.</li>
<li>Exec approvals/channels: auto-enable DM-first native chat approvals when supported channels can infer approvers from existing owner config, while keeping channel fanout explicit and clarifying forwarding versus native approval client config.</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>Providers/transport policy: centralize request auth, proxy, TLS, and header shaping across shared HTTP, stream, and websocket paths, block insecure TLS/runtime transport overrides, and keep proxy-hop TLS separate from target mTLS settings. (#59682) Thanks @vincentkoc.</li>
<li>Providers/Copilot: classify native GitHub Copilot API hosts in the shared provider endpoint resolver and harden token-derived proxy endpoint parsing so Copilot base URL routing stays centralized and fails closed on malformed hints. (#59644) Thanks @vincentkoc.</li>
<li>Providers/streaming headers: centralize default and attribution header merging across OpenAI websocket, embedded-runner, and proxy stream paths so provider-specific headers stay consistent and caller overrides only win where intended. (#59542) Thanks @vincentkoc.</li>
<li>Providers/media HTTP: centralize base URL normalization, default auth/header injection, and explicit header override handling across shared OpenAI-compatible audio, Deepgram audio, Gemini media/image, and Moonshot video request paths. (#59469) Thanks @vincentkoc.</li>
<li>Providers/OpenAI-compatible routing: centralize native-vs-proxy request policy so hidden attribution and related OpenAI-family defaults only apply on verified native endpoints across stream, websocket, and shared audio HTTP paths. (#59433) Thanks @vincentkoc.</li>
<li>Providers/Anthropic routing: centralize native-vs-proxy endpoint classification for direct Anthropic <code>service_tier</code> handling so spoofed or proxied hosts do not inherit native Anthropic defaults. (#59608) Thanks @vincentkoc.</li>
<li>Gateway/exec loopback: restore legacy-role fallback for empty paired-device token maps and allow silent local role upgrades so local exec and node clients stop failing with pairing-required errors after <code>2026.3.31</code>. (#59092) Thanks @openperf.</li>
<li>Agents/subagents: pin admin-only subagent gateway calls to <code>operator.admin</code> while keeping <code>agent</code> at least privilege, so <code>sessions_spawn</code> no longer dies on loopback scope-upgrade pairing with <code>close(1008) "pairing required"</code>. (#59555) Thanks @openperf.</li>
<li>Exec approvals/config: strip invalid <code>security</code>, <code>ask</code>, and <code>askFallback</code> values from <code>~/.openclaw/exec-approvals.json</code> during normalization so malformed policy enums fall back cleanly to the documented defaults instead of corrupting runtime policy resolution. (#59112) Thanks @openperf.</li>
<li>Exec approvals/doctor: report host policy sources from the real approvals file path and ignore malformed host override values when attributing effective policy conflicts. (#59367) Thanks @gumadeiras.</li>
<li>Exec/runtime: treat <code>tools.exec.host=auto</code> as routing-only, keep implicit no-config exec on sandbox when available or gateway otherwise, and reject per-call host overrides that would bypass the configured sandbox or host target. (#58897) Thanks @vincentkoc.</li>
<li>Slack/mrkdwn formatting: add built-in Slack mrkdwn guidance in inbound context so Slack replies stop falling back to generic Markdown patterns that render poorly in Slack. (#59100) Thanks @jadewon.</li>
<li>WhatsApp/presence: send <code>unavailable</code> presence on connect in self-chat mode so personal-phone users stop losing all push notifications while the gateway is running. (#59410) Thanks @mcaxtr.</li>
<li>WhatsApp/media: add HTML, XML, and CSS to the MIME map and fallback gracefully for unknown media types instead of dropping the attachment. (#51562) Thanks @bobbyt74.</li>
<li>Matrix/onboarding: restore guided setup in <code>openclaw channels add</code> and <code>openclaw configure --section channels</code>, while keeping custom plugin wizards on the shared <code>setupWizard</code> seam. (#59462) Thanks @gumadeiras.</li>
<li>Matrix/streaming: keep live partial previews for the current assistant block while preserving completed block updates as separate messages when <code>channels.matrix.blockStreaming</code> is enabled. (#59384) Thanks @gumadeiras.</li>
<li>Feishu/comment threads: harden document comment-thread delivery so whole-document comments fallback to <code>add_comment</code>, delayed reply lookups retry more reliably, and user-visible replies avoid reasoning/planning spillover. (#59129) Thanks @wittam-01.</li>
<li>MS Teams/streaming: strip already-streamed text from fallback block delivery when replies exceed the 4000-character streaming limit so long responses stop duplicating content. (#59297) Thanks @bradgroux.</li>
<li>Slack/thread context: filter thread starter and history by the effective conversation allowlist without dropping valid open-room, DM, or group DM context. (#58380) Thanks @jacobtomlinson.</li>
<li>Mattermost/probes: route status probes through the SSRF guard and honor <code>allowPrivateNetwork</code> so connectivity checks stay safe for self-hosted Mattermost deployments. (#58529) Thanks @mappel-nv.</li>
<li>Zalo/webhook replay: scope replay dedupe key by chat and sender so reused message IDs across different chats or senders no longer collide, and harden metadata reads for partially missing payloads. (#58444)</li>
<li>QQBot/structured payloads: restrict local file paths to QQ Bot-owned media storage, block traversal outside that root, reduce path leakage in logs, and keep inline image data URLs working. (#58453) Thanks @jacobtomlinson.</li>
<li>Image generation/providers: route OpenAI, MiniMax, and fal image requests through the shared provider HTTP transport path so custom base URLs, guarded private-network routing, and provider request defaults stay aligned with the rest of provider HTTP. Thanks @vincentkoc.</li>
<li>Image generation/providers: stop inferring private-network access from configured OpenAI, MiniMax, and fal image base URLs, and cap shared HTTP error-body reads so hostile or misconfigured endpoints fail closed without relaxing SSRF policy or buffering unbounded error payloads. Thanks @vincentkoc.</li>
<li>Browser/host inspection: keep static Chrome inspection helpers out of the activated browser runtime so <code>openclaw doctor browser</code> and related checks do not eagerly load the bundled browser plugin. (#59471) Thanks @vincentkoc.</li>
<li>Browser/CDP: normalize trailing-dot localhost absolute-form hosts before loopback checks so remote CDP websocket URLs like <code>ws://localhost.:...</code> rewrite back to the configured remote host. (#59236) Thanks @mappel-nv.</li>
<li>Agents/output sanitization: strip namespaced <code>antml:thinking</code> blocks from user-visible text so Anthropic-style internal monologue tags do not leak into replies. (#59550) Thanks @obviyus.</li>
<li>Kimi Coding/tools: normalize Anthropic tool payloads into the OpenAI-compatible function shape Kimi Coding expects so tool calls stop losing required arguments. (#59440) Thanks @obviyus.</li>
<li>Image tool/paths: resolve relative local media paths against the agent <code>workspaceDir</code> instead of <code>process.cwd()</code> so inputs like <code>inbox/receipt.png</code> pass the local-path allowlist reliably. (#57222) Thanks Priyansh Gupta.</li>
<li>Podman/launch: remove noisy container output from <code>scripts/run-openclaw-podman.sh</code> and align the Podman install guidance with the quieter startup flow. (#59368) Thanks @sallyom.</li>
<li>Plugins/runtime: keep LINE reply directives and browser-backed cleanup/reset flows working even when those plugins are disabled while tightening bundled plugin activation guards. (#59412) Thanks @vincentkoc.</li>
<li>ACP/gateway reconnects: keep ACP prompts alive across transient websocket drops while still failing boundedly when reconnect recovery does not complete. (#59473) Thanks @obviyus.</li>
<li>ACP/gateway reconnects: reject stale pre-ack ACP prompts after reconnect grace expiry so callers fail cleanly instead of hanging indefinitely when the gateway never confirms the run.</li>
<li>Gateway/session kill: enforce HTTP operator scopes on session kill requests and gate authorization before session lookup so unauthenticated callers cannot probe session existence. (#59128) Thanks @jacobtomlinson.</li>
<li>MS Teams/logging: format non-<code>Error</code> failures with the shared unknown-error helper so logs stop collapsing caught SDK or Axios objects into <code>[object Object]</code>. (#59321) Thanks @bradgroux.</li>
<li>Channels/setup: ignore untrusted workspace channel plugins during setup resolution so a shadowing workspace plugin cannot override built-in channel setup/login flows unless explicitly trusted in config. (#59158) Thanks @mappel-nv.</li>
<li>Exec/Windows: restore allowlist enforcement with quote-aware <code>argPattern</code> matching across gateway and node exec, and surface accurate dynamic pre-approved executable hints in the exec tool description. (#56285) Thanks @kpngr.</li>
<li>Gateway: prune empty <code>node-pending-work</code> state entries after explicit acknowledgments and natural expiry so the per-node state map no longer grows indefinitely. (#58179) Thanks @gavyngong.</li>
<li>Webhooks/secret comparison: replace ad-hoc timing-safe secret comparisons across BlueBubbles, Feishu, Mattermost, Telegram, Twilio, and Zalo webhook handlers with the shared <code>safeEqualSecret</code> helper and reject empty auth tokens in BlueBubbles. (#58432) Thanks @eleqtrizit.</li>
<li>OpenShell/mirror: constrain <code>remoteWorkspaceDir</code> and <code>remoteAgentWorkspaceDir</code> to the managed <code>/sandbox</code> and <code>/agent</code> roots, and keep mirror sync from overwriting or removing user-added shell roots during config synchronization. (#58515) Thanks @eleqtrizit.</li>
<li>Plugins/activation: preserve explicit, auto-enabled, and default activation provenance plus reason metadata across CLI, gateway bootstrap, and status surfaces so plugin enablement state stays accurate after auto-enable resolution. (#59641) Thanks @vincentkoc.</li>
<li>Exec/env: block additional host environment override pivots for package roots, language runtimes, compiler include paths, and credential/config locations so request-scoped exec cannot redirect trusted toolchains or config lookups. (#59233) Thanks @drobison00.</li>
<li>Dotenv/workspace overrides: block workspace <code>.env</code> files from overriding <code>OPENCLAW_PINNED_PYTHON</code> and <code>OPENCLAW_PINNED_WRITE_PYTHON</code> so trusted helper interpreters cannot be redirected by repo-local env injection. (#58473) Thanks @eleqtrizit.</li>
<li>Plugins/install: accept JSON5 syntax in <code>openclaw.plugin.json</code> and bundle <code>plugin.json</code> manifests during install/validation, so third-party plugins with trailing commas, comments, or unquoted keys no longer fail to install. (#59084) Thanks @singleGanghood.</li>
<li>Telegram/exec approvals: rewrite shared <code>/approve … allow-always</code> callback payloads to <code>/approve … always</code> before Telegram button rendering so plugin approval IDs still fit Telegram's <code>callback_data</code> limit and keep the Allow Always action visible. (#59217) Thanks @jameslcowan.</li>
<li>Cron/exec timeouts: surface timed-out <code>exec</code> and <code>bash</code> failures in isolated cron runs even when <code>verbose: off</code>, including custom session-target cron jobs, so scheduled runs stop failing silently. (#58247) Thanks @skainguyen1412.</li>
<li>Telegram/exec approvals: fall back to the origin session key for async approval followups and keep resume-failure status delivery sanitized so Telegram followups still land without leaking raw exec metadata. (#59351) Thanks @seonang.</li>
<li>Node-host/exec approvals: bind <code>pnpm dlx</code> invocations through the approval planner's mutable-script path so the effective runtime command is resolved for approval instead of being left unbound. (#58374)</li>
<li>Exec/node hosts: stop forwarding the gateway workspace cwd to remote node exec when no workdir was explicitly requested, so cross-platform node approvals fall back to the node default cwd instead of failing with <code>SYSTEM_RUN_DENIED</code>. (#58977) Thanks @Starhappysh.</li>
<li>Exec approvals/channels: decouple initiating-surface approval availability from native delivery enablement so Telegram, Slack, and Discord still expose approvals when approvers exist and native target routing is configured separately. (#59776) Thanks @joelnishanth.</li>
</ul>
<h3>Changes</h3>
<ul>
<li>macOS/Voice Wake: add the Voice Wake option to trigger Talk Mode. (#58490) Thanks @SmoothExec.</li>
<li>Tasks/chat: add <code>/tasks</code> as a chat-native background task board for the current session, with recent task details and agent-local fallback counts when no linked tasks are visible. Related #54226. Thanks @vincentkoc.</li>
<li>Web search/SearXNG: add the bundled SearXNG provider plugin for <code>web_search</code> with configurable host support. (#57317) Thanks @cgdusek.</li>
<li>Telegram/errors: add configurable <code>errorPolicy</code> and <code>errorCooldownMs</code> controls so Telegram can suppress repeated delivery errors per account, chat, and topic without muting distinct failures. (#51914) Thanks @chinar-amrutkar</li>
<li>Gateway/webchat: make <code>chat.history</code> text truncation configurable with <code>gateway.webchat.chatHistoryMaxChars</code> and per-request <code>maxChars</code>, while preserving silent-reply filtering and existing default payload limits. (#58900)</li>
<li>Amazon Bedrock/Guardrails: add Bedrock Guardrails support to the bundled provider. (#58588) Thanks @MikeORed.</li>
<li>ZAI/models: add <code>glm-5.1</code> and <code>glm-5v-turbo</code> to the bundled Z.AI provider catalog. (#58793) Thanks @tomsun28</li>
<li>Agents/default params: add <code>agents.defaults.params</code> for global default provider parameters. (#58548) Thanks @lpender.</li>
<li>Agents/failover: cap prompt-side and assistant-side same-provider auth-profile retries for rate-limit failures before cross-provider model fallback, add the <code>auth.cooldowns.rateLimitedProfileRotations</code> knob, and document the new fallback behavior. (#58707) Thanks @Forgely3D</li>
<li>Agents/compaction: resolve <code>agents.defaults.compaction.model</code> consistently for manual <code>/compact</code> and other context-engine compaction paths, so engine-owned compaction uses the configured override model across runtime entrypoints. (#56710) Thanks @oliviareid-svg</li>
<li>Chat/error replies: stop leaking raw provider/runtime failures into external chat channels, return a friendly retry message instead, and add a specific <code>/new</code> hint for Bedrock toolResult/toolUse session mismatches. (#58831) Thanks @ImLukeF.</li>
<li>Sessions/model switching: keep <code>/model</code> changes queued behind busy runs instead of interrupting the active turn, and retarget queued followups so later work picks up the new model as soon as the current turn finishes.</li>
<li>Web UI/OpenResponses: preserve rewritten stream snapshots in webchat and keep OpenResponses final streamed text aligned when models rewind earlier output. (#58641) Thanks @neeravmakwana</li>
<li>Discord/inbound media: pass Discord attachment and sticker downloads through the shared idle-timeout and worker-abort path so slow or stuck inbound media fetches stop hanging message processing. (#58593) Thanks @aquaright1</li>
<li>Telegram/retries: keep non-idempotent sends on the strict safe-send path, retry wrapped pre-connect failures, and preserve <code>429</code> / <code>retry_after</code> backoff for safe delivery retries. (#51895) Thanks @chinar-amrutkar</li>
<li>Telegram/exec approvals: route topic-aware exec approval followups through Telegram-owned threading and approval-target parsing, so forum-topic approvals stay in the originating topic instead of falling back to the root chat. (#58783)</li>
<li>Telegram/local Bot API: preserve media MIME types for absolute-path downloads so local audio files still trigger transcription and other MIME-based handling. (#54603) Thanks @jzakirov</li>
<li>Channels/WhatsApp: pass inbound message timestamp to model context so the AI can see when WhatsApp messages were sent. (#58590) Thanks @Maninae</li>
<li>QQBot/voice: lazy-load <code>silk-wasm</code> in <code>audio-convert.ts</code> so qqbot still starts when the optional voice dependency is missing, while voice encode/decode degrades gracefully instead of crashing at module load time. (#58829) Thanks @WideLee.</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>Tasks/chat: add <code>/tasks</code> as a chat-native background task board for the current session, with recent task details and agent-local fallback counts when no linked tasks are visible. Related #54226. Thanks @vincentkoc.</li>
<li>Web search/SearXNG: add the bundled SearXNG provider plugin for <code>web_search</code> with configurable host support. (#57317) Thanks @cgdusek.</li>
<li>Amazon Bedrock/Guardrails: add Bedrock Guardrails support to the bundled provider. (#58588) Thanks @MikeORed.</li>
<li>macOS/Voice Wake: add the Voice Wake option to trigger Talk Mode. (#58490) Thanks @SmoothExec.</li>
<li>Feishu/comments: add a dedicated Drive comment-event flow with comment-thread context resolution, in-thread replies, and <code>feishu_drive</code> comment actions for document collaboration workflows. (#58497) Thanks @wittam-01.</li>
<li>Gateway/webchat: make <code>chat.history</code> text truncation configurable with <code>gateway.webchat.chatHistoryMaxChars</code> and per-request <code>maxChars</code>, while preserving silent-reply filtering and existing default payload limits. (#58900)</li>
<li>Agents/default params: add <code>agents.defaults.params</code> for global default provider parameters. (#58548) Thanks @lpender.</li>
<li>Agents/failover: cap prompt-side and assistant-side same-provider auth-profile retries for rate-limit failures before cross-provider model fallback, add the <code>auth.cooldowns.rateLimitedProfileRotations</code> knob, and document the new fallback behavior. (#58707) Thanks @Forgely3D</li>
<li>Channels/session routing: move provider-specific session conversation grammar into plugin-owned session-key surfaces, preserving Telegram topic routing and Feishu scoped inheritance across bootstrap, model override, restart, and tool-policy paths.</li>
<li>WhatsApp/reactions: add <code>reactionLevel</code> guidance for agent reactions. Thanks @mcaxtr.</li>
<li>Telegram/errors: add configurable <code>errorPolicy</code> and <code>errorCooldownMs</code> controls so Telegram can suppress repeated delivery errors per account, chat, and topic without muting distinct failures. (#51914) Thanks @chinar-amrutkar</li>
<li>ZAI/models: add <code>glm-5.1</code> and <code>glm-5v-turbo</code> to the bundled Z.AI provider catalog. (#58793) Thanks @tomsun28</li>
<li>Agents/compaction: resolve <code>agents.defaults.compaction.model</code> consistently for manual <code>/compact</code> and other context-engine compaction paths, so engine-owned compaction uses the configured override model across runtime entrypoints. (#56710) Thanks @oliviareid-svg</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>Chat/error replies: stop leaking raw provider/runtime failures into external chat channels, return a friendly retry message instead, and add a specific <code>/new</code> hint for Bedrock toolResult/toolUse session mismatches. (#58831) Thanks @ImLukeF.</li>
<li>Gateway/reload: ignore startup config writes by persisted hash in the config reloader so generated auth tokens and seeded Control UI origins do not trigger a restart loop, while real <code>gateway.auth.*</code> edits still require restart. (#58678) Thanks @yelog</li>
<li>Tasks/gateway: keep the task registry maintenance sweep from stalling the gateway event loop under synchronous SQLite pressure, so upgraded gateways stop hanging about a minute after startup. (#58670) Thanks @openperf</li>
<li>Tasks/status: hide stale completed background tasks from <code>/status</code> and <code>session_status</code>, prefer live task context, and show recent failures only when no active work remains. (#58661) Thanks @vincentkoc</li>
<li>Tasks/gateway: re-check the current task record before maintenance marks runs lost or prunes them, so a task heartbeat or cleanup update that lands during a sweep no longer gets overwritten by stale snapshot state.</li>
<li>Exec/approvals: honor <code>exec-approvals.json</code> security defaults when inline or configured tool policy is unset, and keep Slack and Discord native approval handling aligned with inferred approvers and real channel enablement so remote exec stops falling into false approval timeouts and disabled states. Thanks @scoootscooob and @vincentkoc.</li>
<li>Exec/approvals: make <code>allow-always</code> persist as durable user-approved trust instead of behaving like <code>allow-once</code>, reuse exact-command trust on shell-wrapper paths that cannot safely persist an executable allowlist entry, keep static allowlist entries from silently bypassing <code>ask:"always"</code>, and require explicit approval when Windows cannot build an allowlist execution plan instead of hard-dead-ending remote exec. Thanks @scoootscooob and @vincentkoc.</li>
<li>Exec/cron: resolve isolated cron no-route approval dead-ends from the effective host fallback policy when trusted automation is allowed, and make <code>openclaw doctor</code> warn when <code>tools.exec</code> is broader than <code>~/.openclaw/exec-approvals.json</code> so stricter host-policy conflicts are explicit. Thanks @scoootscooob and @vincentkoc.</li>
<li>Sessions/model switching: keep <code>/model</code> changes queued behind busy runs instead of interrupting the active turn, and retarget queued followups so later work picks up the new model as soon as the current turn finishes.</li>
<li>Gateway/HTTP: skip failing HTTP request stages so one broken facade no longer forces every HTTP endpoint to return 500. (#58746) Thanks @yelog</li>
<li>Gateway/nodes: stop pinning live node commands to the approved node-pair record. Node pairing remains a trust/token flow, while per-node <code>system.run</code> policy stays in that node's exec approvals config. Fixes #58824.</li>
<li>WebChat/exec approvals: use native approval UI guidance in agent system prompts instead of telling agents to paste manual <code>/approve</code> commands in webchat sessions. Thanks @vincentkoc.</li>
<li>Web UI/OpenResponses: preserve rewritten stream snapshots in webchat and keep OpenResponses final streamed text aligned when models rewind earlier output. (#58641) Thanks @neeravmakwana</li>
<li>Discord/inbound media: pass Discord attachment and sticker downloads through the shared idle-timeout and worker-abort path so slow or stuck inbound media fetches stop hanging message processing. (#58593) Thanks @aquaright1</li>
<li>Telegram/retries: keep non-idempotent sends on the strict safe-send path, retry wrapped pre-connect failures, and preserve <code>429</code> / <code>retry_after</code> backoff for safe delivery retries. (#51895) Thanks @chinar-amrutkar</li>
<li>Telegram/exec approvals: route topic-aware exec approval followups through Telegram-owned threading and approval-target parsing, so forum-topic approvals stay in the originating topic instead of falling back to the root chat. (#58783)</li>
<li>Telegram/local Bot API: preserve media MIME types for absolute-path downloads so local audio files still trigger transcription and other MIME-based handling. (#54603) Thanks @jzakirov</li>
<li>Channels/WhatsApp: passinbound message timestamp to model context so the AI can see when WhatsApp messages were sent. (#58590) Thanks @Maninae</li>
<li>Channels/QQ Bot: keep <code>/bot-logs</code> export gated behind a truly explicit QQBot allowlist, rejecting wildcard and mixed wildcard entries while preserving the real framework command path. Thanks @vincentkoc.</li>
<li>Channels/plugins: keep bundled channel plugins loadable from legacy <code>channels.<id></code> config even under restrictive plugin allowlists, and make <code>openclaw doctor</code> warn only on real plugin blockers instead of misleading setup guidance. (#58873) Thanks @obviyus</li>
<li>Plugins/bundled runtimes: restore externalized bundled plugin runtime dependency staging across packed installs, Docker builds, and local runtime staging so bundled plugins keep their declared runtime deps after the 2026.3.31 externalization change. (#58782)</li>
<li>LINE/runtime: resolve the packaged runtime contract from the built <code>dist/plugins/runtime</code> layout so LINE channels start correctly again after global npm installs on <code>2026.3.31</code>. (#58799) Thanks @vincentkoc.</li>
<li>MiniMax/plugins: auto-enable the bundled MiniMax plugin for API-key auth/config so MiniMax image generation and other plugin-owned capabilities load without manual plugin allowlisting. (#57127) Thanks @tars90percent.</li>
<li>Ollama/model picker: show only Ollama models after provider selection in the CLI picker. (#55290) Thanks @Luckymingxuan.</li>
<li>CDP/profiles: prefer <code>cdpPort</code> over stale WebSocket URLs so browser automation reconnects cleanly. (#58499) Thanks @Mlightsnow.</li>
<li>Media/paths: resolve relative <code>MEDIA</code> paths against the agent workspace so local attachment references keep working. (#58624) Thanks @aquaright1.</li>
<li>Memory/session indexing: keep full reindexes from skipping session transcripts when sync is triggered by <code>session-start</code> or <code>watch</code>, so restart-driven reindexes preserve session memory. (#39732) Thanks @upupc</li>
<li>Memory/QMD: prefer <code>--mask</code> over <code>--glob</code> when creating QMD collections so default memory collections keep their intended patterns and stop colliding on restart. (#58643) Thanks @GitZhangChi.</li>
<li>Subagents/tasks: keep subagent completion and cleanup from crashing when task-registry writes fail, so a corrupt or missing task row no longer takes down the gateway during lifecycle finalization. Thanks @vincentkoc.</li>
<li>Sandbox/browser: compare browser runtime inspection against <code>agents.defaults.sandbox.browser.image</code> so <code>openclaw sandbox list --browser</code> stops reporting healthy browser containers as image mismatches. (#58759) Thanks @sandpile.</li>
<li>Plugins/install: forward <code>--dangerously-force-unsafe-install</code> through archive and npm-spec plugin installs so the documented override reaches the security scanner on those install paths. (#58879) Thanks @ryanlee-gemini.</li>
<li>Auto-reply/commands: strip inbound metadata before slash command detection so wrapped <code>/model</code>, <code>/new</code>, and <code>/status</code> commands are recognized. (#58725) Thanks @Mlightsnow.</li>
<li>Agents/Anthropic: preserve thinking blocks and signatures across replay, cache-control patching, and context pruning so compacted Anthropic sessions continue working instead of failing on later turns. (#58916) Thanks @obviyus</li>
<li>Agents/failover: unify structured and raw provider error classification so provider-specific <code>400</code>/<code>422</code> payloads no longer get forced into generic format failures before retry, billing, or compaction logic can inspect them. (#58856) Thanks @aaron-he-zhu.</li>
<li>Auth profiles/store: coerce misplaced SecretRef objects out of plaintext <code>key</code> and <code>token</code> fields during store load so agents without ACP runtime stop crashing on <code>.trim()</code> after upgrade. (#58923) Thanks @openperf.</li>
<li>ACPX/runtime: repair <code>queue owner unavailable</code> session recovery by replacing dead named sessions and resuming the backend session when ACPX exposes a stable session id, so the first ACP prompt no longer inherits a dead handle. (#58669) Thanks @neeravmakwana</li>
<li>ACPX/runtime: retry dead-session queue-owner repair without <code>--resume-session</code> when the reported ACPX session id is stale, so recovery still creates a fresh named sessioninstead of failing session init. Thanks @obviyus.</li>
<li>Auth/OpenAI Codex: persist plugin-refreshed OAuth credentials to <code>auth-profiles.json</code> before returning them, so rotated Codex refresh tokens survive restart and stop falling into <code>refresh_token_reused</code> loops. (#53082)</li>
<li>Discord/gateway: hand reconnect ownership back to Carbon, keep runtime status aligned with close/reconnect state, and force-stop sockets that open without reaching READY so Discord monitors recover promptly instead of waiting on stale health timeouts. (#59019) Thanks @obviyus</li>
</ul>
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
Text("Paste setup code or enter host/port manually.",style=onboardingCaption1Style,color=onboardingTextSecondary)
Text("Paste setup code or enter host/port manually. Private LAN ws:// is supported; Tailscale/public hosts need wss://.",style=onboardingCaption1Style,color=onboardingTextSecondary)
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.