Commit Graph

11964 Commits

Author SHA1 Message Date
Vincent Koc
3f363e0450 fix(qa): extend config mutation Windows budget 2026-05-25 17:20:54 +02:00
Chunyue Wang
89aea9b843 fix(sessions): stop doctor OOM on large session stores and reclaim stale store temps (#85967)
* fix(sessions): stop doctor OOM on large session stores and reclaim stale store temps

`openclaw doctor` loaded the full sessions.json via loadSessionStore with the
default cache-write plus return clone, materializing a multi-hundred-MB
monolithic store several times and exhausting the heap (#56827). The read-only
doctor checks (state integrity, heartbeat target, codex route scan) now load
with { skipCache: true, clone: false } so the store is materialized once.

Orphaned session-store atomic-write temps were also never reclaimed: the store
write went through the generic atomic writer, staging a shared
.fs-safe-replace.<pid>.<uuid>.tmp not identifiable as a store temp. Give the
store write a store-specific tempPrefix so its temps stage as
sessions.json.<pid>.<uuid>.tmp, classify them (isSessionStoreTempArtifactName),
and reclaim stale ones via the disk-budget sweep and the unreferenced-artifact
prune on a short staleness window so in-flight temps are preserved.

Fixes #56827

* docs(changelog): note large session store doctor fix

* test(qa): preserve WhatsApp RTT source literal

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-25 16:19:35 +01:00
clawsweeper[bot]
c4bce00727 fix(ollama): strip inline kimi cloud reasoning leak (#86515)
Summary:
- This PR adds an Ollama Kimi-cloud visible-content sanitizer for streamed and final assistant replies, updates stream handling and regression tests, and adds a changelog entry.
- PR surface: Source +183, Tests +473, Docs +1. Total +657 across 7 files.
- Reproducibility: yes. from source and the linked report: current main appends Ollama `message.content` direc ...  payload described in the issue would be shown. I did not run a live vendor repro in this read-only review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(ollama): sanitize kimi inline reasoning in stream events
- PR branch already contained follow-up commit before automerge: fix(ollama): buffer kimi cloud stream reasoning
- PR branch already contained follow-up commit before automerge: fix(ollama): cover kimi inline boundary variants
- PR branch already contained follow-up commit before automerge: fix(ollama): preserve text start partial state
- PR branch already contained follow-up commit before automerge: fix(ollama): bound kimi stream sanitizer hold
- PR branch already contained follow-up commit before automerge: fix(ollama): keep kimi sanitizer deltas append-only

Validation:
- ClawSweeper review passed for head b709229157.
- Required merge gates passed before the squash merge.

Prepared head SHA: b709229157
Review: https://github.com/openclaw/openclaw/pull/86515#issuecomment-4534945393

Co-authored-by: Jason O'Neal <jason.allen.oneal@gmail.com>
Co-authored-by: Onur Solmaz <2453968+osolmaz@users.noreply.github.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: osolmaz
Co-authored-by: osolmaz <2453968+osolmaz@users.noreply.github.com>
2026-05-25 15:16:42 +00:00
BonRaynn
16ffc2507a fix(memory): prevent silent vector index degradation when embedding provider temporarily unavailable (#85704)
* fix(memory): prevent silent vector index degradation when embedding provider temporarily unavailable

Two related bugs cause complete loss of semantic vector data:

1. Promise cache deadlock in ensureProviderInitialized():
   When the embedding provider (e.g. local MLX server on port 8123) is
   temporarily unreachable at Gateway startup, loadProviderResult() throws
   and providerInitPromise becomes a permanently-cached Rejected Promise.
   The  block only clears it on success (providerInitialized=true),
   so the stale rejection blocks all future init attempts until Gateway restart.

2. Silent fts-only overwrite in runSync():
   With the provider stuck at null, shouldRunFullMemoryReindex() compares
   the stored meta.model (e.g. 'jina-embeddings-v5-text-small') against the
   runtime provider model, and since provider is null, falls through to the
   'meta.model !== fts-only' check — returning true. This triggers a full
   reindex where every file is written as fts-only, silently erasing all
   existing 11k+ semantic vectors.

Fix 1: Clear providerInitPromise in the catch block so the next call can
retry initialization (self-healing when the provider comes back online).

Fix 2: Guard runSync() — if requestedProvider is set and not 'none', but
the runtime provider is null, throw an error instead of silently degrading
to fts-only. This protects existing vector data by failing loudly.

Tested on production: 11,715 chunks + 1024-dim vectors fully preserved
after Gateway restart with the fix applied. The guard correctly blocks
sync when MLX is offline and allows normal operation when it recovers.

* fix: use this.settings.provider instead of private requestedProvider

The guard clause in runSync() was referencing this.requestedProvider
which is a private property on the MemoryIndexManager subclass and not
accessible from MemoryManagerSyncOps. Use this.settings.provider
instead, which is the same value and is accessible via the protected
abstract settings property.

* fix(memory): narrow degradation guard to only protect existing semantic indexes

The previous guard was too broad — it blocked sync for ALL non-none
provider configurations when provider was null, including the default
'auto' path where users without embedding credentials legitimately
build FTS-only indexes.

Narrow the guard to only abort when:
1. provider is null (embedding unavailable)
2. existing index metadata has a semantic model (not 'fts-only')
3. settings.provider is configured and not 'none'

This preserves the legitimate FTS-only fallback for auto/no-provider
users while still protecting existing semantic vector indexes from
silent degradation.

Reported-by: ClawSweeper (PR #85704 review)

* test: cover memory semantic index outage guard

* fix: protect semantic memory index fallback paths

* test: update memory sync harnesses

---------

Co-authored-by: Bo Yan <yaaboo-gif@users.noreply.github.com>
Co-authored-by: Yan Bo <yanbo@Mac.lan>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-25 16:15:59 +01:00
Peter Steinberger
bb6f37e777 test(qa): annotate live transport RTT measurements 2026-05-25 15:56:13 +01:00
clawsweeper[bot]
aa702cf3db fix(qqbot): derive outbound watchdog from configured timeouts (#85267) (#86500)
Summary:
- The branch replaces QQBot's hardcoded outbound response watchdog with a resolver based on existing agent/provider `timeoutSeconds` settings, adds regression tests, and updates the changelog.
- PR surface: Source +113, Tests +116, Docs +1. Total +230 across 5 files.
- Reproducibility: yes. at source level: current main and the latest release use a hardcoded 300000 ms QQBot o ... s an 1800s provider timeout. I did not run the reporter's live QQBot/Ollama setup in this read-only review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: test(qqbot): cover slow provider response watchdog
- PR branch already contained follow-up commit before automerge: fix(qqbot): derive outbound watchdog from configured timeouts (#85267)
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-8527…

Validation:
- ClawSweeper review passed for head 7bd829292a.
- Required merge gates passed before the squash merge.

Prepared head SHA: 7bd829292a
Review: https://github.com/openclaw/openclaw/pull/86500#issuecomment-4534669816

Co-authored-by: SymbolStar <symbolstar@users.noreply.github.com>
Co-authored-by: Onur Solmaz <2453968+osolmaz@users.noreply.github.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: osolmaz
Co-authored-by: osolmaz <2453968+osolmaz@users.noreply.github.com>
2026-05-25 14:52:42 +00:00
Peter Steinberger
a3ae5c8382 refactor(plugin-sdk): rename plain text tool-call compat wrapper 2026-05-25 15:08:01 +01:00
Peter Steinberger
b9f975b64e Replace Sharp image backend with Photon (#86437)
* refactor: replace sharp image backend with photon

* refactor: remove whatsapp jimp dependency

* chore: remove stale sharp install workarounds

* test: keep image fixtures off photon

* test: use valid prompt image fixtures

* test: account for optimized PNG fixtures

* test: use valid minimax image fixtures
2026-05-25 15:04:44 +01:00
Peter Steinberger
b077c3a813 fix: accept OpenClaw voice wake confusions (#86507) 2026-05-25 15:03:16 +01:00
Peter Steinberger
8fe4f34af2 fix: accept leading fuzzy Discord voice wake names (#86484) 2026-05-25 14:01:15 +01:00
Peter Steinberger
5d018034f6 feat: promote provider tool call stream wrapper (#86489) 2026-05-25 13:55:23 +01:00
Vincent Koc
ba2b820c5c fix(qa): extend memory fallback Windows budget 2026-05-25 14:43:25 +02:00
Peter Steinberger
dc2c4aab6d fix: rotate realtime voice sessions on max duration
- Rotate OpenAI Realtime voice sessions on provider max-duration events without surfacing the expected expiry as a Discord voice error.
- Add lifecycle logging for Realtime rotation/reconnect and regression coverage for max-duration reconnect.
- Allowlist the existing Control UI chunking helper for the optional Knip unused-file guard so the dependency shard stays green on the current base.
2026-05-25 13:16:48 +01:00
Onur Solmaz
7ff29a9e6d Fix local embedding worker safety (#85348)
Summary:
- The PR routes local GGUF memory embeddings through a bundled worker sidecar, adds structured degradation and fallback handling, updates memory tests/build output, and keeps the local config contract unchanged.
- PR surface: Source +831, Tests +503, Docs +1, Other +2. Total +1337 across 23 files.
- Reproducibility: Do we have a high-confidence way to reproduce the issue? Source and report evidence are str ... cludes native crash logs; the exact Metal teardown abort was not reproduced in this review or the PR proof.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(memory): keep local embedding config unchanged
- PR branch already contained follow-up commit before automerge: fix(memory): type local embedding degradation
- PR branch already contained follow-up commit before automerge: fix(memory): refresh keywords after embedding fallback
- PR branch already contained follow-up commit before automerge: fix(memory): keep worker errors internal
- PR branch already contained follow-up commit before automerge: test: satisfy memory provider lifecycle harnesses
- PR branch already contained follow-up commit before automerge: fix: harden local embedding worker fallback

Validation:
- ClawSweeper review passed for head 1d1fe41c4e.
- Required merge gates passed before the squash merge.

Prepared head SHA: 1d1fe41c4e
Review: https://github.com/openclaw/openclaw/pull/85348#issuecomment-4518516047

Co-authored-by: Onur Solmaz <onur@Onurs-MacBook-Pro.local>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: osolmaz
Co-authored-by: osolmaz <2453968+osolmaz@users.noreply.github.com>
2026-05-25 11:03:04 +00:00
Sebastien Tardif
8b42771aab fix(memory-core): filter REM dreaming candidates to light-staged entries (#86302)
* fix(memory-core): filter REM dreaming candidates to light-staged entries

REM dreaming re-ingested the full short-term recall store independently,
ignoring which entries were staged by the light sleep phase. Because the
confidence formula heavily weights accumulated averageScore (45%) and
recallStrength (25%), old high-recall entries permanently dominated
freshly staged candidates. The intended light→REM→deep pipeline was
broken: light correctly staged current material, but REM selected a
different set entirely, so lightHits never paired with remHits for deep
ranking.

Fix: in runRemDreaming(), read the phase-signals store for keys with
lightHits > 0 and filter entries to that set before passing to
previewRemDreaming(). When no light-staged keys exist (light disabled
or first run), fall back to the full entry set for backward
compatibility.

Added readLightStagedKeys() to short-term-promotion.ts as a clean
export for reading the light-staged key set from the phase signal store.

Closes #86249

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>

* fix(memory-core): keep REM staging pending

* fix(memory-core): mark REM-considered staged entries

---------

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-25 11:00:24 +01:00
Sebastien Tardif
5182ebcf38 fix(telegram): propagate forum topic names into agent context (#86299)
* fix(telegram): propagate forum topic names into agent context

The topic-name-cache already tracks forum topic names via
forum_topic_created/edited/closed events in bot-message-context, but
this metadata was not surfaced in two key paths:

1. The native-command handler (bot-native-commands.ts) builds the agent
   context payload with IsForum but never looked up the cached topic
   name. Now it resolves the topic name from the cache and includes
   TopicName in the context, giving agents awareness of which forum
   topic they are responding in.

2. The action runtime (action-runtime.ts) executes createForumTopic and
   editForumTopic actions but never persisted the resulting topic
   metadata back to the cache. Now both actions write the topic name
   (and optional icon metadata) to the cache after success, ensuring
   subsequent messages in those topics can resolve the name.

Closes #86024

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>

* fix(telegram): scope forum topic cache updates

---------

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-25 11:00:17 +01:00
Neerav Makwana
2fcd481276 fix(slack): keep downloaded files out of reply media (#86318)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 11:00:12 +01:00
Vincent Koc
9afbfc1b63 fix(qa): capture Windows gateway metrics 2026-05-25 11:24:16 +02:00
Peter Steinberger
a1fe86a0ff feat(qa): add coverage scenario matching 2026-05-25 10:22:51 +01:00
Peter Steinberger
bbc1772f4d build: enable modern TypeScript module syntax
* build: enable modern TypeScript flags

* build: drop erasable TypeScript syntax flag

* build: keep legacy class field semantics
2026-05-25 10:10:12 +01:00
Nimrod Gutman
c791e4242b fix(gateway): gate talk secret bootstrap handoff (#85690)
Merged via squash.

Prepared head SHA: 9247cdab05
Co-authored-by: ngutman <1540134+ngutman@users.noreply.github.com>
Co-authored-by: ngutman <1540134+ngutman@users.noreply.github.com>
Reviewed-by: @ngutman
2026-05-25 11:34:12 +03:00
Jason (Json)
35dcd42c9d fix: suppress async media incomplete-turn errors (#85933)
* fix: suppress async media incomplete-turn errors

* fix: mark async media starts as side effects

* fix: preserve async markers in codex dynamic tool progress

* fix: carry async codex tool metadata into attempts

* fix: preserve async codex metadata across snapshots

* fix: suppress async media incomplete-turn errors (#85933) (thanks @fuller-stack-dev)

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-25 09:17:30 +01:00
FullerStackDev
0a98c2d626 address migrate auth review comments 2026-05-25 09:16:37 +01:00
FullerStackDev
44bb2be0b4 fix migrate supported auth imports 2026-05-25 09:16:37 +01:00
FullerStackDev
50e6cb0828 fix migrate auth lint 2026-05-25 09:16:37 +01:00
FullerStackDev
f036bac144 migrate auth credentials 2026-05-25 09:16:37 +01:00
Jason O'Neal
b552919277 fix(telegram): preserve inbound text entities (#83873) 2026-05-25 13:27:19 +05:30
Peter Steinberger
84ab206887 test(telegram): type topic cache harness store 2026-05-25 08:47:27 +01:00
Peter Steinberger
ff1fde1bb4 test(telegram): provide topic cache store in message context harness 2026-05-25 08:47:27 +01:00
Peter Steinberger
c3ab2def0a refactor: keep plain text tool-call promotion private (#86374)
Move the plain-text tool-call promotion wrapper out of the public provider stream SDK helper and into a private local-only bundled-provider runtime seam.
2026-05-25 08:43:21 +01:00
Jason (Json)
0014724428 fix(discord): suppress self-reply prompt echoes (#86238)
* fix(discord): suppress self-reply prompt echoes

* docs(changelog): note Discord self-reply fix

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-25 08:41:07 +01:00
Jason (Json)
cd627803a0 fix: prevent plain text tool call leaks (#86222)
Prevent plain text tool call leaks from xAI/LM Studio fallback streams.

- Promotes plain-text tool-call fallback chunks into structured tool calls.
- Strips leaked internal tool syntax before user-facing/outbound text.
- Adds regression coverage across provider stream wrappers, tool payload parsing, user-facing sanitization, and outbound send validation.

Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
2026-05-25 08:15:11 +01:00
clawsweeper[bot]
b962110637 fix(codex): preserve source reply mode for active runs (#86325)
Summary:
- This PR forwards Codex app-server source reply delivery mode into active run handling, adds a focused regression test, and adds a changelog entry.
- PR surface: Source +1, Tests +38, Docs +1. Total +40 across 3 files.
- Reproducibility: yes. Source inspection shows the shared active-run queue rejects `message_tool_only` replies when the active handle lacks that mode, and current main's Codex app-server handle omits it.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(codex): preserve source reply mode for active runs

Validation:
- ClawSweeper review passed for head d8fac59d8f.
- Required merge gates passed before the squash merge.

Prepared head SHA: d8fac59d8f
Review: https://github.com/openclaw/openclaw/pull/86325#issuecomment-4531516197

Co-authored-by: Fermin Quant <ferminquant@hotmail.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-05-25 06:00:47 +00:00
Alex Knight
c3c8a65373 fix codex usage-limit recovery copy (#86305) 2026-05-25 15:53:40 +10:00
Vincent Koc
a5d5604198 fix(tests): harden native macos plugin proof 2026-05-25 07:21:12 +02:00
pashpashpash
dd47e479ae Fail Codex compaction at the Codex boundary (#85958) 2026-05-24 22:12:34 -07:00
Val Alexander
119a01c829 fix(webchat): stabilize live transcript run state
Stabilize WebChat transcript/run-state truth for Codex and selected-session observers.

Summary:
- Mirror Codex inbound prompts at turn start without duplicating suppressed persisted prompts.
- Deliver hidden external-channel live chat/tool/agent updates only to exact selected-session subscribers.
- Repair Control UI selected-session subscription state, alias-aware run adoption, and accumulated stream dedupe.
- Add focused Codex, gateway/session-event, and Control UI regression coverage.

Verification:
- Current-head CI: 101 green, 0 pending; stale canceled entries are superseded automation from prior force-pushed heads.
- Local focused Vitest shards passed: Codex app-server 2 files / 233 tests, gateway/session 4 files / 116 tests, UI 7 files / 238 tests.
- `node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.core.test.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/core-test.tsbuildinfo`
- `node --import tsx scripts/check-no-extension-test-core-imports.ts`
- `git diff --check origin/main..HEAD`

Closes #83528.
Closes #82611.
Refs #83949.
2026-05-24 23:07:29 -05:00
Ayaan Zaidi
9db04a27eb fix(openai): scope external codex auth to realtime 2026-05-25 09:01:07 +05:30
Ayaan Zaidi
48c4f57401 fix(openai): prefer codex auth for GPT realtime 2026-05-25 09:01:07 +05:30
Galin Iliev
b5c1199217 fix(telegram): route polling diagnostics away from errors
Route normal [telegram][diag] polling diagnostics through runtime.log while keeping non-diag Telegram warnings/errors and offset persistence failures on runtime.error.

Verification:
- node scripts/run-vitest.mjs extensions/telegram/src/monitor.test.ts (34 passed)
- git diff --check
- CI run 26378692736 passed on 979c6f31a4

Fixes #82957
2026-05-24 18:39:52 -07:00
Omar Shahine
f37fbc9ef4 fix: repair anchorless iMessage watch payloads
Repair explicit anchorless iMessage watch payloads by GUID before debounce/routing, and drop unrecoverable payloads fail-closed instead of routing them as sender DMs.

Closes #84470.
Refs #84503.

Thanks @zhangguiping-xydt and @zqchris.
2026-05-24 18:13:03 -07:00
Gio Della-Libera
3a72a30074 fix(oc-path): support deep config edits (#86060) 2026-05-24 18:10:02 -07:00
Damian Finol
f09b4ebe31 fix(google-vertex): support production ADC modes (#83971)
Fix Google Vertex production ADC mode support by routing explicit google-vertex models to the Vertex transport and relying on google-auth-library for request-time ADC resolution.

Verification:
- pnpm install --frozen-lockfile
- pnpm test extensions/google/transport-stream.test.ts extensions/google/index.test.ts src/config/zod-schema.models.test.ts src/agents/pi-embedded-runner/model.inline-provider.test.ts -- --reporter=verbose
- pnpm check:changed
- GitHub PR checks green on c4b7cad4df
- Live ADC smoke reached Google Vertex auth/transport and failed only because the configured redacted project has the Vertex AI API disabled

Co-authored-by: Damian Finol <damian@felixpago.com>
2026-05-25 01:37:52 +01:00
Peter Steinberger
d9af23fb5a fix(codex): log app-server approval promotion trigger 2026-05-25 01:26:37 +01:00
Andy Ye
8dc6b4d330 Clean up browser MCP subprocess tree (#85832)
* fix: clean up browser MCP subprocess tree

* fix: clean up windows browser mcp tree before close

* fix(browser): repair chrome mcp cleanup rebase

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-25 00:57:34 +01:00
Dallin Romney
8209426867 fix(telegram): transient Telegram pairing prompts (#85555)
* fix: avoid false telegram pairing prompts

* docs: add telegram pairing changelog

* refactor(telegram): share pairing-store gating and align isGroup check

Extract loadTelegramPairingStoreIfNeeded so the text-fragment flush path
and resolveTelegramGroupAllowFromContext share one implementation, and
align the isGroup derivation in the flush path with the
'group || supergroup' form used elsewhere in bot-handlers.runtime.ts.

Note on transient-vs-known errors: readChannelAllowFromStore already
translates missing-file (ENOENT) and JSON parse failures to an empty
allowlist internally, so the only errors that escape into the new
silent-drop path are unexpected I/O failures (EMFILE/EACCES/EIO/...) —
unpaired senders still get a pairing challenge as expected.

* fix(telegram): skip pairing-store read when commands.allowFrom already authorizes the sender

Native command auth resolves group/dm allow context (which may read the
pairing store) before checking commands.allowFrom. On DMs with
dmPolicy: "pairing", a transient pairing-store I/O failure was therefore
dropping commands from senders explicitly authorized by
commands.allowFrom.telegram.

Add a skipPairingStoreRead hint on resolveTelegramGroupAllowFromContext /
loadTelegramPairingStoreIfNeeded, precompute the command authorization
once at chat scope before the context call, and pass the hint when that
pre-check already authorizes the sender. The post-context command auth
check still owns the topic-scoped decision.

Regression covers a DM /status from a sender allowed by
commands.allowFrom.telegram with dmPolicy: "pairing" and a rejecting
readChannelAllowFromStore mock.

* fix(telegram): satisfy test-types on harness readChannelAllowFromStore

CI check-test-types failed because the harness now stores a loose
AnyAsyncMock for readChannelAllowFromStore but TelegramNativeCommandDeps
requires the precise typeof readChannelAllowFromStore signature. Cast at
the telegramDeps assignment so harness callers can keep passing any
vi.fn(...) (including ones that reject) without type pollution at the
call site.

* feat(telegram): reply with a retry hint when pairing-store read fails transiently

Wrap unexpected pairing-store I/O errors (EACCES, EMFILE, ...) in a
typed TelegramPairingStoreReadError and surface them through
handleInboundMessageLike with a friendly "please try again" reply that
matches the media-failure precedent at bot-handlers.runtime.ts:1893.
Beats silent drop: paired senders see why their message wasn't
processed, and unpaired senders who happen to send a DM during a
transient store outage retry naturally and get the correct pairing
prompt once the store recovers.

Verified live against @paxicoto_bot with chmod 000 on
~/.openclaw/credentials/telegram-default-allowFrom.json after touching
mtime to bypass the stat-pinned cache.
2026-05-24 15:12:30 -07:00
Omar Shahine
5c7980fa11 feat(imessage): support thumb approval reactions (#85952)
* feat(imessage): support thumb approval reactions

Mirrors openclaw#85477 (WhatsApp) for the iMessage channel. iMessage can now
deliver exec/plugin approval prompts via the existing imsg/BlueBubbles
transport and resolve approvals from 👍 (allow-once) / 👎 (deny) tapbacks.
Allow-always remains on the manual /approve <id> allow-always fallback.

What changed:
- New approval surfaces under extensions/imessage/src/:
  approval-auth.ts, approval-resolver.ts, approval-reactions.ts,
  approval-handler.runtime.ts, approval-native.ts (+ tests for each).
- channel.ts wires base.approvalCapability to the new iMessage capability.
- send.ts appends the 👍/👎 hint to outbound /approve prompts and registers
  the reaction binding (keyed by accountId + chat_guid/chat_identifier/
  chat_id/handle + messageId) after a successful send.
- monitor/monitor-provider.ts resolves approval reactions ahead of the
  normal inbound decision pipeline so resolution bypasses
  reactionNotifications gating and runs its own actor authorization.
- runtime.ts now exports getIMessageRuntime / getOptionalIMessageRuntime so
  approval-reactions can open a persistent keyed store for binding state
  across gateway restarts.

What did NOT change:
- Core approval surfaces in src/gateway/server-methods/* and src/infra/*
  remain channel-agnostic; the channels.imessage.allowFrom field already
  exists and is reused as the approver list for reactions.
- Other channels and the manual /approve sender-authorized path are
  untouched.

* fix(imessage): address codex review findings on thumb approvals

Addresses 15 findings from the multi-angle codex review:

Critical (correctness / blocking):
- Register CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY in the iMessage
  monitor so the gateway can actually deliver native approval prompts via
  approval-handler.runtime.ts (it was dead code without the context lease).
- DM tapback approvals never resolved because send keyed by handle while
  inbound preferred chat_guid. Register and look up under EVERY available
  conversation key (chat_guid / chat_identifier / chat_id / handle); inbound
  probes them all and accepts the first hit.
- Reaction binding now requires the bridge's GUID string (rejecting numeric
  ROWIDs) so the binding key matches inbound reacted_to_guid.
- Outbound regex now requires both a canonical `ID: <approvalId>` header AND
  a matching `/approve <id> <decision>` line, so non-approval messages that
  legitimately mention /approve syntax no longer get a phantom reaction
  binding (and can no longer resolve a colliding live approval).
- Drop is_from_me reaction events so cross-device echoes of the operator's
  own tap cannot self-approve when their handle is in allowFrom.

High (operability / cleanup):
- Non-ApprovalNotFound errors now log at warn via the runtime child logger
  (no longer hidden behind OPENCLAW_LOG_LEVEL=debug).
- In-memory binding is cleared on successful resolve so a toggle 👍👎 (or
  chat.db replay) does not refire and emit a misleading 'expired approval'
  log line. Removed tapbacks are also owned by the shortcut and not surfaced
  as noisy reaction system events.
- Move resolveIMessageReactionContext (and its helpers) to a slim
  monitor/reaction-context.ts so approval-reactions.ts no longer transitively
  pulls monitor/inbound-processing.ts (14+ heavy runtime modules) into the
  hot channel.ts entrypoint per extensions/CLAUDE.md.

Medium (consistency / future-proofing):
- Native runtime exec pending payload now passes agentId, ask, and
  sessionKey through buildExecApprovalPendingReplyPayload so the two
  delivery routes produce identical operator-visible prompts.
- Both delivery paths now use addIMessageApprovalReactionHintToText (single
  insertion point after ID:) so the hint cannot be double-emitted by the
  native runtime path bypassing the idempotency guard.
- Extract replaceApprovalIdPlaceholder into a shared approval-text.ts that
  escapes `$` in the replacement string so an approvalId containing
  `$&`/`$1`-`$9`/`$$` cannot interpolate into the outbound text.
- In-memory Map now stores TTL alongside each entry and prunes expired
  bindings on each register so the gateway no longer accumulates an
  unbounded reaction-target Map.
- bindPending refuses to bind when accountId is missing or the approval is
  already expired, with explicit error logs instead of silent no-ops.
- Reject chat_id=0 as a synthetic key value (chat.db ROWIDs start at 1).
- Drop dead getIMessageRuntime export — only the optional accessor is used.

Documentation:
- docs/channels/imessage.md gains an 'Approval reactions (👍 / 👎)' accordion
  documenting the reaction emoji map, allowFrom approver requirement, the
  /approve <id> allow-always manual fallback, and the deliberate change to
  /approve command authorization for users with non-empty allowFrom.
- CHANGELOG.md entry added under 2026.5.24.

Tests: 411 iMessage tests pass (was 406). Added explicit coverage for the
DM key-mismatch fix, the regex-tightening fix, the is_from_me guard, the
clear-on-success behavior, and the approval-id `$` escape.

* test(imessage): match WhatsApp approval-native test coverage

Backfills the nine cases from extensions/whatsapp/src/approval-native.test.ts
that weren't mirrored in iMessage:

- target-mode exec + plugin prompt rendering with the canonical hint
- target-mode availability when no iMessage target matches
- agentFilter / sessionFilter applied to native handling
- account-scoped target enabled/disabled per account
- shouldSuppressForwardingFallback session-origin exact-match cases
- shouldSuppressForwardingFallback off when native cannot bind (locks down
  the targets-only forwarding path the Lobster live deploy exercised)
- both-mode explicit + unscoped target suppression
- group-origin tapback approvals require explicit approvers

Tests: extensions/imessage/src/approval-native.test.ts 21 passed (was 11).
Total iMessage approval-specific cases now 49 (was 40).

* fix(imessage): preserve service-prefixed direct handles as approvers

ClawSweeper P1 review finding on #85952. normalizeIMessageApproverId was
calling looksLikeIMessageExplicitTargetId() to reject conversation-target
prefixes, but that helper also matches the imessage:/sms:/auto: service
prefixes — which are valid direct-handle forms. Any allowFrom entry like
'imessage:+15551230000' dropped to undefined, leaving approvers empty,
which:
  - silently denied reaction resolution ('reactions require explicit
    approvers'), and
  - let text /approve fall back to implicit same-chat authorization.

Fix: normalize first via normalizeIMessageHandle (strips the service
prefix), then reject only chat_id:/chat_guid:/chat_identifier:
conversation-target shapes that remain after normalization.

Tests:
  - approval-auth.test.ts: assert the resolved approver list contains the
    normalized handle, plus the corollary that a non-matching sender is
    explicitly rejected (no longer masked by the implicit-same-chat
    fallback). Add a separate case covering chat_id/chat_guid/
    chat_identifier rejection (with and without a service prefix).
  - approval-reactions.test.ts: reaction resolution end-to-end with a
    service-prefixed allowFrom entry — proves resolveIMessageApproval is
    called rather than silently denied.

Focused suite: 48 passed (was 47).

* test(imessage): satisfy strict buildPendingPayload signature in render tests

CI check:test-types caught that the render.exec/render.plugin
buildPendingPayload calls were passing accountId (not in the type
signature). The signature is { cfg, request, target, nowMs }. Replace
accountId with target on the four render-test sites so the strict
test-types pass matches the SDK contract:

  - it('renders thumbs-only reaction hints in exec approval prompts')
  - it('renders thumbs-only reaction hints in plugin approval prompts ...')
  - it('renders target-mode exec prompts with concrete thumbs-only ...')
  - it('renders target-mode plugin prompts with concrete thumbs-only ...')

Verified locally with pnpm check:test-types (tsgo:core:test +
tsgo:extensions:test). 49 approval-specific tests still pass.

* fix(imessage): probe every tapback GUID form for approval lookup

ClawSweeper P1 review finding on #85952. readApprovalReactionEvent was
only using reaction.targetGuid (the first/normalized form), but
resolveIMessageReactionContext produces reaction.targetGuids = [normalized,
raw] for both `abc-123` and `p:0/abc-123` forms. If the imsg bridge
returned 'p:0/<guid>' from send() and send.ts registered the binding under
that prefixed key, the inbound resolver probing only the unprefixed form
would miss and the tapback would silently fall through.

Fix:
- Surface every GUID candidate in IMessageApprovalReactionEvent
  (messageIdCandidates).
- maybeResolveIMessageApprovalReaction now probes each candidate in
  precedence order; first hit wins.
- On success / ApprovalNotFoundError, clear the binding under all
  candidate keys so toggle/replay does not refire.

Tests: extensions/imessage/src/approval-reactions.test.ts gains a
'resolves a reaction when the binding was registered under a p:0/…
prefixed GUID and the tapback surfaces both forms' regression case;
22/22 reaction tests pass. Full iMessage suite: 424/424.

* fix(imessage): native approval binding requires GUID, not numeric id

ClawSweeper third P1 review finding on #85952. approval-handler.runtime.ts
deliverPending was using result.messageId as the approval-reaction binding
key, but that field can be a numeric ROWID coerced to a string ('12345')
when the imsg bridge returns only message_id. Inbound tapbacks carry
reacted_to_guid which is always a GUID, so a numeric-id binding can never
match.

Fix mirrors the send.ts forwarding-path treatment:
- IMessageSendResult now exposes a separate guid?: string field, populated
  from the same resolveOutboundMessageGuid helper send.ts already uses for
  the forwarding-path binding. The generic messageId field is unchanged so
  reply-cache, echo-cache, and receipt-building paths still see the
  broadest id form.
- deliverPending now binds against result.guid; when it's undefined (numeric
  ROWID or 'ok'/'unknown' placeholders), the function returns null instead
  of binding against an id the inbound tapback can't possibly match.

Tests: approval-handler.runtime.test.ts gets a deliverPending GUID-only
binding describe block with three regression cases (numeric ROWID refused,
GUID accepted, ok/unknown placeholders refused). vi.mock isolates
sendMessageIMessage so the cases run synchronously without spawning imsg.
11 tests pass across handler.runtime + send specs.

---------

Co-authored-by: Omar Shahine <10343873+omarshahine@users.noreply.github.com>
2026-05-24 10:51:21 -07:00
Andy Ye
102555c6e0 Advance iMessage catchup cursor after live handling (#85475)
Fixes #85363.

Thanks @TurboTheTurtle.
2026-05-24 09:34:16 -07:00
Vincent Koc
694d45e535 fix(media): use static image compression metadata 2026-05-24 16:47:59 +02:00
Ayaan Zaidi
5cfb12fa5d fix(telegram): migrate account topic cache sidecars 2026-05-24 18:58:02 +05:30