Skip to content

channels: opt-in SDK-backed persistent state for slack, discord, msteams, matrix#74933

Closed
amknight wants to merge 2 commits intomainfrom
ak/plugin-fixes-sdk-opt-in
Closed

channels: opt-in SDK-backed persistent state for slack, discord, msteams, matrix#74933
amknight wants to merge 2 commits intomainfrom
ak/plugin-fixes-sdk-opt-in

Conversation

@amknight
Copy link
Copy Markdown
Member

Summary

Follow-up to #74190 — wires the new SQLite-backed api.runtime.state.openKeyedStore into the four in-memory caches the PR called out as migration candidates, while keeping the existing process-local fast paths as the default and gating the persistent variant behind a per-plugin opt-in.

Each bundled channel plugin now declares experimentalPersistentState: boolean (default false) in its openclaw.plugin.json#configSchema. When operators opt in via plugins.entries.<id>.config.experimentalPersistentState: true, the in-memory cache is mirrored into a per-plugin namespace in the new state store so it survives restarts; otherwise the runtime behaves exactly as it does today.

Migrations

Plugin Cache Namespace
Slack thread participation (sent-thread-cache) slack.thread-participation
Discord component & modal registries (components-registry) discord.components, discord.modals
MS Teams sent-message dedupe (sent-message-cache) msteams.sent-messages
Matrix approval reaction targets (approval-reactions) matrix.approval-reactions

For each plugin:

  • The synchronous record/lookup helpers stay sync and keep the in-memory cache hot, so the read path used by message dispatch is unchanged.
  • A new *ForConfig async variant is awaited by callers that already run in async contexts (prepare.ts, dispatch.ts, monitor handlers, reaction events, modal handlers). It checks the in-memory cache first and falls back to the persistent store on miss.
  • Persistent writes are fire-and-forget; failures are logged via runtime.logging.getChildLogger(...) and never throw into channel-side message handling.
  • Discord additionally calls delete on the persistent entry when an in-memory hit consumes a still-persisted entry, so consumed components/modals don't resurrect after restart.

Migrations intentionally deferred

PR #74190 listed two more candidates (BlueBubbles inbound dedupe, Feishu persistent dedupe) — both want claim/check semantics that aren't safely expressible on the v1 register / lookup / consume API yet, so they're left as follow-ups.

Tests

  • extensions/slack/src/sent-thread-cache.test.ts — opt-in gating + persistent register/lookup with a fake runtime store
  • extensions/discord/src/components.test.ts — same shape, covering both the component and modal stores
  • extensions/msteams/src/sent-message-cache.test.ts — same shape
  • extensions/matrix/src/approval-reactions.test.ts — same shape, including the resolved decision payload

Validation

Local from this worktree:

  • pnpm exec oxfmt --check on touched TS files — clean
  • pnpm test for the four new cache tests + relevant existing call-site tests (slack/action-runtime, slack/monitor/.../prepare, discord/monitor/monitor, matrix/.../reaction-events, matrix/approval-handler.runtime, msteams/monitor-handler/message-handler.authz) — 4 vitest projects, 126 tests passing
  • pnpm check:changed — extensions prod typecheck, extension tests typecheck, oxlint, runtime sidecar loaders, import cycles, dup coverage, conflict markers, changelog attributions, plugin-sdk wildcard re-exports — all clean

Have not yet run a broad pnpm check / pnpm test sweep — happy to run the broader gate (or Testbox) before flipping out of draft.

Closes / refs

…ams, matrix

Adds plugins.entries.<id>.config.experimentalPersistentState (default
false) to slack/discord/msteams/matrix, mirroring four in-memory caches
into the SDK-backed plugin state store while keeping the existing
process-local fast paths as the default behavior:

- Slack thread participation (sent-thread-cache)
- Discord component & modal registries (components-registry)
- MS Teams sent-message dedupe (sent-message-cache)
- Matrix approval reaction targets (approval-reactions)

Errors from the persistent path are logged via the plugin runtime and
never break the synchronous record/lookup paths. Each plugin gets a
*ForConfig async variant used by handlers that already run in async
contexts; opt-in is gated by resolvePluginConfigObject(cfg, ...).

Tests: extends each plugin's cache test with no-op-when-disabled and
persistent register/lookup with a fake runtime store. Updates relevant
docs (channels/{slack,discord,msteams,matrix}.md, plugins/sdk-runtime.md)
and the changelog.
@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation channel: discord Channel integration: discord channel: matrix Channel integration: matrix channel: msteams Channel integration: msteams channel: slack Channel integration: slack plugin: file-transfer size: XL maintainer Maintainer-authored PR labels Apr 30, 2026
@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented Apr 30, 2026

Codex review: needs maintainer review before merge.

What this changes:

The PR adds opt-in experimentalPersistentState config, docs, tests, and SDK keyed-store persistence for Slack thread participation, Discord component/modal registries, Microsoft Teams sent-message markers, and Matrix approval reaction targets.

Maintainer follow-up before merge:

Protected maintainer label plus draft state make this an owner review item, and the diff review did not identify a concrete defect suitable for an automated repair PR.

Security review:

Security review cleared: Read-only diff review found no workflow, dependency, lockfile, lifecycle script, secret-handling, or third-party code execution changes; persisted data is existing channel metadata behind opt-in plugin config.

Review details

Best possible solution:

Keep the PR open for maintainer review. If accepted, land the opt-in bundled-plugin migrations on top of the existing keyed-store SDK, preserve process-local defaults, and keep BlueBubbles/Feishu claim-style dedupe work deferred until the store has safe claim/check semantics.

Do we have a high-confidence way to reproduce the issue?

Not applicable as a feature migration PR. The motivating limitation is statically verifiable on current main: the targeted Slack, Discord, Microsoft Teams, and Matrix registries are process-local, so restart drops their state.

Is this the best way to solve the issue?

Unclear pending maintainer review. The opt-in per-plugin config over the bundled-only keyed-store SDK appears narrow and consistent with #74190, but protected draft status means owner validation is still the right next step.

What I checked:

  • Protected PR routing: GitHub issue API shows the PR is open, draft, authored by a contributor, and labeled maintainer, which requires explicit maintainer handling rather than automated cleanup closure. (f0b8ac61e959)
  • PR diff scope: GitHub PR files API lists 30 changed files limited to changelog, channel/plugin docs, four bundled plugin manifests, channel cache/runtime code, and focused tests; no workflow, dependency, lockfile, package script, or vendor artifact changes were present. (f0b8ac61e959)
  • State-store foundation exists: Current main exposes runtime.state.openKeyedStore through the plugin runtime proxy and restricts it to bundled plugins, matching the PR's intended bundled-channel migration surface. (src/plugins/registry.ts:2025, d117ed183aaa)
  • Current Slack cache is still process-local: Slack thread participation on current main uses a process-global dedupe cache with no persistent keyed-store path. (extensions/slack/src/sent-thread-cache.ts:16, d117ed183aaa)
  • Current Teams cache is still process-local: Microsoft Teams sent-message markers on current main are stored in a process-global map with a 24-hour TTL, so a gateway restart drops the marker state. (extensions/msteams/src/sent-message-cache.ts:1, d117ed183aaa)
  • Current Discord registry is still process-local: Discord component and modal entries on current main are stored in process-global maps with local TTL and consume behavior. (extensions/discord/src/components-registry.ts:4, d117ed183aaa)

Likely related people:

  • amknight: Merged feat(plugins): add SQLite plugin state store #74190 introduced the SQLite-backed plugin state store and documented these channel cache migrations as intended follow-up consumers. (role: state-store foundation author; confidence: high; commits: bbf985d50a64; files: src/plugin-state/plugin-state-store.ts, docs/plugins/sdk-runtime.md)
  • steipete: Recent remote history shows work on plugin SDK/runtime boundaries and channel cache seams across Slack, Teams, and Discord, including Slack cache refactors and Discord registry alignment. (role: recent plugin SDK and channel-cache maintainer; confidence: high; commits: f0000ab72d01, 89d65521fe34, 78160b5f8813; files: extensions/slack/src/sent-thread-cache.ts, extensions/msteams/src/sent-message-cache.ts, extensions/discord/src/components-registry.ts)
  • gumadeiras: Remote path history points to the Matrix exec approval reaction shortcut implementation as the origin of the Matrix approval reaction target behavior this PR persists. (role: Matrix approval behavior owner; confidence: high; commits: 0aaf7531481d; files: extensions/matrix/src/approval-reactions.ts, extensions/matrix/src/approval-handler.runtime.ts, extensions/matrix/src/matrix/monitor/reaction-events.ts)
  • TaKO8Ki: Remote path history points to the MSTeams sent-message dedupe storage change as the origin of the current process-local marker behavior. (role: Microsoft Teams sent-message cache introducer; confidence: high; commits: 0bee3f337ae5; files: extensions/msteams/src/sent-message-cache.ts)

Remaining risk / open question:

  • The PR is draft and protected by maintainer, so merge readiness depends on explicit maintainer review.
  • The author reports targeted tests and pnpm check:changed, but this read-only review did not run Testbox or a broad pnpm check/pnpm test sweep.

Codex review notes: model gpt-5.5, reasoning high; reviewed against d117ed183aaa.

Address draft PR review feedback for the opt-in SDK-backed channel state
migration:

- backfill Slack and MS Teams in-memory caches after persistent lookup hits
  so hot inbound paths only pay the SQLite lookup once per key after restart
- reset cached persistent store handles from existing cache clear helpers so
  tests and runtime reset paths reopen stores from the current runtime
- use a narrow Matrix plugins config lookup type instead of a double cast
- document why Slack persists agentId even though current reads only check
  participation presence

Validation: pnpm test extensions/slack/src/sent-thread-cache.test.ts extensions/discord/src/components.test.ts extensions/msteams/src/sent-message-cache.test.ts extensions/matrix/src/approval-reactions.test.ts; pnpm check:changed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: discord Channel integration: discord channel: matrix Channel integration: matrix channel: msteams Channel integration: msteams channel: slack Channel integration: slack docs Improvements or additions to documentation maintainer Maintainer-authored PR plugin: file-transfer size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant