Skip to content

feat(plugins): Tier-2 plugin capability layer (scoped ctx.messages/ctx.engine)#294

Merged
rmyndharis merged 10 commits into
mainfrom
feat/plugin-capability-layer
Jun 17, 2026
Merged

feat(plugins): Tier-2 plugin capability layer (scoped ctx.messages/ctx.engine)#294
rmyndharis merged 10 commits into
mainfrom
feat/plugin-capability-layer

Conversation

@rmyndharis

Copy link
Copy Markdown
Owner

Summary

Adds a Tier-2 plugin capability layer so extension plugins can act safely — the foundation for moving bot-shaped features (auto-reply, translation) out of core. Replaces the stubbed PluginContext.getService with curated, scoped capabilities.

This is Tier-2 of the engine-decoupling / pluggable-architecture effort (#265). It unblocks #278 (auto-translation) to be ported from a core module into a real extension plugin. MCP (#256) and a Baileys engine belong to the Tier-1 boot-time tier (a follow-up).

What's included

  • HookManager re-entrancy guard (AsyncLocalStorage) — a plugin that sends from inside a hook can't recurse into the same event (synchronous re-entry).
  • ctx.messagessendText / reply, routed through MessageService so persistence and the normal send pipeline are preserved.
  • ctx.engine — read-only scoped queries (getGroupInfo / getContacts / getContactById / checkNumberExists / getChats).
  • Manifest-declared sessions scope — enforced at the facade before any service/engine resolution; default ['*'].
  • auto-reply reference extension plugin — first-party, registered disabled by default; enable via POST /plugins/auto-reply/enable to validate the layer live.

Design notes

  • Capability services resolve lazily via ModuleRef + a runtime require of the class token to avoid a provider/module-load cycle (PluginLoaderService → SessionService → EngineFactory → PluginLoaderService). A static import of MessageService / SessionService corrupts MessageService's constructor paramtype metadata at boot — this was caught by the e2e boot gate during development, hence the dedicated e2e check.
  • A dead/unstarted session on a ctx.messages or ctx.engine call throws PluginCapabilityError (no orphaned pending DB row), consistent across both surfaces.
  • sendText fires the message:sending hook + anti-ban typing; reply is intentionally leaner (neither).

Breaking change

  • PluginContext.getService is removed (it was a stub returning undefined; zero real consumers). Out-of-tree plugins relying on it must migrate to ctx.messages / ctx.engine.

Known limitation (documented)

  • The re-entrancy guard covers synchronous re-entry only; the asynchronous message:sent echo loop is not guarded (a per-plugin send rate-limiter is deferred to the untrusted/sandbox tier).

Tests

  • Backend: 511/511 unit, e2e boot 4/4 (the e2e boot proves the DI cycle is broken at bootstrap).
  • Build + lint clean (backend + dashboard).

- Assert moduleRef.get is called in the reply delegation test.
- Add test proving manifest.sessions absent defaults to wildcard allow-all.
- Reword ModuleRef constructor comment to clarify it avoids (not causes)
  the PluginLoaderService → SessionService → EngineFactory cycle.
- Capture and assert `{ continue: true }` from the group-message guard so a
  future regression returning `{ continue: false }` is caught immediately.
- Add test for the `source !== 'Engine'` guard: overrides `source` to `'API'`
  via spread, asserts reply is not called and result is `{ continue: true }`.
@rmyndharis rmyndharis merged commit 8c2ea17 into main Jun 17, 2026
5 checks passed
@rmyndharis rmyndharis deleted the feat/plugin-capability-layer branch June 17, 2026 10:49
rmyndharis pushed a commit that referenced this pull request Jun 18, 2026
Ports the translation feature onto the Tier-2 capability layer (#294): the framework-agnostic core/ (coordinator, command parser, reply formatter, ports) is reused unchanged, with ChatGateway/ConfigStore implemented over ctx.messages / ctx.engine / ctx.storage and per-group state in plugin KV storage. Registered disabled by default; enable via POST /plugins/translation/enable. Supersedes the core-module approach in #278 now that the plugin send-capability exists.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant