Commit Graph

954 Commits

Author SHA1 Message Date
Dizesales
ff58d23e8f fix(telegram): resolve local Bot API container file paths against trustedLocalFileRoots [AI-assisted] (#91984)
* fix(telegram): resolve local Bot API container file paths against trustedLocalFileRoots

When the self-hosted telegram-bot-api server runs with --local inside a
container, getFile returns absolute file_path values rooted at the
container data dir (/var/lib/telegram-bot-api/...). The host process
mounts that volume at a different path, so the absolute path never
matches a trustedLocalFileRoots entry and inbound media fails to
materialize (messages reach the agent as bare <media:document>
placeholders without bytes).

Map container-absolute paths back to relative candidates and resolve
them under each trusted root, trying both with and without the
per-token directory segment the local server uses. Relative file_path
values are now also resolved against trusted roots before falling back
to the HTTP file endpoint, which keeps large-file (>20MB) local-mode
downloads working.

Path traversal protections: candidates are normalized, NUL bytes and
dot-segments are rejected, and reads stay behind the trusted-roots
file-access sandbox.

AI-assisted (Claude Code), validated with targeted vitest run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Co-Authored-By: Dizesales <269209351+Dizesales@users.noreply.github.com>

* fix(telegram): map local Bot API container media paths

Co-authored-by: Lucas Magalhaes <ellucasrj@gmail.com>

* chore(config): refresh channel metadata

---------

Co-authored-by: Lucas Magalhaes <ellucasrj@gmail.com>
Co-authored-by: Dizesales <269209351+Dizesales@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-04 17:07:51 -04:00
Barbara Kudiess
129e0a8cf0 feat: pair mobile devices from the Control UI (#94672)
* feat(gateway): add device.pair.setupCode RPC for connect QR

The OpenClaw mobile/companion app scans a pairing setup code to connect to the gateway, but that code + QR could only be produced by the openclaw qr CLI (ASCII to stdout). Non-terminal clients driving onboarding had no way to display the connect QR.

Add a device.pair.setupCode gateway method that reuses resolvePairingSetupFromConfig + encodePairingSetupCode + renderQrPngDataUrl to return { setupCode, qrDataUrl?, gatewayUrl, auth, urlSource }. The embedded setup code mints a short-lived bootstrap token that hands off broad operator scopes (read/write/approvals/talk.secrets), so the method requires operator.admin (matching the wizard methods a companion already uses) and is not advertised. auth is a label only; the gateway credential is never returned, and an oversized QR is omitted so the response always satisfies the result schema.

Refs #94661.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(ui): pair mobile devices from Control UI

* docs: refresh generated docs map

* docs: clarify mobile QR auto-connect

* docs: clarify mobile QR auto-connect

* docs: clarify mobile QR auto-connect

---------

Co-authored-by: Barbara Kudiess <76582160+bkudiess@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-04 12:41:14 -04:00
Peter Steinberger
82610d3b15 feat(whatsapp): add requester-bound MeowCaller calls (#99635)
* feat(whatsapp): add requester-bound MeowCaller calls

* fix(whatsapp): align MeowCaller CLI contract

* test(whatsapp): narrow MeowCaller audio path

* fix(whatsapp): budget MeowCaller setup phases

* feat(whatsapp): gate experimental calls

* fix(whatsapp): use managed call temp storage

* fix(whatsapp): preserve channel entry boundary
2026-07-04 02:19:28 -07:00
Jesse Merhi
8c915f0685 feat(signal): add target aliases (#95738)
* feat(signal): add target aliases

* fix(signal): canonicalize alias delivery targets

* fix(signal): harden alias target resolution

* test(signal): cover formatted media aliases

* test(signal): align approval alias proof with request shape

* Reuse Signal alias map for directory listing
2026-07-03 23:40:10 +10:00
Jesse Merhi
8abc633cf8 Add Signal status reaction lifecycle (#98791) 2026-07-03 21:42:39 +10:00
Jesse Merhi
64318d7624 Rework Android gateway onboarding setup (#98752)
* feat(android): rework gateway onboarding setup

* fix(android): restore protocol mismatch onboarding guidance

* fix(android): sync onboarding native i18n

* fix(android): preserve LAN manual setup prefill

* fix(android): refine onboarding node approval step

* fix(android): polish onboarding recovery actions

* test(android): cover onboarding protocol mismatch copy

* fix(android): separate onboarding node approval

* fix(android): guard onboarding node approval transitions

* fix(android): keep onboarding approval states reachable

* fix(android): wait for node access before onboarding continue

* fix(android): refresh node approval after operator handoff

* fix(android): refresh onboarding approval states

* fix(android): require approval after onboarding permission changes

* fix(android): keep onboarding approval gates active

* fix(android): preserve permission reapproval state

* fix(android): skip node approval on legacy gateways

* fix(android): wait for node approval refresh

* fix(android): preserve camera setting on upgrade

* fix(android): avoid stuck approval check spinner

* fix(android): request talk secrets on operator connects

* fix(android): avoid missed node approval completion

* fix(android): keep nearby LAN setup local

* fix(android): complete onboarding after node approval

* fix(android): reconcile onboarding with gateway auth plans

* chore(android): refresh native i18n inventory after rebase

* Fix Android onboarding review edge cases

* Fix native i18n onboarding sentinels
2026-07-03 21:20:12 +10:00
Suhruth Vuppala
4440226d52 test(discord): clarify and guardrail gateway proxy selection (#99126)
* feat(discord): add HTTP_PROXY/HTTPS_PROXY env fallback for gateway WebSocket

Discord Gateway WebSocket connections now honor HTTP_PROXY/HTTPS_PROXY
environment variables as fallback before direct connection, matching the
existing pattern from Discord REST API and WhatsApp.

This enables Discord channel deployment in Kubernetes/OpenShift environments
with network policy egress restrictions, removing the need for broad :443
egress as a workaround.

- Add createEnvProxyDiscordGatewayAgent() helper (mirrors rest-fetch.ts pattern)
- Try env proxy before direct HttpsAgent in createDiscordGatewayPlugin()
- Explicit channels.discord.proxy still overrides env proxy
- Add formatErrorMessage and resolveEnvHttpProxyAgentOptions imports

Fixes #98266

* test(discord): keep gateway proxy explicit

Signed-off-by: sallyom <somalley@redhat.com>

* fix(discord): allow explicit remote proxy URLs

---------

Signed-off-by: sallyom <somalley@redhat.com>
Co-authored-by: sallyom <somalley@redhat.com>
Co-authored-by: joshavant <830519+joshavant@users.noreply.github.com>
2026-07-02 20:27:19 -05:00
Ayaan Zaidi
dad7768da8 docs(telegram): document group history removal 2026-07-02 09:22:59 -07:00
Mark
5db7c3797d Fix Telegram plugin callback routing (#97174)
* fix telegram plugin callback routing

* Add Telegram callback proof coverage

* Fix proof script lint

* Fix Telegram proof CI regressions

* test: fix telegram callback proof CI regressions

* fix: retire registry-owned callbacks

* fix: retire registry-owned callbacks

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-01 19:49:54 +01:00
wm0018
8a519a9abe docs(matrix): document missing streaming.progress mode, progress sub-fields, and mentionPatterns config (#98318)
* docs(matrix): document missing streaming.progress mode, progress sub-fields, and mentionPatterns config

* docs: clarify Matrix progress labels

---------

Co-authored-by: Vincent Koc <25068+vincentkoc@users.noreply.github.com>
2026-07-01 02:49:23 -07:00
Omar Shahine
c92c33d108 feat(imessage): native poll support — create, read, vote (#98421)
* feat(imessage): add native poll action

Wire the imsg CLI 'poll send' bridge command into the iMessage channel
message-tool action surface, mirroring the existing Discord poll action.
Adds the 'poll' action (gate: polls), a sendPoll runtime, selector-gated
capability advertisement (pollPayloadMessage), config type + zod schema,
regenerated channel metadata, docs, and tests.

* feat(imessage): read inbound polls, vote, and suppress vote echo

Builds on the native poll send action:

- Inbound polls now render to the agent as a readable line (question +
  numbered options + tallies) instead of the raw 0xFFFD balloon placeholder,
  so a received poll no longer reads as an empty message.
- New `poll-vote` action casts a vote via `imsg poll vote`, resolving a
  1-based option index / text / UUID to the poll's option identifier.
- message_tool_only echo guard: the model tends to narrate its choice in a
  text reply right after voting ("Blue."), which is redundant since the vote
  shows on the poll. A new `poll_vote_echo` suppression reason (alongside
  inbound_metadata_echo / internal_runtime_context_echo) drops a send/reply
  that exactly restates the just-cast vote, using the option label imsg
  returns. Extra content passes through untouched.

* fix(imessage): gate poll-vote on imsg poll.vote rpc capability

Released imsg carries the pollPayloadMessage selector (poll create) but
predates the poll.vote CLI/RPC. Gating both poll and poll-vote on that
selector alone would advertise a vote action the released CLI rejects.
Gate poll-vote additionally on the advertised poll.vote rpc method so this
plugin can ship ahead of the imsg release.

* fix(imessage): enforce poll.vote capability at execution, not just discovery

Codex review flagged the discovery gate as bypassable: a caller that already
knows action=poll-vote skips describeMessageTool and reaches handleAction
directly. Add the same imessageRpcSupportsMethod(status, 'poll.vote') check in
the poll-vote execution path (after assertPrivateApiEnabled), so a direct
dispatch on released imsg fails closed with a clear message instead of an
opaque CLI rejection. Adds a negative handleAction test.

* fix(imessage): harden native poll support

* fix(message): validate targets before channel discovery

* fix(message): validate targets before channel discovery

---------

Co-authored-by: Omar Shahine <lobster@users.noreply.github.com>
Co-authored-by: Omar Shahine <10343873+omarshahine@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-01 10:48:32 +01:00
Marcus Castro
fa3c9de459 test(qa-lab): harden whatsapp qa scenarios (#95622)
* fix(whatsapp): preserve group participant identity in QA driver

* fix(whatsapp): infer same-chat action targets

* test(qa-lab): record whatsapp scenario posture

* test(qa-lab): harden whatsapp live scenario coverage

* docs(qa): describe whatsapp qa lane coverage
2026-07-01 00:01:09 -03:00
wm0018
ff1e7e1305 docs(telegram): correct errorPolicy values and defaults 2026-06-29 01:20:44 -07:00
wm0018
293f0369d0 docs(feishu): remove nonexistent disabled dmPolicy option 2026-06-29 01:05:03 -07:00
slammajamma28
68ad8dacc0 fix(docs): Fix formatting on permissions for Discord bot (#97584)
* Fix required formatting text permissions for Discord bot

* remove whitespace
2026-06-28 18:30:36 -07:00
黄伟浩
f4201578e5 fix(matrix): warn on accumulated token storage roots (#97353) 2026-06-28 12:34:52 -07:00
Naseem Muhammad
9ff8510f52 fix path to Discord Developer Mode in setup guide (#97336) 2026-06-28 11:03:25 -07:00
Omar Shahine
f2af052cee perf(imessage): show typing sooner for slow replies (#95621)
Merged via squash.

Prepared head SHA: 65e9ad10fd
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Reviewed-by: @omarshahine
2026-06-24 15:31:48 -07:00
Sliverp
9535b102d3 Gate private QQBot group commands (#92154)
* fix: gate private qqbot group commands

* fix(qqbot): keep authorized stop urgent in groups

* fix(qqbot): preserve omitted group command level

* fix(qqbot): preserve ignore-other-mentions gate

* test(qqbot): avoid unbound mention gate mock

* fix(qqbot): close strict command visibility gaps

* fix(qqbot): gate private group commands and close strict command visibility gaps (#92154) (thanks @sliverp)
2026-06-23 17:20:50 +08:00
Alex Knight
64c81f25c0 fix(mattermost): record thread participation on preview-finalized replies; document thread mention exception 2026-06-23 18:58:38 +10:00
Hannes Rudolph
69c4d1aa85 Revert "feat(discord): add server management helper actions"
This reverts commit ae22f485ec.
2026-06-22 10:20:19 -06:00
Hannes Rudolph
ae22f485ec feat(discord): add server management helper actions 2026-06-22 10:18:28 -06:00
Vincent Koc
03a71f3b46 fix(matrix): prevent double bootstrapCrossSigning reset in forced reset (#95720)
Merged via squash.

Prepared head SHA: afa7684e4b
Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>
Reviewed-by: @steipete
2026-06-22 02:45:56 -07:00
Vincent Koc
f2eca94391 feat(plugins): externalize additional official plugins (#95683) 2026-06-22 16:12:51 +08:00
Vincent Koc
b39e905b69 fix(raft): support current bridge protocol 2026-06-22 14:36:12 +08:00
Vincent Koc
2b50bbf152 feat(raft): add wake bridge channel 2026-06-22 14:36:12 +08:00
Omar Shahine
a0714a3d68 fix(imessage): keep split-send coalescing opt-in (#93143)
Merged via squash.

Prepared head SHA: 7fc3eca084
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Reviewed-by: @omarshahine
2026-06-21 17:12:21 -07:00
Sarah Fortune
6fa944e80f [codex] Add Slack relay mode for incoming messages (#94707) 2026-06-21 11:28:33 -07:00
snowzlmbot
6441e56465 fix(telegram): materialize streaming progress placeholders (#95183)
* fix(telegram): materialize streaming progress placeholders

* fix(telegram): cancel delayed progress drafts before final

* fix(telegram): satisfy progress placeholder lint

* fix(telegram): cancel delayed draft preview on clear

* refactor(telegram): simplify delayed preview flush

---------

Co-authored-by: snowzlmbot <snowzlmbot@users.noreply.github.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-06-21 19:48:41 +05:30
Peter Steinberger
341ae21d03 feat(slack): handle global and message shortcuts (#94881)
Merged via squash.

Prepared head SHA: 32dea12d7a
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>
Reviewed-by: @steipete
2026-06-19 14:12:30 +01:00
Kendrick Ha
4723602e7e feat(channels): add Zalo ClawBot external channel entry and documenta… (#89586)
Merged via squash.

Prepared head SHA: 5ef4fe999a
Co-authored-by: ken-kuro <47441476+ken-kuro@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>
Reviewed-by: @steipete
2026-06-19 13:42:38 +01:00
Subash Natarajan
01dcaba78d fix(slack): remove socket reconnect attempt cap so gateway stays connected indefinitely (#73162)
Merged via squash.

Prepared head SHA: ac51979a7f
Co-authored-by: suboss87 <11032439+suboss87@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>
Reviewed-by: @steipete
2026-06-18 08:18:30 +02:00
Ayaan Zaidi
68eb5031bd fix(telegram): surface rich-message disabled state 2026-06-17 11:26:52 +05:30
Alix-007
9dbc423aa4 fix(whatsapp): bound stalled read-receipt socket operations (#93303)
Merged via squash.

Prepared head SHA: 27752b17a0
Co-authored-by: Alix-007 <267018309+Alix-007@users.noreply.github.com>
Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
Reviewed-by: @vincentkoc
2026-06-16 19:21:12 +08:00
Vincent Koc
63825369a2 fix(auto-reply): allow attachment sends in legacy group automatic replies (#93529)
Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com>
2026-06-16 14:48:16 +08:00
Martin Kessler
840cfd69cd fix(telegram): bind bot mentions to assistant identity (#93088)
* fix(telegram): bind bot mentions to assistant identity

* fix(telegram): satisfy context payload mention typing

* refactor(telegram): carry mention facts as one context object

* test(telegram): use neutral bot handle fixture

* fix(ci): terminate heartbeat command groups

* fix(ci): preserve heartbeat shell functions

* fix(telegram): project effective mention facts

* fix(telegram): keep mention identity portable

* test(telegram): align mention facts mock

---------

Co-authored-by: Vincent Koc <25068+vincentkoc@users.noreply.github.com>
2026-06-16 14:35:14 +08:00
NianJiu
a375d6c849 fix(telegram): gate rich messages behind opt-in (#93279)
Restore readable standard Telegram text delivery by default after Bot API 10.1 rich messages rendered as unsupported in current clients. Keep native rich tables and structured messages available through the account-level richMessages opt-in, with account-aware capability advertising and documented structural limits.

Fixes #93263.
2026-06-15 17:22:41 -07:00
Michael Appel
32af1c0697 Control Telegram group history context (#89547)
* fix(telegram): control group history context

* fix(telegram): keep history mode type local

* fix(telegram): respect history mode during forum recovery
2026-06-15 12:37:04 -07:00
Ayaan Zaidi
da92615816 feat(telegram): send rich messages as rich html (#93286)
* feat(telegram): render rich messages through rich html

* docs(telegram): teach agents rich formatting

* fix(telegram): bound rich draft payloads (#93286)

* fix(telegram): narrow rich draft payload type (#93286)

* fix(telegram): preserve rich table cell formatting (#93286)

* fix(telegram): honor rich table mode config (#93286)

* fix(telegram): default rich markdown tables (#93286)

* fix(telegram): gate rich table block mode (#93286)

* fix(telegram): normalize raw rich html limits (#93286)

* fix(telegram): preserve link preview suppression (#93286)

* fix(telegram): preserve rich markdown headings (#93286)

* fix(telegram): reject unsupported rich media sources (#93286)

* fix(telegram): honor link preview off in rich chunks (#93286)

* fix(telegram): avoid double escaping markdown media (#93286)

* fix(telegram): render markdown media via placeholders (#93286)

* fix(telegram): preserve table text in prompt context (#93286)
2026-06-15 20:38:41 +05:30
Marcus Castro
7d8b000bf7 fix(whatsapp): bound socket operations (#93094)
* fix(whatsapp): bound socket operations

* test(whatsapp): type monitor fixture config

* fix(whatsapp): align socket timeout semantics

* test(whatsapp): cover socket timeout edge cases

* test(whatsapp): shrink socket timeout coverage

* refactor(whatsapp): simplify socket timeout boundary

* fix(whatsapp): keep send api socket type structural
2026-06-15 00:04:11 -03:00
liuhao1024
db5e415888 fix(feishu): re-resolve route when dynamic agent binding already exists in runtime config (fixes #42837) (#92814)
* fix(feishu): re-resolve route when dynamic agent binding already exists in runtime config

When dynamicAgentCreation is enabled and a binding was previously written
to the config file (e.g. from a prior message), the in-memory cfg may be
stale and not contain the binding. Previously, maybeCreateDynamicAgent
returned { created: false, updatedCfg: cfg } with the stale cfg, and
bot.ts only re-resolved the route when created === true. This caused
subsequent messages to still route to agent:main.

Fix: check runtime.config.current() for the binding when it is missing
from the in-memory cfg. When found, return the runtime's current config
so the caller can re-resolve the route with up-to-date bindings.

Fixes #42837

* fix(feishu): serialize dynamic agent config updates

* fix(feishu): route with refreshed runtime config

* fix(feishu): use current dynamic-agent policy

* fix(feishu): reauthorize refreshed dynamic routes

* fix(feishu): authorize dynamic agent mutations

* fix(feishu): complete account-scoped dynamic routing

* fix(feishu): revalidate current direct routes

* fix(feishu): isolate named-account dynamic agents

* fix(feishu): bound named dynamic agent ids

* docs(feishu): explain legacy dynamic agent cap

* test(feishu): fix dynamic routing check types

---------

Co-authored-by: Vincent Koc <25068+vincentkoc@users.noreply.github.com>
2026-06-14 17:28:54 +08:00
Andy Ye
8549a203d4 Honor WhatsApp configured ACP bindings (#92513)
Merged via squash.

Prepared head SHA: 665080f482
Co-authored-by: TurboTheTurtle <35905412+TurboTheTurtle@users.noreply.github.com>
Co-authored-by: mcaxtr <7562095+mcaxtr@users.noreply.github.com>
Reviewed-by: @mcaxtr
2026-06-13 18:09:22 -03:00
Ayaan Zaidi
b9ad8649d0 fix(telegram): allow rich tables in group prompts 2026-06-13 21:45:22 +05:30
Ayaan Zaidi
a3bc0097c8 fix(telegram): migrate retired native draft config 2026-06-13 21:45:22 +05:30
Ayaan Zaidi
e8b142feb1 refactor(telegram): remove native draft previews 2026-06-13 21:45:22 +05:30
Peter Steinberger
418d7e1e83 fix(clickclack): allow explicit enable through plugin allowlist (#92084)
Allow an explicit canonical ClickClack enable/setup selection to record ClickClack in a nonempty plugin allowlist, while preserving unrelated allowlist rejection, denylist authority, and global plugin disablement.

Validated at source head 24af9d8e75 with focused regressions, built-CLI disposable-config E2E, security checks, and autoreview. Merged under owner authorization despite the two documented untouched-main agent-core baseline failures.
2026-06-10 21:47:20 -07:00
Omar Shahine
bfccbc3fee fix(imessage): harden outbound send transport (#91783)
Merged via squash.

Prepared head SHA: 39ea25767b

Proof:
- Focused tests, docs/config generation, lint/type/doc checks passed before merge.
- ClawSweeper re-review marked proof and patch quality platinum after lobster live send proof.
- Maintainer accepted the `channels.imessage.sendTransport` config surface and compatibility-risk tradeoff.

Lobster proof id: openclaw-lobster-live-proof-c74895c2-b629-4bb0-abcb-e6521069b3d8

Reviewed-by: @omarshahine
2026-06-09 19:09:15 -07:00
Dallin Romney
370cef2e3b docs: align Feishu DM policy defaults (#91755) 2026-06-09 12:31:47 -07:00
Dallin Romney
a2dd821908 docs: clarify matrix plugin upgrade repair (#91753) 2026-06-09 12:22:59 -07:00
Omar Shahine
fc6400ede3 fix(imessage): always-on inbound recovery and dedupe (#91335)
* feat(imessage): always-on inbound recovery, deprecate catchup

Replaces the opt-in catchup subsystem with always-on inbound replay
protection that brings iMessage in line with the other channels, and
fixes #89237 (stale backlog dispatched as fresh after bridge recovery).

- New inbound-dedupe.ts: persistent claimable GUID dedupe (claim/commit/
  release) plus a stale-backlog age fence that suppresses live rows whose
  send date is materially older than arrival (logged, never silent).
- monitor-provider: claim at ingestion, carry the exact claimed key on the
  debouncer entry, commit on successful flush / release on dispatch failure
  (per-unit so a coalesced bucket cannot strand a sibling claim). Keeps the
  local startup since_rowid watermark so startup-window rows are not skipped.
- Deprecate catchup: delete catchup.ts + catchup-bridge.ts, remove the
  channels.imessage.catchup schema, cursor migration, and config-guard nag.
  Back-compat: strip the retired key before validation; new imessage doctor
  contract reports + removes it on doctor --fix.
- Docs updated for the new recovery model.

Net -947 prod LOC.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(imessage): recover downtime messages via since_rowid replay

Builds downtime recovery on the new inbound dedupe instead of restoring the
old catchup subsystem. On startup the monitor passes the last dispatched rowid
(a persisted per-account cursor) to imsg watch.subscribe as since_rowid, so imsg
replays the messages that landed while the gateway was down, then tails live.
The GUID dedupe drops anything already handled, so no cursor/retry bookkeeping
is needed.

- recovery-cursor.ts: minimal persisted per-account lastDispatchedRowid.
- monitor-provider: since_rowid = cursor (capped to the most recent
  IMESSAGE_RECOVERY_MAX_ROWS); split the age fence on the startup rowid boundary
  so replayed rows (<= boundary) use the wider recovery window and live rows
  (> boundary) keep the tight #89237 fence; advance the cursor on commit.
- Local only: remote SSH cliPath cannot read chat.db, so it tails from the
  current rowid (suppress-and-move-on) as before.

Restores missed-message recovery that the catchup removal dropped, with no
config and a fraction of the old LOC.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(imessage): make recovery cursor advance failure- and suppression-safe

Addresses two cursor-state regressions in the downtime-recovery path:

- Failed replay rows could be skipped forever: a released (failed) row keeps
  its dedupe claim for retry, but a later successful row in the same flush
  advanced the cursor past it, so the next startup's since_rowid skipped it.
  Hold a per-session floor at the lowest released rowid and never advance the
  cursor past it.
- Suppressed live backlog could be re-delivered after a restart: a live row
  suppressed under the tight live fence was not recorded, so after a restart it
  fell under the wider recovery window (its rowid now below the new boundary)
  and was delivered. Commit its dedupe key on suppression so the recovery
  replay treats it as already handled.

Both caught by Codex autoreview. Adds regression tests for the floor and the
suppression record.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(imessage): bound the GUID-less replay key length

Hash the composite fallback key's variable parts (conversation, sender,
created_at, text) so the key is length-bounded regardless of message text.
The persistent dedupe store already hashes keys internally, so this was not a
live overflow, but the bounded key removes the dependency on that and keeps the
fallback fail-open. Flagged by autoreview.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(imessage): recover downtime messages on remote cliPath setups too

The since_rowid replay runs over the imsg RPC client, so driving it from the
persisted recovery cursor (not the local chat.db boundary) makes downtime
recovery work for remote SSH cliPath gateways — the topology the old RPC-based
catchup served and that the rowid-boundary-only version regressed. Local setups
keep the wider, capped recovery window via the chat.db boundary; remote uses the
live age-fence window. Flagged by autoreview.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(imessage): seed recovery cursor from retired catchup cursor on upgrade

A one-time, self-cleaning migration: when the recovery cursor is empty on the
first startup after upgrade, seed it from the retired imessage.catchup-cursors
lastSeenRowid and consume the legacy entry. Without this a user who had catchup
enabled would not replay messages missed across the upgrade restart. Flagged by
autoreview.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(imessage): preserve catchup recovery on upgrade

---------

Co-authored-by: Omar Shahine <10343873+omarshahine@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-06-08 16:54:10 +09:00