mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-21 10:16:44 +00:00
docs(imessage): align recovery with durable ingress (#111002)
This commit is contained in:
@@ -748,10 +748,10 @@ The "Flag on" column shows behavior on an `imsg` build that emits `balloon_bundl
|
||||
|
||||
## Inbound recovery after a bridge or gateway restart
|
||||
|
||||
iMessage recovers messages missed while the gateway was down, and at the same time suppresses the stale "backlog bomb" Apple can flush after a Push recovery. The default behavior is always on, built on the inbound dedupe.
|
||||
iMessage recovers messages missed while the gateway was down, and at the same time suppresses the stale "backlog bomb" Apple can flush after a Push recovery. The default behavior is always on, built on durable ingress plus an age fence.
|
||||
|
||||
- **Replay dedupe.** Every dispatched inbound message is recorded by its Apple GUID in persistent plugin state (`imessage.inbound-dedupe`), claimed at ingestion and committed after handling (released on a transient failure so it can retry). Anything already handled is dropped instead of dispatched twice. This is what lets recovery replay aggressively without per-message bookkeeping.
|
||||
- **Downtime recovery.** On startup the monitor remembers the last dispatched `chat.db` rowid (a persisted per-account cursor) and passes it to `imsg watch.subscribe` as `since_rowid`, so imsg replays the rows that landed while the gateway was down, then tails live. Replay is bounded to the most recent 500 rows and to messages up to ~2 hours old, and the dedupe drops anything already handled.
|
||||
- **Durable replay protection.** Before advancing the recovery cursor, OpenClaw journals each raw row in the shared SQLite ingress queue with its Apple GUID as the event ID. A completed row leaves a tombstone for about 4 hours, capped at 10,000 entries, so a replay with the same GUID is dropped even after a restart. A pending row stays recoverable until dispatch adopts it.
|
||||
- **Downtime recovery.** On startup the monitor remembers the last durably admitted `chat.db` rowid (a persisted per-account cursor) and passes it to `imsg watch.subscribe` as `since_rowid`, so imsg replays rows that were not yet journaled and then tails live. Rows journaled before a crash resume from SQLite. Replay is bounded to the most recent 500 rows and to messages up to ~2 hours old, and GUID tombstones drop anything already handled.
|
||||
- **Stale-backlog age fence.** Rows above the startup boundary are genuinely live; one whose send date is more than ~15 minutes older than its arrival is the Push-flush backlog and is suppressed. Replayed rows (at or below the boundary) use the wider recovery window instead, so a recently-missed message is delivered while ancient history is not.
|
||||
|
||||
Recovery works over both local and remote `cliPath` setups, because `since_rowid` replay runs over the same `imsg` RPC connection. The difference is the window: when the gateway can read `chat.db` (local), it anchors the startup rowid boundary, caps the replay span, and delivers missed messages up to a couple of hours old. Over a remote SSH `cliPath` it cannot read the database, so the replay is uncapped and every row uses the live age fence — it still recovers recently-missed messages and still suppresses old backlog, just with the narrower live window. Run the gateway on the Messages Mac for the wider recovery window.
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
// Per-(account, database) high-water of the last dispatched chat.db rowid. On
|
||||
// startup it is passed to imsg `watch.subscribe` as `since_rowid` so imsg
|
||||
// replays the rows that landed while the gateway was down (downtime recovery),
|
||||
// then tails live. The GUID dedupe makes over-replay safe — anything already
|
||||
// handled is dropped — so this needs none of the cursor/retry bookkeeping the
|
||||
// old catchup subsystem carried. The database identity is part of the store key
|
||||
// (not just a number per account): a high-water from one chat.db must never seed
|
||||
// Per-(account, database) high-water of the last durably admitted chat.db rowid.
|
||||
// It advances only after the SQLite ingress enqueue, then seeds `since_rowid`
|
||||
// on startup. GUID-keyed ingress tombstones make over-replay safe, while rows
|
||||
// journaled before a crash resume from the queue. The store key also includes
|
||||
// the database identity: a high-water from one chat.db must never seed
|
||||
// since_rowid for a different one, or repointing `dbPath`/`remoteHost` to a
|
||||
// lower-rowid database silently suppresses every row in it forever (#99638).
|
||||
import { createHash } from "node:crypto";
|
||||
@@ -149,9 +147,9 @@ function migrateLegacyCatchupCursor(accountId: string, dbIdentity: string): numb
|
||||
}
|
||||
|
||||
/**
|
||||
* Last dispatched rowid for this account on `dbIdentity`, or null when none is
|
||||
* recorded yet (including when the only stored cursor belongs to a different
|
||||
* database).
|
||||
* Last durably admitted rowid for this account on `dbIdentity`, or null when
|
||||
* none is recorded yet (including when the only stored cursor belongs to a
|
||||
* different database).
|
||||
*/
|
||||
export function loadIMessageRecoveryCursor(
|
||||
accountId: string,
|
||||
|
||||
Reference in New Issue
Block a user