Skip re-attach when user explicitly dismisses debugger bar or opens
DevTools. Prevents frustrating re-attach loop that fights user intent.
Addresses review feedback from greptile-apps.
When Chrome's debugger detaches during page navigation (common in SPAs
like Gmail, Google Calendar), the extension now automatically re-attaches
instead of permanently losing the connection.
Changes:
- onDebuggerDetach: detect navigation vs tab close, attempt re-attach
with 3 retries and exponential backoff (300ms, 700ms, 1500ms)
- Add reattachPending guard to prevent concurrent re-attach races
- connectOrToggleForActiveTab: handle pending re-attach state
- onRelayClosed: clear reattachPending on relay disconnect
- Add chrome.tabs.onRemoved listener for proper cleanup
Fixes#19744
Subagent and isolated cron sessions only loaded AGENTS.md and TOOLS.md,
causing subagents to lose their role personality, identity, and user
preferences. Expand MINIMAL_BOOTSTRAP_ALLOWLIST to include the three
missing identity files.
Closes#24852
(cherry picked from commit c33377150eeddb42c2a24f4a48c2d01b5cdf8d3e)
The diagnostics-otel extension validates that protocol is "http/protobuf"
but was importing JSON-based `-http` exporters. This caused silent failures
with backends like VictoriaMetrics that only accept protobuf-encoded OTLP.
Switch all three exporter imports (metrics, traces, logs) from
`@opentelemetry/exporter-*-otlp-http` to `@opentelemetry/exporter-*-otlp-proto`.
Fixes#24942
Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit f5c0bf0497bff4c9c0748472ad5a63742af43374)
When session.dmScope is set to 'per-channel-peer', WhatsApp DMs correctly
resolve isolated session keys, but updateLastRouteInBackground unconditionally
wrote lastTo to the main session key. This caused reply routing corruption
and privacy violations.
Only update main session's lastRoute when the DM session actually IS
the main session (sessionKey === mainSessionKey).
Fixes#24912
"off" is a truthy string, so the existing guard `if (thinkingLevel && ...)`
was always entering the injection block and sending `reasoning: { effort: "none" }`
to every OpenRouter request — even when thinking wasn't enabled. Models that
require reasoning (e.g. deepseek/deepseek-r1) reject this with:
400 Reasoning is mandatory for this endpoint and cannot be disabled.
Fix: skip the reasoning injection entirely when thinkingLevel is "off".
The reasoning_effort flat-field cleanup still runs. Omitting the reasoning
field lets each model use its own default behavior.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
sessions_send timeout/error results were being surfaced as raw warning
messages in Telegram chats because the tool is classified as mutating,
which forces error warnings to always be shown. However, sessions_send
failures are transient inter-session communication issues where the
message may still have been delivered, so they should not leak to users.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The deliver callback in process-message.ts was forwarding all payload
kinds (tool, block, final) to WhatsApp. Block payloads contain the
model's reasoning/thinking content, which should only be visible in
the internal web UI. This caused chain-of-thought to leak to end users
as separate WhatsApp messages.
Add an early return for non-final payloads so only the actual response
is delivered to the WhatsApp channel, matching how Telegram already
filters by info.kind === "final".
Fixes#24954Fixes#24605
Co-authored-by: Cursor <cursoragent@cursor.com>
Apply redactIdentifier() (SHA-256 hashing) to all recipient JIDs and
phone numbers logged by sendMessageWhatsApp, sendReactionWhatsApp,
sendPollWhatsApp, and runWebHeartbeatOnce. Remove poll question text
and message preview content from log entries, replacing with character
counts where useful for debugging.
The existing redactIdentifier() utility in src/logging/redact-identifier.ts
was already implemented but not wired into any WhatsApp logging path.
This commit connects it to all affected call sites while leaving
functional parameters (actual send calls, event emitters) untouched.
Closes#24957
Isolated cron sessions (agentTurn) were grouped with subagent sessions
under the "minimal" prompt mode, which causes buildSkillsSection to
return an empty array. This meant <available_skills> was never included
in the system prompt for isolated cron runs.
Subagent sessions legitimately need minimal prompts (reduced context),
but isolated cron sessions are full agent turns that should have access
to all configured skills, matching the behavior of normal chat sessions
and non-isolated cron runs.
Remove isCronSessionKey from the minimal prompt condition so only
subagent sessions use "minimal" mode.
Fixes openclaw#24888
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(plugins): use manifest id as config key instead of npm package name
Plugin manifests (openclaw.plugin.json) define a canonical 'id' field that
is used as the authoritative plugin identifier by the manifest registry.
However, the install command was deriving the config entry key from the npm
package name (e.g. 'cognee-openclaw') rather than the manifest id (e.g.
'memory-cognee'), causing a latent mismatch.
On the next gateway reload the plugin could not be found under the config key
derived from the npm package name, causing 'plugin not found' errors and
potentially shutting the gateway down.
Fix: after extracting the package directory, read openclaw.plugin.json and
prefer its 'id' field over the npm package name when registering the config
entry. Falls back to the npm-derived id if the manifest file is absent or
has no valid id. A diagnostic info message is emitted when the two values
differ so the mismatch is visible in the install log.
The update path (src/plugins/update.ts) already correctly reads the manifest
id and is unaffected.
Fixes#24429
* fix: format plugin install manifest-id path (#24796)
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
When the Synology Chat plugin restarts (auto-restart or health monitor),
startAccount is called again without calling the previous stop(). The
HTTP route is still registered, so registerPluginHttpRoute returns a
no-op unregister function and logs "already registered". This triggers
another restart, creating an infinite loop.
Store the unregister function at module level keyed by account+path.
Before registering, check for and call any stale unregister from the
previous start cycle, ensuring a clean slate for route registration.
Fixes#24894
Co-authored-by: Cursor <cursoragent@cursor.com>
Block payloads (info.kind === "block") contain reasoning/thinking content
that should only be visible in the internal web UI. When streamMode is
"partial", these blocks were being delivered to Discord as visible
messages, leaking chain-of-thought to end users.
Add an early return for block payloads in the deliver callback,
consistent with the WhatsApp fix and Telegram's existing behavior.
Fixes#24532
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(gateway): safely extract text from message content arrays in prompt builder
When HistoryEntry.body is a content array (e.g. [{type:"text",
text:"hello"}]) rather than a plain string, template literal
interpolation produces "[object Object]" instead of the actual message
text. This affects users whose session messages were stored with array
content format.
Add a safeBody helper that detects non-string body values and uses
extractTextFromChatContent to extract the text, preventing the
[object Object] serialization in both the current-message return path
and the history formatting path.
Fixes openclaw#24688
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: format gateway agent prompt helper (#24946)
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
When the gateway rejects connections (e.g. scope-upgrade 'pairing required'),
the announce queue drain loop would retry every ~1s indefinitely because
the only delay was the fixed debounceMs (default 1000ms).
This adds a consecutiveFailures counter with exponential backoff:
2s, 4s, 8s, 16s, 32s, 60s (capped). The counter resets on successful drain.
The backoff is applied by shifting lastEnqueuedAt forward so that
waitForQueueDebounce naturally delays the next attempt.
Fixes#24777
Co-authored-by: Knut <knut@Knut-sin-Mac-mini.local>
When a user runs /reasoning off, the session patch handler deleted
the reasoningLevel field from the session entry. This caused
get-reply-directives to treat reasoning as 'not explicitly set',
which triggered resolveDefaultReasoningLevel() to re-enable
reasoning for capable models (e.g. Claude Opus).
The fix persists 'off' explicitly, matching how directive-handling.persist.ts
already handles the inline /reasoning off command.
Fixes#24406Fixes#24411
Co-authored-by: echoVic <AkiraVic@outlook.com>
* fix(infra): handle Windows dev=0 in sameFileIdentity TOCTOU check
On Windows, `fs.lstatSync` (path-based) returns `dev: 0` while
`fs.fstatSync` (fd-based) returns the real NTFS volume serial number.
This mismatch caused `sameFileIdentity` to always fail, making
`openVerifiedFileSync` reject every file — silently breaking all
Control UI static file serving (HTTP 404).
Fall back to ino-only comparison when either dev is 0 on Windows.
ino remains unique within a single volume, so TOCTOU protection
is preserved.
Fixes#24692
* fix: format sameFileIdentity wrapping (#24939)
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Use tryRealpath() instead of path.resolve() when comparing expected
package paths in detectGlobalInstallManagerForRoot(). path.resolve()
only normalizes path strings without following symlinks, causing pnpm
global installs to go undetected since pnpm symlinks node_modules
entries into its .pnpm content-addressable store.
Fixes#22768
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>