openclaw/extensions/telegram
PollyBot13 dba4da1f8b
fix(channels): keep deeper queued messages alive through long turns (#133248)
## Contributor description — original implementation

The contributor’s original description follows. Historical heads, scope and open decisions in this section refer to that implementation; the reviewed current implementation, compatibility decision and proof are recorded in the maintainer update below.

Closes #133245.

Related: #127950. This supersedes the current-main-incompatible queue-owned approach in #125385 while preserving today's canonical timeout retry disposition.

## What Problem This Solves

A durably claimed channel message can wait behind another follow-up turn for longer than the five-minute claim-to-adoption watchdog. Existing liveness renewal covers queue-head active-run admission checks, but not an ingress-backed lifecycle waiting deeper in the follow-up queue. The claim can therefore be retired before the message reaches the model.

## Why This Change Was Made

The follow-up queue now owns periodic deferred heartbeats for the exact ingress lifecycle while it remains in pending items, in-flight delivery, summary sources, or compacted summary elisions.

Ingress supplies a cadence derived from one third of its adoption-stall timeout. Debounce, Plugin SDK fan-in, and channel lifecycle wrappers preserve the shortest applicable cadence. Queue ownership triggers an immediate renewal, then periodic renewal; it stops after successful adoption, completion, ownership loss, or callback failure. A rejected adoption callback keeps renewal alive for the supported retry path.

The existing watchdog and canonical timeout retry policy remain unchanged, so silent handlers and orphaned claims still recover normally.

## User Impact

Messages accepted by durable channel ingress remain adoptable while they wait behind long-running work instead of silently disappearing before model execution. No setting or migration is required.

## Evidence

SDK documentation polish (2026-09-10, head `76097fef9397a04c10637e06dfaf64ebb6ca104a`): the public channel SDK guide now documents forwarding both heartbeat fields, the shortest-positive-finite fan-in cadence, renewal termination, and compatibility when older wrappers omit the optional cadence. The documentation-only addition passed changed-page formatting, MDX sanity, and `git diff --check`; production code and the previously tested runtime head below are unchanged. Current-head hosted CI has completed successfully; the final exact-head ClawSweeper review accepts the implementation and proof, leaving only SDK-owner acceptance of the documented contract.

Refreshed on 2026-09-08 for exact head `57c4faff0ed7bb2d9b4fd308aa46b19ae4095e61`, rebased onto `1ff45cad5a`.

- Preserved upstream's ingress-monitor type extraction and gateway-suspension repair; the optional cadence field follows its new type owner.
- Repaired the retry integration fixture: after real enqueue and initial renewal, only the abandoned message's heartbeat callback fails. The real watchdog then recovers it. Retry delivery, healthy sibling cleanup, and true duplicate assertions remain intact; no production behavior or timeout changed for this repair.
- 102 focused tests pass: dispatch ingress retry, queue in-flight/dedupe, ingress lifecycle/watchdog, Plugin SDK fan-in, Discord queue handling, and Slack handler.
- Changed-file gates pass: core/core-test/extension typechecks, formatting, changed core/extension lint, SDK boundaries/exports, dead-export scans, and repository guards.
- Fresh independent source review found no actionable P0–P2 defects. The standalone autoreview CLI failed at startup without a verdict and is not counted as review coverage.
- Current-head CI run [34520397963](https://github.com/openclaw/openclaw/actions/runs/34520397963) completed successfully. The earlier startup subprocess timeout is historical, not a current failing gate. Exact-head ClawSweeper review (September 10, 20:00 UTC) reports no actionable correctness or proof findings; explicit SDK-owner acceptance remains required before merge.
- Existing regressions cover immediate renewal after late handoff, periodic deeper-queue renewal, stopping after owner loss, and continuing across a rejected adoption callback.

Exact-head durable-ingress boundary proof used isolated SQLite with the production ingress drain/lifecycle binder, follow-up queue, and canonical adoption helper. The model callback was synthetic; this is not live Telegram or Discord transport proof. No production Gateway, channel state, or configuration was touched. Temporary state and queue ownership were cleaned up.

```json
{
  "schema": "openclaw.pr133248.durable-ingress-proof.v1",
  "exactHead": "57c4faff0ed7bb2d9b4fd308aa46b19ae4095e61",
  "setup": {
    "durableStore": "isolated OpenClaw SQLite state",
    "executor": "production follow-up queue with synthetic model callback",
    "transportLifecycle": "production ingress drain lifecycle",
    "adoptionStallTimeoutMs": 180
  },
  "queuedBehindLongTurn": {
    "heldMs": 620,
    "formerDeadlineCrossed": true,
    "heartbeatCount": 11,
    "claimStillOwnedAtCheckpoint": [
      "queued-event"
    ],
    "retryRowsAtCheckpoint": 0,
    "failedRowsAtCheckpoint": 0,
    "executionCount": 1,
    "duplicateAfterAdoption": "completed"
  },
  "orphanRecovery": {
    "status": "released-for-retry",
    "attempts": 1,
    "lastErrorContainsHandlerTimeout": true
  },
  "productionTouched": false
}
```

Owner acceptance:
- Intended behavior: accepted messages remain adoptable while their exact lifecycle is owned by the queue; ownerless claims retain canonical timeout recovery.
- Boundary: one optional cadence field propagated through existing shared lifecycle and channel wrappers; no configuration, schema, or migration change.
- Maintainer decision remains open: accept the additive public Plugin SDK lifecycle field and its queue-owned cadence semantics. Bot review is not that acceptance.
- Rollback: revert the renewal fix and its companion retry-fixture adjustment.
- Scope: 23 files, +273/-2. The width is required lifecycle forwarding; renewal policy stays in the queue and ingress drain.

AI-assisted.

---

## Maintainer update — reviewed head `9325ad501aa4`

## What Problem This Solves

Messages accepted while a long reply is running can expire in the follow-up queue and consume a retry. The reproduction confirmed expiry and retry; all three messages eventually arrived. It did not reproduce the older permanent-loss report in #133245.

## Fix and impact

The queue starts one heartbeat when it accepts a lifecycle and stops it on adoption, completion, cancellation, or callback failure. Heartbeats no longer scan queue collections or copy the in-flight set. Ingress remains responsible for the watchdog and retry settlement, and a heartbeat cannot undo the watchdog pause during adoption finalization.

Ingress derives the cadence from its adoption timeout. The optional lifecycle metadata remains necessary because wrappers rebuild callbacks and combine abort signals, losing the original timeout. No channel setting, schema, migration, dependency, or protocol change is added.

The simplification removes 110 lines net from the initial implementation plus main merge. Total production growth is 40 lines. Existing lifecycle types are reused, and the queue cases share the existing lifecycle test fixtures.

## Evidence

- Real Gateway and Discord: three marked messages on one route, with a queued reply held for 330 seconds. Main expired the third claim after 300.004 seconds. This head retained the same claim at 322.257 seconds with zero attempts; replies arrived once, in order, at 17.804, 349.201, and 350.223 seconds.
- Real SQLite/drain/binder/queue controls: explicit abandonment releases the claim; callback failure stops renewal and lets the watchdog retry without model execution.
- 191 core owner/sibling tests and 190 channel tests passed. All five new regression cases failed on plain main for the intended reasons.
- The local preflight passed core/extension type-aware lint, production and test types, script types, and protocol checks in an isolated checkout of this head.
- External consumers compiled and ran against published 2026.9.4 and this head, including omitted metadata, forwarding, optional return fields, and asynchronous abandonment.

## Consumers

Shared ingress binding, batching, reply dispatch, and the Feishu, Slack, Telegram, and Twitch wrappers preserve the source cadence. The fan-in uses the shortest valid cadence. Legacy wrappers that omit the optional field retain their existing head-only heartbeat behavior. Adoption, queue clearing, overflow, cancellation, summaries, and drain replacement retain or finish the same lifecycle owner.

Closes #133245.

Original implementation and report by @PollyBot13 (#133245). The contributor's commits and authorship are retained.

AI-assisted.

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-09-14 10:52:52 +05:30
..
assets feat: give every plugin a compact chat activity icon (#147333) 2026-09-13 13:49:51 -07:00
src fix(channels): keep deeper queued messages alive through long turns (#133248) 2026-09-14 10:52:52 +05:30
account-inspect-api.ts
AGENTS.md fix(telegram): preserve rich button labels in reply context (#133307) 2026-08-30 20:25:14 +05:30
allow-from.ts
api.test.ts
api.ts
channel-config-api.test.ts
channel-config-api.ts
channel-plugin-api.ts
CLAUDE.md
config-api.ts
config-doctor-api.ts fix(doctor): repair channel config before external plugin installation (#132446) 2026-08-29 02:29:24 -07:00
contract-api.ts
directory-contract-api.ts
doctor-contract-api.ts perf: defer migration runtimes during Doctor config repair (#145334) 2026-09-11 16:15:39 -07:00
index.ts
miniapp-api.ts
openclaw.plugin.json feat(plugins): define package-owned categories (#142710) 2026-09-09 19:05:40 -07:00
package.json fix(agents): announce channel turns resumed after restart (#141797) 2026-09-13 13:04:00 -04:00
runtime-api.ts
runtime-setter-api.ts
secret-contract-api.ts
security-audit-contract-api.ts
session-key-api.ts
setup-entry.ts
setup-plugin-api.ts
test-api.ts fix(channels): show progress-card notes in drafts (#144790) 2026-09-13 21:08:45 +05:30
tsconfig.json
update-offset-runtime-api.ts