feat(relay): notify paired chats of lifecycle#38
Conversation
There was a problem hiding this comment.
Pull request overview
Adds best-effort, deduped lifecycle notifications (offline / restored-online / local disconnect) to paired Telegram/Discord/Slack conversations, including persisted lifecycle metadata and instance-scoped delivery for multi-instance Discord/Slack runtimes.
Changes:
- Introduces lifecycle notification domain/formatting + dedupe/rate-limit decision logic with persisted metadata in relay state.
- Emits lifecycle notifications from extension lifecycle edges (
session_start,session_shutdown,/relay disconnect) and delivers via Telegram/Discord/Slack runtimes (Discord/Slack scoped to the matching instance). - Adds unit, runtime, and integration tests plus OpenSpec change docs for the new lifecycle notification behavior.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/slack-runtime.test.ts | Adds a regression test ensuring lifecycle notifications are delivered only via the matching Slack instance. |
| tests/relay/lifecycle.test.ts | Adds unit tests for lifecycle message formatting, dedupe/rate-limit decisions, and backward-compatible persistence. |
| tests/integration.test.ts | Adds integration coverage for Telegram lifecycle notifications and ensures failures are nonfatal diagnostics. |
| tests/discord-runtime.test.ts | Adds a regression test ensuring lifecycle notifications are delivered only via the matching Discord instance. |
| openspec/changes/notify-relay-lifecycle-events/tasks.md | Tracks implementation tasks completed for the OpenSpec change. |
| openspec/changes/notify-relay-lifecycle-events/specs/relay-lifecycle-notifications/spec.md | Specifies lifecycle notification requirements, safety, dedupe, and nonfatal failure behavior. |
| openspec/changes/notify-relay-lifecycle-events/specs/messenger-relay-sessions/spec.md | Extends messenger-neutral session semantics with lifecycle presence requirements. |
| openspec/changes/notify-relay-lifecycle-events/proposal.md | Captures motivation, scope, and impact of lifecycle notification feature. |
| openspec/changes/notify-relay-lifecycle-events/design.md | Documents design decisions, trade-offs, and migration plan. |
| openspec/changes/notify-relay-lifecycle-events/.openspec.yaml | Registers the OpenSpec change metadata. |
| extensions/relay/state/tunnel-store.ts | Adds persisted lifecycleNotifications and a recordLifecycleNotification() helper that updates dedupe metadata atomically. |
| extensions/relay/runtime/extension-runtime.ts | Wires lifecycle notifications into session start/shutdown and local disconnect flows, collecting nonfatal failures. |
| extensions/relay/notifications/lifecycle.ts | New lifecycle notification formatter + storage key + dedupe/rate-limit decision logic. |
| extensions/relay/core/types.ts | Extends persisted store shape with lifecycleNotifications. |
| extensions/relay/adapters/slack/runtime.ts | Adds Slack runtime notifyLifecycle() using instance-scoped bindings and dedupe metadata. |
| extensions/relay/adapters/discord/runtime.ts | Adds Discord runtime notifyLifecycle() using instance-scoped bindings and dedupe metadata. |
cefa68c to
4175ace
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
extensions/relay/runtime/extension-runtime.ts:1198
session_shutdownunregisters Telegram and Slack routes but does not unregister Discord routes. This leaves the Discord runtime with a liveSessionRouteafter the session has ended, so inbound Discord messages can still be routed/injected (and the Discord polling client may stay running), violating the offline safety boundary. Add afor (const discordRuntime of discordRuntimes.values()) await discordRuntime.unregisterRoute(currentRoute.sessionKey);similar to the Slack loop (and consider clearingcurrentRouteif appropriate).
if (runtime && currentRoute) {
await runtime.unregisterRoute(currentRoute.sessionKey);
}
if (currentRoute) {
for (const slackRuntime of slackRuntimes.values()) await slackRuntime.unregisterRoute(currentRoute.sessionKey);
|
Evaluated the suppressed low-confidence Copilot note about Discord shutdown routing and confirmed it was valid: session_shutdown unregistered Telegram/Slack routes but not Discord. Addressed in 7c4ebab by unregistering Discord runtimes during shutdown and adding integration coverage that verifies Discord unregisterRoute is called. Validation: npm run typecheck; npm test; openspec validate notify-relay-lifecycle-events --strict. |
Summary
Stack
Validation