Skip to content

feat: implement channel system with various channel types and tools f…#340

Merged
frontegg-david merged 11 commits into
mainfrom
support-claude-channels
Apr 6, 2026
Merged

feat: implement channel system with various channel types and tools f…#340
frontegg-david merged 11 commits into
mainfrom
support-claude-channels

Conversation

@frontegg-david
Copy link
Copy Markdown
Contributor

@frontegg-david frontegg-david commented Apr 6, 2026

…or notifications

Summary by CodeRabbit

  • New Features
    • Real-time Channels: push notifications from multiple source types, optional two‑way replies, and configurable replay buffering (with eviction).
  • Tools & Flows
    • Runtime tools/flows to emit events, send channel notifications, reply, simulate file/messaging inputs, inspect/clear replay buffers, and manage test sessions.
  • End-to-end Tests
    • New E2E suites covering channels, replay behavior, file-watcher, security/isolation, and error/edge cases.
  • Documentation
    • New guides, SDK references, examples, and a channels skill pack.
  • Chores
    • Package version bumps and new e2e project/configuration.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 6, 2026

📝 Walkthrough

Walkthrough

Adds a Channels subsystem: channel metadata/decorators, runtime instances and registry, event bus, notification delivery (subscriptions, replay buffer, two‑way replies), scope/transport wiring, flows/tools, extensive docs, an E2E demo app with tests, and version bumps across packages.

Changes

Cohort / File(s) Summary
Core channel runtime & registry
libs/sdk/src/channel/... channel.instance.ts, channel.registry.ts, channel.events.ts, channel.utils.ts, index.ts, channel-scope.helper.ts, channel-notification.service.ts
New ChannelInstance/ChannelRegistry/ChannelEmitter, ChannelNotificationService, ChannelEventBus, normalization/helpers, capability registration, lifecycle/teardown, and public exports.
Channel sources & wiring
libs/sdk/src/channel/sources/... app-event.source.ts, webhook.source.ts, agent-completion.source.ts, job-completion.source.ts, index.ts
Adapters for app-event bus, webhook middleware, agent/job emitters, filtering and session-scoped forwarding.
Flows & reply tooling
libs/sdk/src/channel/flows/..., libs/sdk/src/channel/reply/... send-channel-notification.flow.ts, list-channels.flow.ts, reply/channel-reply.tool.ts, reply/reply.types.ts
Flows to list/send channel notifications and a validated channel-reply tool for two‑way replies.
Common infra: decorators, types & entries
libs/sdk/src/common/... decorators/channel.decorator.ts, interfaces/channel.interface.ts, records/channel.record.ts, entries/channel.entry.ts, metadata/channel.metadata.ts, tokens/channel.tokens.ts, schemas/annotated-class.schema.ts
Channel decorator + function builder, ChannelContext base class, Zod metadata, ChannelEntry abstraction, tokens and annotated-item detection.
Scope, notification & transport integration
libs/sdk/src/scope/scope.instance.ts, libs/sdk/src/notification/notification.service.ts, libs/sdk/src/transport/..., libs/sdk/src/front-mcp/front-mcp.ts, libs/sdk/src/transport/mcp-handlers/initialize-request.handler.ts
Scope optionally initializes channels; NotificationService gains channel subscription APIs and custom send; transports advertise channel capabilities and auto‑subscribe sessions; init/shutdown wiring updated.
E2E demo app & tests
apps/e2e/demo-e2e-channels/src/..., apps/e2e/demo-e2e-channels/e2e/*.e2e.spec.ts, project.json, jest.e2e.config.ts, tsconfig*, webpack.config.js, src/main.ts, src/config.ts
New demo ChannelsApp, multiple demo channel implementations, MCP tools for testing, Jest E2E config, build/serve/test targets, and comprehensive E2E suites (replay, security/session isolation, file-watcher, errors).
Demo channel implementations & helpers
apps/e2e/demo-e2e-channels/src/apps/channels/channels/* chat-bridge.channel.ts, deploy-alert.channel.ts, error-alert.channel.ts, file-watcher.channel.ts, manual-status.channel.ts, messaging-service.channel.ts, replay-alert.channel.ts
Demo channels (webhook, app-event, file-watcher, manual, service, two‑way) and test utilities (replyLog, sentMessages, queues, simulate helpers).
Demo tools for testing
apps/e2e/demo-e2e-channels/src/apps/channels/tools/* emit-app-event.tool.ts, send-channel-notification.tool.ts, simulate-incoming.tool.ts, file-watcher-tools.tool.ts, list-reply-log.tool.ts, replay-tools.tool.ts, session-tools.tool.ts
MCP tools to emit events, send notifications, simulate incoming/file events, list logs, manage replay buffers, and manage fake sessions/notifications.
Docs, guides & skill catalog
docs/frontmcp/..., libs/sdk/src/channel/README.md, libs/skills/catalog/frontmcp-channels/..., docs/docs.json
Extensive docs and guides for channels: features, decorator API, ChannelContext, registry, sources, replay, two‑way examples, skill catalog and navigation updates.
Monorepo packaging & exports
libs/*/package.json, libs/sdk/package.json, libs/sdk/src/index.ts, libs/sdk/src/notification/index.ts
Multiple libs bumped to 1.0.2; SDK package.json adjustments; added channel re-exports and notification helper exports; minor packaging edits.
Demo build & config
apps/e2e/demo-e2e-channels/jest.e2e.config.ts, tsconfig*.json, webpack.config.js, project.json
Jest E2E config, TypeScript project configs, Webpack build config and Nx project setup for the demo app.

Sequence Diagram(s)

sequenceDiagram
  participant External as External Source
  participant EventBus as ChannelEventBus
  participant Registry as ChannelRegistry
  participant Channel as ChannelInstance
  participant Notifier as ChannelNotificationService
  participant Session as MCP Session (Client)

  External->>EventBus: emit(eventName, payload)
  EventBus->>Registry: route to ChannelInstance.handleEvent(payload)
  Registry->>Channel: create/obtain context -> onEvent()
  Channel->>Channel: (optional) append to replay buffer
  Channel->>Notifier: pushNotification(content, meta, targetSessionId?)
  Notifier->>Session: send "notifications/claude/channel" (filtered by capability/subscription)
  Session-->>Registry: call tool "channel-reply"(channel_name, text, meta)
  Registry->>Channel: handleReply(text, meta)
  Channel->>External: onReply() forwards reply to external system
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐇
I hop through channels, one by one,
Events arrive, the buffering's done,
Five kept safe, the newest sing,
Replies hop back on nimble wing,
Carrots for each routed run.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch support-claude-channels

Comment thread libs/sdk/src/channel/channel-scope.helper.ts Fixed
Comment thread libs/sdk/src/channel/reply/channel-reply.tool.ts Fixed
Comment thread libs/sdk/src/common/decorators/channel.decorator.ts Fixed
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
libs/sdk/src/common/metadata/app.metadata.ts (1)

19-34: ⚠️ Potential issue | 🟡 Minor

Update SDK documentation to include the new channels field in LocalAppMetadata.

The new channels?: ChannelType[] field on LocalAppMetadata is a public SDK surface that requires documentation. Update docs/frontmcp/sdk-reference/decorators/app.mdx to add channels to the "Optional Properties" or "Components" table of the @App decorator configuration options. Also verify that docs/frontmcp/servers/apps.mdx includes this field in its "App options" table.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/sdk/src/common/metadata/app.metadata.ts` around lines 19 - 34, Add the
new public SDK field channels?: ChannelType[] to the `@App` decorator docs and
server app options docs: update the SDK reference page that lists
LocalAppMetadata/@App optional properties to include an entry "channels?:
ChannelType[] — list of channel types the app supports" and ensure the
servers/apps documentation's "App options" table also lists this property;
reference the LocalAppMetadata type and ChannelType symbol in the description
and mark it as optional in both tables so consumers see it in the public SDK
surface.
🟠 Major comments (18)
libs/skills/catalog/frontmcp-channels/examples/channel-two-way/whatsapp-bridge.md-11-12 (1)

11-12: ⚠️ Potential issue | 🟠 Major

Webhook signature validation is declared but not implemented in the example.

The page advertises signature validation, but the code path accepts and processes webhook payloads without validating provider signatures first. For a chat bridge, this weakens sender trust and allows spoofed inbound events.

As per coding guidelines (referenced libs/skills/catalog/frontmcp-channels/SKILL.md): "for sources like chat bridges enforce sender authentication/allowlisting to prevent prompt injection."

Also applies to: 20-95

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@libs/skills/catalog/frontmcp-channels/examples/channel-two-way/whatsapp-bridge.md`
around lines 11 - 12, The example advertises webhook signature validation but
never performs it; add a validateWebhookSignature function and call it at the
start of the webhook handler (e.g., in the handler that currently processes
inbound events such as handleWebhookEvent or processInboundEvent) to verify
provider signatures or HMACs and reject requests that fail validation before any
parsing or processing; implement signature extraction/verification logic
consistent with the provider (e.g., compare computed HMAC of the raw body to the
X-Signature header or check a JWT) and return a 401/403 on failure, and annotate
the example with the required environment/config values (signing secret/header
name) so the new validation is clearly referenced in the flow.
libs/sdk/src/channel/channel.types.ts-1-16 (1)

1-16: 🛠️ Refactor suggestion | 🟠 Major

Move IndexedChannel to common/records to match SDK record-type placement.

This index-row shape is a record-type artifact and should be centralized instead of living under a feature module path.

As per coding guidelines, "Centralize record types in common/records directory instead of scattering across module-specific files".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/sdk/src/channel/channel.types.ts` around lines 1 - 16, The
IndexedChannel interface is a record-type that should be moved to the
centralized records area: create a new exported type file in the common/records
module that declares export interface IndexedChannel { instance:
ChannelInstance; owner: EntryOwnerRef; resolvedName: string } and import the
referenced types ChannelInstance and EntryOwnerRef from their existing modules,
then update all usages/imports of IndexedChannel throughout the codebase to
import from the new common/records file and remove the original IndexedChannel
declaration from the channel module (or replace it with a re-export if needed
for compatibility).
libs/sdk/src/front-mcp/front-mcp.ts-482-488 (1)

482-488: ⚠️ Potential issue | 🟠 Major

Add scope.notifications.unregisterServer(sessionId) to stdio shutdown handlers to prevent resource leak.

The stdio session is registered and auto-subscribed to channels (lines 483-488), but the SIGINT/SIGTERM handlers (lines 491-506) only close the server without cleanup. This leaves the session entry in the servers, subscriptions, and channelSubscriptions maps indefinitely. Compare with in-memory-server.ts (line 173), which properly calls unregisterServer() before server close:

Correct cleanup pattern
close: async () => {
  scope.notifications.unregisterServer(sessionId);
  await mcpServer.close();
}

Apply the same pattern to both SIGINT and SIGTERM handlers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/sdk/src/front-mcp/front-mcp.ts` around lines 482 - 488, The stdio
shutdown handlers currently only close mcpServer and leak session state; update
both SIGINT and SIGTERM handlers to first call
scope.notifications.unregisterServer(sessionId) to remove the session from
servers/subscriptions/channelSubscriptions, then await mcpServer.close();
reference the existing symbols sessionId, mcpServer and
scope.notifications.unregisterServer in the SIGINT and SIGTERM handler functions
so the unregister runs before closing the server.
libs/skills/catalog/frontmcp-channels/references/channel-sources.md-124-141 (1)

124-141: ⚠️ Potential issue | 🟠 Major

This twoWay example is incomplete without onReply().

With twoWay: true, readers will get channel-reply registered, but this sample never shows how replies are forwarded back to the service. As written, it demonstrates a feature that will just hit the default warning path.

As per coding guidelines, For two-way support, set twoWay: true so channel-reply is available, and implement onReply(reply, meta?).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/skills/catalog/frontmcp-channels/references/channel-sources.md` around
lines 124 - 141, The example sets twoWay: true on WhatsAppChannel (extending
ChannelContext) but omits the required onReply(reply, meta?) implementation that
forwards inbound replies to the underlying service; add an async onReply(reply:
string, meta?: unknown): Promise<void> method on WhatsAppChannel that transforms
the reply into the service payload and calls the WhatsApp client/send API (use
the same client used in onConnect, e.g., this.client) to deliver the message so
the registered channel-reply handler actually forwards replies instead of
hitting the default warning path.
libs/sdk/src/channel/sources/agent-completion.source.ts-42-44 (1)

42-44: ⚠️ Potential issue | 🟠 Major

agentIds should not match display names.

Matching event.agentName here broadens the documented contract and can notify the wrong channel when names collide or change. If name-based filtering is intentional, it should be a separate option.

As per coding guidelines, `agentIds` | `string[]` | Optional filter — only these agent IDs trigger the channel.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/sdk/src/channel/sources/agent-completion.source.ts` around lines 42 -
44, The filter currently checks both filterAgentIds and event.agentName which
violates the documented contract that filterAgentIds only matches agent IDs;
update the logic in the agent-completion source (where filterAgentIds is used)
to only compare filterAgentIds.includes(event.agentId) and remove the
includes(event.agentName) branch, and if name-based filtering is desired
introduce a separate option (e.g., filterAgentNames) instead of reusing
filterAgentIds.
apps/e2e/demo-e2e-channels/e2e/channels.e2e.spec.ts-56-92 (1)

56-92: ⚠️ Potential issue | 🟠 Major

These cases only validate the helper tool response, not channel delivery.

emit-app-event and send-channel-notification can both return success even if no notifications/claude/channel message ever reaches a capable session. The new session capture tools in this PR are a better fit here.

As per coding guidelines, Channel notifications are server-initiated and delivered only to MCP sessions that advertise experimental: { 'claude/channel': {} }.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/e2e/demo-e2e-channels/e2e/channels.e2e.spec.ts` around lines 56 - 92,
The tests currently only assert the tool responses from
server.callTool('emit-app-event') and
server.callTool('send-channel-notification') but do not verify actual channel
delivery; update the two specs ("App Event Source" / emit-app-event and "Manual
Channel Push" / send-channel-notification) to use the new session-capture
utilities provided in this PR to capture sessions that advertise experimental: {
'claude/channel': {} } and assert that a captured session actually received the
expected channel message (e.g., contains the event payload or notification with
channelName/status), rather than only parsing the tool response; keep the
existing tool calls but add capture/await logic to validate delivery to
ChannelEventBus/ChannelNotificationService recipients.
apps/e2e/demo-e2e-channels/e2e/channels.e2e.spec.ts-43-50 (1)

43-50: ⚠️ Potential issue | 🟠 Major

This test never checks the advertised channel capability.

Finding channel-reply in listTools() only proves channel registration ran. A broken initialize payload would still make every Claude client miss experimental['claude/channel'], and this test would stay green.

As per coding guidelines, Capability handshake: server advertises experimental: { "claude/channel": {} }.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/e2e/demo-e2e-channels/e2e/channels.e2e.spec.ts` around lines 43 - 50,
The test currently only checks server.listTools() for 'channel-reply' which
doesn't verify the initialize handshake; update the spec to call the server's
initialize/get-initialize response (e.g. server.initialize() or
server.getInitializeResponse()) and assert that the returned capabilities
include experimental['claude/channel'] (non-null/object), while keeping the
existing tools check for 'channel-reply' to ensure registration also ran.
libs/sdk/src/channel/sources/app-event.source.ts-45-49 (1)

45-49: ⚠️ Potential issue | 🟠 Major

The unsubscribe callback can tear down a newer subscription set.

Because the closure mutates the Set captured at subscribe time, calling an old unsubscribe after clear()/re-registration can delete the current this.handlers[event] entry as soon as the old set becomes empty. Guard this against a replaced set before mutating the map.

Suggested fix
     return () => {
-      handlers!.delete(handler);
-      if (handlers!.size === 0) {
+      const current = this.handlers.get(event);
+      if (!current || current !== handlers) {
+        return;
+      }
+      current.delete(handler);
+      if (current.size === 0) {
         this.handlers.delete(event);
       }
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/sdk/src/channel/sources/app-event.source.ts` around lines 45 - 49, The
unsubscribe closure currently mutates the captured Set (handlers) and can remove
a newly registered set if an old unsubscribe runs after re-registration; fix by
reading the current set from this.handlers for the same event inside the
returned function and only modify the map when the current set is the same Set
captured by subscribe and its size becomes 0. In other words, inside the
returned () => { ... } handler, guard with a check like currentSet === handlers
before deleting this.handlers.delete(event), and also null-check
handlers/currentSet before calling delete on the Set to avoid touching a
replaced set.
apps/e2e/demo-e2e-channels/src/apps/channels/tools/session-tools.tool.ts-193-195 (1)

193-195: ⚠️ Potential issue | 🟠 Major

Return a real tool error when the channel is missing.

A plain { error: ... } payload still looks like a successful tool invocation to MCP clients, so callers checking result.isError will miss this failure path. Throw the same tool/MCP error type the rest of the demo uses instead of encoding the failure in the payload.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/e2e/demo-e2e-channels/src/apps/channels/tools/session-tools.tool.ts`
around lines 193 - 195, Replace the plain return payload with a thrown tool/MCP
error so callers see a real failure: instead of `return { error: \`Channel
"${input.channelName}" not found\` };` throw the same demo error type used
elsewhere (e.g. `throw new ToolError(\`Channel "${input.channelName}" not
found\`)` or the project's MCP error class), import that error class into
session-tools.tool.ts, and use it where `channel` is falsy so callers checking
result.isError will detect the failure.
libs/sdk/src/channel/channel-scope.helper.ts-51-62 (1)

51-62: ⚠️ Potential issue | 🟠 Major

channelsConfig.defaultMeta never leaves config.

RegisterChannelCapabilitiesArgs accepts channelsConfig, and ChannelsConfigOptions documents defaultMeta, but this helper drops it before constructing ChannelNotificationService. Server-level default channel metadata is dead config right now.

Also applies to: 70-71

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/sdk/src/channel/channel-scope.helper.ts` around lines 51 - 62, The
helper is stripping channelsConfig.defaultMeta from the config so server-level
default channel metadata never gets passed into the notification service; update
the destructuring and construction so defaultMeta is preserved and forwarded:
when unpacking RegisterChannelCapabilitiesArgs avoid dropping
channelsConfig.defaultMeta (or capture it as defaultMeta) and include that
defaultMeta in the ChannelNotificationService instantiation (and any other
places referenced at the same spots around lines 70-71), ensuring
ChannelsConfigOptions.defaultMeta flows through to ChannelNotificationService.
libs/sdk/src/channel/channel.registry.ts-71-85 (1)

71-85: ⚠️ Potential issue | 🟠 Major

Fail fast on duplicate channel names.

The registry keys instances by DI token, but every delivery path here is name-based (findByName(), subscriptions, meta.source). Two different channel definitions that resolve to the same instance.name will be ambiguous at runtime; reject duplicates during initialization.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/sdk/src/channel/channel.registry.ts` around lines 71 - 85, During
initialize(), detect duplicate channel names and fail fast: after constructing
ChannelInstance (symbol ChannelInstance and variable instance) but before adding
to this.instances and this.localRows, check whether instance.name has already
been seen (track names with a Set or Map) and if so throw an error identifying
the duplicate name and the conflicting tokens/definitions; otherwise record the
name and proceed to set this.instances and push the localRows entry. This
ensures name-based lookup methods like findByName() won't be ambiguous.
docs/frontmcp/servers/channels.mdx-302-307 (1)

302-307: ⚠️ Potential issue | 🟠 Major

Clarify that replay buffers only global events.

Step 2 currently reads like every notification is stored. The rest of the channel docs/security model says replay is only for global events, so this wording makes targeted agent/job results sound replayable to future sessions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/frontmcp/servers/channels.mdx` around lines 302 - 307, The wording in
step 2 is misleading: only global events are stored in the replay buffer, not
every notification; update the docs around onEvent(), the in-memory ring buffer
description, and replayBufferedEvents(sessionId) to state explicitly that only
global (non-targeted) events are buffered and replayed, and clarify that
replayed events include meta.replayed="true" while targeted agent/job results
are excluded from the buffer and will not be replayed to future sessions.
libs/sdk/src/common/interfaces/channel.interface.ts-111-119 (1)

111-119: ⚠️ Potential issue | 🟠 Major

Don't silently drop replies from channel-reply.

Any twoWay channel gets reply support registered, but the default onReply() only logs a warning. That makes reply tool calls look successful while discarding the outbound message. Throw an explicit error here or reject twoWay: true during registration when onReply() is not overridden.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/sdk/src/common/interfaces/channel.interface.ts` around lines 111 - 119,
The default onReply implementation currently only logs a warning and silently
discards replies; change it to fail loudly by throwing an explicit Error from
the onReply(reply, meta?) method (include channelName in the message) so reply
tool calls cannot appear successful when unanswered. Alternatively (or
additionally), add a validation where channels are registered (check the
registration code that reads the twoWay flag) to reject/throw if twoWay === true
but the subclass has not overridden onReply, using a runtime check like
comparing the prototype implementation to ensure subclasses implemented onReply.
Ensure references to onReply, twoWay, and channelName are used in the
error/validation messages to make the failure clear.
libs/skills/catalog/frontmcp-channels/examples/channel-sources/service-connector.md-61-67 (1)

61-67: ⚠️ Potential issue | 🟠 Major

Either implement onReply() here or drop twoWay: true.

This snippet auto-registers channel-reply, but the class never forwards replies back to WhatsApp. As copied, readers get a reply tool that silently does nothing.

Also applies to: 69-109

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@libs/skills/catalog/frontmcp-channels/examples/channel-sources/service-connector.md`
around lines 61 - 67, The Channel configuration sets twoWay: true which
auto-registers the channel-reply handler but the class does not implement
onReply(), so replies are never forwarded; either remove twoWay: true or
implement an onReply(payload) method on the channel class that forwards incoming
replies to the WhatsApp service by invoking the SendWhatsAppTool (or the
equivalent send method) and handling errors/acknowledgements; reference the
Channel decorator configuration (name: 'whatsapp', twoWay: true), the generated
channel-reply behavior, and the SendWhatsAppTool/onReply method to locate where
to add the forwarding logic.
docs/frontmcp/servers/channels.mdx-444-446 (1)

444-446: ⚠️ Potential issue | 🟠 Major

Use the public notification service in this warning.

channel.pushNotification() looks like an internal instance hook, while the public surface in this PR is scope.channelNotifications.send() / sendToSession(). Point readers at that API so copied examples stay on the supported path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/frontmcp/servers/channels.mdx` around lines 444 - 446, Update the
warning to reference the public notification API instead of the internal
instance hook: replace mentions of channel.pushNotification() with the public
surface scope.channelNotifications.send() and/or
scope.channelNotifications.sendToSession() (and note passing targetSessionId
where session-scoped delivery is required) so examples point to the supported
API; update the text in the Warning block to call out these methods by name and
indicate which one enforces session isolation.
libs/sdk/src/channel/channel-notification.service.ts-119-123 (1)

119-123: ⚠️ Potential issue | 🟠 Major

Don't let additionalMeta.source override the selected channel.

send('deploy-alerts', ..., { source: 'error-alerts' }) currently routes to error-alerts subscribers because the spread happens after the explicit source. That breaks the method contract and can leak a manual push into the wrong channel.

🛠️ Suggested fix
   const meta: Record<string, string> = {
-    source: channelName,
     ...(additionalMeta ?? {}),
+    source: channelName,
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/sdk/src/channel/channel-notification.service.ts` around lines 119 - 123,
The send method currently lets additionalMeta.source override the intended
channel because source is set before spreading additionalMeta; update the merge
so the explicit channelName wins (e.g., merge additionalMeta first and then set
source to channelName, or remove any source key from additionalMeta before
merging) in the send function of channel-notification.service.ts so
send(channelName, ...) cannot be hijacked by additionalMeta.source.
libs/sdk/src/channel/channel.instance.ts-91-102 (1)

91-102: ⚠️ Potential issue | 🟠 Major

Only publish the service context after onConnect() succeeds.

_serviceContext is assigned before onConnect() runs. If onConnect() throws, the instance still looks connected and teardown/follow-up logic will act on a half-initialized connector.

Suggested change
-    this._serviceContext = ctx;
-
     try {
       await ctx.onConnect();
+      this._serviceContext = ctx;
       const sourceLabel =
         this.metadata.source.type === 'service'
           ? (this.metadata.source as { service: string }).service
           : this.metadata.source.type;
       logger.info(`Channel "${this.name}" connected (${sourceLabel})`);
     } catch (err) {
+      this._serviceContext = undefined;
       logger.error(`Channel "${this.name}" service failed to connect`, { error: err });
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/sdk/src/channel/channel.instance.ts` around lines 91 - 102, The instance
sets this._serviceContext before awaiting ctx.onConnect(), so a thrown error
leaves the channel appearing connected; move the assignment to after await
ctx.onConnect() (or assign to a temp variable and set this._serviceContext only
on success) so that onConnect() must complete without throwing before publishing
the service context; ensure any existing catch block does not leave
this._serviceContext set when ctx.onConnect() fails and keep the existing
logging (use this.name and metadata for context).
libs/sdk/src/channel/channel.instance.ts-181-187 (1)

181-187: ⚠️ Potential issue | 🟠 Major

Keep meta.source authoritative.

The current merge order lets staticMeta or per-event meta override source, so a channel can mislabel its own notifications. Anything downstream that keys off meta.source will then see the wrong channel.

Suggested change
     const mergedMeta: Record<string, string> = {
-      source: this.name,
       ...(this.staticMeta ?? {}),
       ...(meta ?? {}),
+      source: this.name,
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/sdk/src/channel/channel.instance.ts` around lines 181 - 187, The merge
currently allows staticMeta or the per-notification meta to override the
channel's source; in pushNotification, ensure the channel's source (this.name)
is authoritative by applying it after merging staticMeta and meta (or explicitly
setting mergedMeta.source = this.name) so mergedMeta.source cannot be overridden
by this.staticMeta or the meta parameter.
🟡 Minor comments (8)
libs/skills/catalog/frontmcp-channels/examples/channel-sources/file-watcher.md-61-66 (1)

61-66: ⚠️ Potential issue | 🟡 Minor

Clear lastSize on disconnect to prevent stale replay offsets.

onDisconnect() closes watchers but keeps lastSize. On reconnect, this can skip appended content until file length exceeds prior session length.

💡 Suggested fix
   async onDisconnect(): Promise<void> {
     for (const watcher of this.watchers) {
       watcher.close();
     }
     this.watchers = [];
+    this.lastSize.clear();
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@libs/skills/catalog/frontmcp-channels/examples/channel-sources/file-watcher.md`
around lines 61 - 66, onDisconnect currently closes each watcher (this.watchers)
but does not clear the per-file replay offset (lastSize), which can cause
skipped appended data on reconnect; update onDisconnect to both close watchers
and reset the associated lastSize state (e.g., clear this.lastSize map or set
each watcher.lastSize = 0) so that onReconnect starts tailing from the file end
correctly; ensure you reference and clear the same lastSize storage used by the
file watcher logic in this class (the lastSize field) after closing watchers and
before emptying this.watchers.
apps/e2e/demo-e2e-channels/src/main.ts-4-5 (1)

4-5: ⚠️ Potential issue | 🟡 Minor

Add upper bound validation for the PORT environment variable.

parseInt() accepts strings with junk suffixes ("3120abc" → 3120), and the current guard only checks Number.isFinite(rawPort) && rawPort > 0, which allows values above 65535 through. These invalid ports fail later at HTTP server bind time instead of being caught early.

Suggested fix
-const rawPort = parseInt(process.env['PORT'] ?? '3120', 10);
-const port = Number.isFinite(rawPort) && rawPort > 0 ? rawPort : 3120;
+const rawPort = Number(process.env['PORT'] ?? 3120);
+const port =
+  Number.isInteger(rawPort) && rawPort > 0 && rawPort <= 65_535 ? rawPort : 3120;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/e2e/demo-e2e-channels/src/main.ts` around lines 4 - 5, The current PORT
parsing using parseInt(raw) allows junk suffixes and doesn't enforce the valid
TCP port range; update the logic that computes rawPort/port (the
parseInt(process.env['PORT'] ?? '3120') usage) to first validate the raw env
string contains only digits (e.g., /^\d+$/) before parsing, then parse to an
integer and ensure Number.isInteger(rawPort) and rawPort >= 1 && rawPort <=
65535; if validation fails, fall back to the default 3120 or otherwise handle
the error so invalid or out‑of‑range ports are rejected early.
libs/sdk/src/transport/adapters/transport.local.adapter.ts-153-164 (1)

153-164: ⚠️ Potential issue | 🟡 Minor

Remove the as unknown as cast and access this.scope.channels directly.

Scope already exposes a properly typed channels getter returning ChannelRegistry | undefined. The structural cast disables compile-time checking for no reason; simply use this.scope.channels?.hasAny() and this.scope.channels?.getCapabilities() instead.

As per coding guidelines **/*.{ts,tsx}: Use strict type checking in TypeScript; avoid any types without strong justification.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/sdk/src/transport/adapters/transport.local.adapter.ts` around lines 153
- 164, Remove the structural cast and access the typed channels API directly:
replace the temporary "scope" variable that uses "as unknown as { channels?: ...
}" with direct references to this.scope.channels (the ChannelRegistry getter),
e.g. compute channelCapabilities from this.scope.channels?.getCapabilities() and
compute channelInstructions using this.scope.channels?.hasAny() and
this.scope.channels?.getChannelInstances(); ensure you remove the cast so
TypeScript uses the real ChannelRegistry types and adjust null/undefined guards
(?.) where needed to preserve the existing behavior for channelCapabilities and
channelInstructions.
apps/e2e/demo-e2e-channels/src/apps/channels/channels/replay-alert.channel.ts-14-19 (1)

14-19: ⚠️ Potential issue | 🟡 Minor

Validate the replay event payload before formatting a notification.

The cast on line 15 means bad app-event data turns into Event #undefined: undefined instead of failing fast. Parse the payload with Zod before reading index and message fields.

Suggested fix
 import { Channel, ChannelContext } from '@frontmcp/sdk';
 import type { ChannelNotification } from '@frontmcp/sdk';
+import { z } from 'zod';
+
+const replayAlertPayloadSchema = z.object({
+  index: z.number(),
+  message: z.string(),
+});

 `@Channel`({
   name: 'replay-alerts',
@@
 export class ReplayAlertChannel extends ChannelContext {
   async onEvent(payload: unknown): Promise<ChannelNotification> {
-    const data = payload as { index: number; message: string };
+    const data = replayAlertPayloadSchema.parse(payload);
     return {
       content: `Event #${data.index}: ${data.message}`,
       meta: { index: String(data.index) },
     };
   }
 }

Per coding guidelines **/*.{ts,tsx}: Follow existing TypeScript patterns and keep strict typing. Avoid unsafe type assertions without validation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/e2e/demo-e2e-channels/src/apps/channels/channels/replay-alert.channel.ts`
around lines 14 - 19, onEvent currently unsafely casts payload to {index:number;
message:string} and formats Event `#undefined` when input is wrong; validate the
payload with a Zod schema before reading fields. In the onEvent method create a
Zod object schema for { index: z.number(), message: z.string() }, parse or
safeParse the incoming payload, throw or return an appropriate
error/notification on failure, and only format and return the
ChannelNotification after successful validation (refer to onEvent and
ChannelNotification to locate the code).
apps/e2e/demo-e2e-channels/src/apps/channels/tools/session-tools.tool.ts-15-15 (1)

15-15: ⚠️ Potential issue | 🟡 Minor

meta is modeled as required even though channel notifications allow it to be omitted.

This helper stores msg.params.meta behind a non-optional Record<string, string>. Notifications without metadata will be recorded as undefined with the wrong type, which makes downstream assertions on notification.meta.foo unsafe. Either make the field optional in sessionNotifications or normalize missing metadata to {}.

As per coding guidelines, Implement onEvent(payload) to transform source payloads into { content, meta? }.

Also applies to: 32-35

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/e2e/demo-e2e-channels/src/apps/channels/tools/session-tools.tool.ts` at
line 15, sessionNotifications currently types meta as a required
Record<string,string> which breaks when channel notifications omit meta; change
the stored shape to allow meta?: Record<string,string> or normalize missing meta
to an empty object when recording. Update the Map declaration for
sessionNotifications to Map<string, Array<{ content: string; meta?:
Record<string,string> }>> (or keep required but always set meta = {}), and
implement onEvent(payload) to transform incoming payloads into { content, meta?
} — i.e., extract payload.params.content and set payload.params.meta ?? {} (or
omit meta) before pushing into sessionNotifications; apply the same change to
the other occurrences referenced around the block (lines ~32-35).
libs/skills/catalog/frontmcp-channels/references/channel-sources.md-2-3 (1)

2-3: ⚠️ Potential issue | 🟡 Minor

The page metadata still says “five” source types.

Both the frontmatter description and the intro say five, but this page now documents seven: webhook, app-event, agent-completion, job-completion, service, file-watcher, and manual.

Also applies to: 8-8

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/skills/catalog/frontmcp-channels/references/channel-sources.md` around
lines 2 - 3, The frontmatter description key and the intro paragraph still say
"five" source types but the page documents seven; update the frontmatter
'description' value and the intro sentence that mentions "five" to say "seven"
and optionally enumerate the seven types (webhook, app-event, agent-completion,
job-completion, service, file-watcher, manual) so the text matches the
documented list; make the same update wherever the phrase "five source types"
appears (including the duplicate in 8-8).
libs/sdk/src/channel/sources/webhook.source.ts-55-56 (1)

55-56: ⚠️ Potential issue | 🟡 Minor

Query parameters may contain arrays, not just strings.

Express and Fastify parse repeated query parameters (e.g., ?id=1&id=2) as arrays. The cast to Record<string, string> may lose data or cause downstream issues if array values are expected.

Consider adjusting the type to handle both cases:

-  query: req.query as Record<string, string> | undefined,
+  query: req.query as Record<string, string | string[]> | undefined,

And update the WebhookPayload interface accordingly at line 18.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/sdk/src/channel/sources/webhook.source.ts` around lines 55 - 56, The
current cast "query: req.query as Record<string, string> | undefined" loses
array query params; change the query typing to accept string or string[] (e.g.,
Record<string, string | string[]> | undefined) and update the WebhookPayload
interface (the type defined around "WebhookPayload") so its query property uses
string | string[] for values; ensure any code consuming payload.query handles
both single values and arrays (e.g., normalize to array or pick first element
where appropriate).
apps/e2e/demo-e2e-channels/src/apps/channels/channels/messaging-service.channel.ts-105-113 (1)

105-113: ⚠️ Potential issue | 🟡 Minor

Add validation schema for the incoming event payload.

onEvent() receives an unknown payload but uses an unsafe type assertion on line 106. This bypasses validation and could produce malformed notification content/metadata if the payload shape is unexpected. Since zod is already imported in this file, use a schema with .parse() to validate the payload at the channel boundary:

Suggested fix
+const incomingMessageSchema = z.object({
+  from: z.string(),
+  text: z.string(),
+  chatId: z.string(),
+});
+
 export class MessagingServiceChannel extends ChannelContext {
@@
   async onEvent(payload: unknown): Promise<ChannelNotification> {
-    const msg = payload as { from: string; text: string; chatId: string };
+    const msg = incomingMessageSchema.parse(payload);
     return {
       content: `${msg.from}: ${msg.text}`,
       meta: {
         chat_id: msg.chatId,
         sender: msg.from,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/e2e/demo-e2e-channels/src/apps/channels/channels/messaging-service.channel.ts`
around lines 105 - 113, onEvent currently unsafely asserts payload to { from,
text, chatId }; add a zod schema (e.g., const EventSchema = z.object({ from:
z.string(), text: z.string(), chatId: z.string() })) and use
EventSchema.parse(payload) inside onEvent to validate and obtain a typed msg
before building the ChannelNotification; if parse throws, either let the
exception bubble or catch and return/throw a clear error so malformed payloads
are rejected at the channel boundary. Ensure you reference the onEvent method
and the returned ChannelNotification when implementing the schema-based parse.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fc0acd66-0db5-46d1-b44b-ee969376b092

📥 Commits

Reviewing files that changed from the base of the PR and between 4241132 and 62cd60b.

📒 Files selected for processing (90)
  • apps/e2e/demo-e2e-channels/e2e/channels-errors.e2e.spec.ts
  • apps/e2e/demo-e2e-channels/e2e/channels-file-watcher.e2e.spec.ts
  • apps/e2e/demo-e2e-channels/e2e/channels-replay.e2e.spec.ts
  • apps/e2e/demo-e2e-channels/e2e/channels-security.e2e.spec.ts
  • apps/e2e/demo-e2e-channels/e2e/channels.e2e.spec.ts
  • apps/e2e/demo-e2e-channels/jest.e2e.config.ts
  • apps/e2e/demo-e2e-channels/project.json
  • apps/e2e/demo-e2e-channels/src/apps/channels/channels/chat-bridge.channel.ts
  • apps/e2e/demo-e2e-channels/src/apps/channels/channels/deploy-alert.channel.ts
  • apps/e2e/demo-e2e-channels/src/apps/channels/channels/error-alert.channel.ts
  • apps/e2e/demo-e2e-channels/src/apps/channels/channels/file-watcher.channel.ts
  • apps/e2e/demo-e2e-channels/src/apps/channels/channels/manual-status.channel.ts
  • apps/e2e/demo-e2e-channels/src/apps/channels/channels/messaging-service.channel.ts
  • apps/e2e/demo-e2e-channels/src/apps/channels/channels/replay-alert.channel.ts
  • apps/e2e/demo-e2e-channels/src/apps/channels/index.ts
  • apps/e2e/demo-e2e-channels/src/apps/channels/tools/emit-app-event.tool.ts
  • apps/e2e/demo-e2e-channels/src/apps/channels/tools/file-watcher-tools.tool.ts
  • apps/e2e/demo-e2e-channels/src/apps/channels/tools/list-reply-log.tool.ts
  • apps/e2e/demo-e2e-channels/src/apps/channels/tools/replay-tools.tool.ts
  • apps/e2e/demo-e2e-channels/src/apps/channels/tools/send-channel-notification.tool.ts
  • apps/e2e/demo-e2e-channels/src/apps/channels/tools/session-tools.tool.ts
  • apps/e2e/demo-e2e-channels/src/apps/channels/tools/simulate-incoming.tool.ts
  • apps/e2e/demo-e2e-channels/src/config.ts
  • apps/e2e/demo-e2e-channels/src/main.ts
  • apps/e2e/demo-e2e-channels/tsconfig.app.json
  • apps/e2e/demo-e2e-channels/tsconfig.json
  • apps/e2e/demo-e2e-channels/webpack.config.js
  • docs/docs.json
  • docs/frontmcp/features/channels.mdx
  • docs/frontmcp/features/decorator-driven-development.mdx
  • docs/frontmcp/guides/your-first-channel.mdx
  • docs/frontmcp/sdk-reference/contexts/channel-context.mdx
  • docs/frontmcp/sdk-reference/decorators/channel.mdx
  • docs/frontmcp/sdk-reference/registries/channel-registry.mdx
  • docs/frontmcp/servers/channels.mdx
  • libs/sdk/src/channel/README.md
  • libs/sdk/src/channel/channel-notification.service.ts
  • libs/sdk/src/channel/channel-scope.helper.ts
  • libs/sdk/src/channel/channel.events.ts
  • libs/sdk/src/channel/channel.instance.ts
  • libs/sdk/src/channel/channel.registry.ts
  • libs/sdk/src/channel/channel.types.ts
  • libs/sdk/src/channel/channel.utils.ts
  • libs/sdk/src/channel/flows/list-channels.flow.ts
  • libs/sdk/src/channel/flows/send-channel-notification.flow.ts
  • libs/sdk/src/channel/index.ts
  • libs/sdk/src/channel/reply/channel-reply.tool.ts
  • libs/sdk/src/channel/reply/reply.types.ts
  • libs/sdk/src/channel/sources/agent-completion.source.ts
  • libs/sdk/src/channel/sources/app-event.source.ts
  • libs/sdk/src/channel/sources/index.ts
  • libs/sdk/src/channel/sources/job-completion.source.ts
  • libs/sdk/src/channel/sources/webhook.source.ts
  • libs/sdk/src/common/decorators/channel.decorator.ts
  • libs/sdk/src/common/decorators/index.ts
  • libs/sdk/src/common/entries/channel.entry.ts
  • libs/sdk/src/common/entries/index.ts
  • libs/sdk/src/common/interfaces/channel.interface.ts
  • libs/sdk/src/common/interfaces/index.ts
  • libs/sdk/src/common/interfaces/internal/registry.interface.ts
  • libs/sdk/src/common/metadata/app.metadata.ts
  • libs/sdk/src/common/metadata/channel.metadata.ts
  • libs/sdk/src/common/metadata/front-mcp.metadata.ts
  • libs/sdk/src/common/metadata/index.ts
  • libs/sdk/src/common/records/channel.record.ts
  • libs/sdk/src/common/records/index.ts
  • libs/sdk/src/common/schemas/annotated-class.schema.ts
  • libs/sdk/src/common/tokens/app.tokens.ts
  • libs/sdk/src/common/tokens/channel.tokens.ts
  • libs/sdk/src/common/tokens/front-mcp.tokens.ts
  • libs/sdk/src/common/tokens/index.ts
  • libs/sdk/src/front-mcp/front-mcp.ts
  • libs/sdk/src/index.ts
  • libs/sdk/src/notification/index.ts
  • libs/sdk/src/notification/notification.service.ts
  • libs/sdk/src/scope/scope.instance.ts
  • libs/sdk/src/transport/adapters/transport.local.adapter.ts
  • libs/sdk/src/transport/mcp-handlers/initialize-request.handler.ts
  • libs/skills/catalog/frontmcp-channels/SKILL.md
  • libs/skills/catalog/frontmcp-channels/examples/channel-sources/agent-notify.md
  • libs/skills/catalog/frontmcp-channels/examples/channel-sources/app-errors.md
  • libs/skills/catalog/frontmcp-channels/examples/channel-sources/file-watcher.md
  • libs/skills/catalog/frontmcp-channels/examples/channel-sources/job-completion.md
  • libs/skills/catalog/frontmcp-channels/examples/channel-sources/replay-buffer.md
  • libs/skills/catalog/frontmcp-channels/examples/channel-sources/service-connector.md
  • libs/skills/catalog/frontmcp-channels/examples/channel-sources/webhook-github.md
  • libs/skills/catalog/frontmcp-channels/examples/channel-two-way/whatsapp-bridge.md
  • libs/skills/catalog/frontmcp-channels/references/channel-sources.md
  • libs/skills/catalog/frontmcp-channels/references/channel-two-way.md
  • libs/skills/catalog/skills-manifest.json

Comment thread libs/sdk/src/channel/channel.instance.ts
Comment thread libs/sdk/src/channel/sources/agent-completion.source.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (2)
libs/sdk/src/channel/channel.instance.ts (2)

28-38: Consider removing underscore prefix from private fields.

The coding guidelines recommend using private fields without underscore prefix and exposing via getters. This file uses _providers, _channelNotificationService, _serviceContext, _replayBuffer, and _maxReplayEvents with underscores.

This is a minor style inconsistency that could be addressed for guideline compliance if the team prefers. As per coding guidelines: "Use private fields without underscore prefix and expose via getters following idiomatic TypeScript patterns."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/sdk/src/channel/channel.instance.ts` around lines 28 - 38, The private
field names in ChannelInstance (e.g., _providers, _channelNotificationService,
_serviceContext, _replayBuffer, _maxReplayEvents) use an underscore prefix which
conflicts with the style guide; rename these private fields to providers,
channelNotificationService, serviceContext, replayBuffer, and maxReplayEvents
respectively, update all assignments (constructor, initializers) and internal
references within the class to use the new names, and keep or add public getters
with the intended exposed names if needed so external API remains unchanged;
ensure imports/exports and any tests referencing the old private names are
updated accordingly.

200-202: Consider logging a warning when notification service is not set.

Per the channel context documentation, calling pushIncoming() early should warn. While the initialization order guarantees the service is wired before connectService(), a warning here would help diagnose misconfigurations or unexpected call sequences.

📝 Proposed fix to add warning
     if (!this._channelNotificationService) {
+      const logger = this._providers.get(FrontMcpLogger);
+      logger.warn(`Channel "${this.name}": notification service not set, notification dropped`);
       return;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/sdk/src/channel/channel.instance.ts` around lines 200 - 202, When
pushIncoming finds this._channelNotificationService unset (the current check if
(!this._channelNotificationService) { return; }) add a warning log before
returning; use the channel instance's logger (e.g., this.logger or this._logger)
if available, otherwise fallback to console.warn, and include identifying
context (channel id/name or other instance properties) and a clear message such
as "pushIncoming called before channelNotificationService initialized" to aid
debugging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/e2e/demo-e2e-channels/src/apps/channels/tools/session-tools.tool.ts`:
- Around line 16-24: The FakeServer only implements notification() but is later
cast to McpServer; add a request() stub to the fake so it safely implements the
expected server API: update the FakeServer interface to include request(method:
string, params?: unknown): Promise<unknown> (or void) and implement that stub in
createFakeServer to return a resolved Promise or appropriate default;
alternatively replace the cast with a properly typed test double that implements
both notification() and request(), so NotificationService.getRoots() calls to
server.request() won't break at runtime.

In `@libs/sdk/src/channel/channel-notification.service.ts`:
- Around line 87-111: sendToSession currently assumes
NotificationService.sendCustomNotification always succeeds, so it can return
true even when per-session transport writes fail; update the API and call path
so sendCustomNotification (or a new sendCustomNotificationWithResult) returns a
boolean/Result or throws on send failures, then have sendToSession call that and
return false when a send error occurs (or rethrow). Specifically, modify
NotificationService.sendCustomNotification and its internal per-session send
logic to surface per-session errors (instead of swallowing them) and change
channel-notification.service.ts sendToSession to await/inspect that result (or
catch the thrown error) before returning true; keep prechecks
(getRegisteredServer, supportsChannels, isChannelSubscribed) as-is but ensure
send failures cause sendToSession to report failure.

In `@libs/sdk/src/channel/channel-scope.helper.ts`:
- Around line 194-200: The teardown function currently aborts on the first
failing disconnectService() call; change it to be best-effort by handling errors
per-channel or using Promise.allSettled on serviceChannels.map(ci =>
ci.disconnectService()), collect/log any errors but do not throw, then always
run unsubscribers (for(const unsub of unsubscribers) unsub()) and
channelEventBus.clear() in a finally/after path so teardown completes even if
some disconnects fail; reference the teardown function, serviceChannels,
disconnectService(), unsubscribers, and channelEventBus.clear() when making the
change.
- Around line 177-180: The loop that calls instance.connectService() on
serviceChannels must rollback already-connected instances if a later connect
fails: track successful instances (e.g., connectedInstances array) as you await
instance.connectService(); if a connectService() throws, iterate over
connectedInstances and call/await instance.disconnectService() (handling/logging
any errors) before rethrowing the original error, and ensure the helper’s
teardown callback remains consistent with this rollback behavior so no
subscriptions/event bus remain open on initialization failure.

In `@libs/sdk/src/channel/channel.instance.ts`:
- Around line 140-151: The switch over ChannelKind in the method that returns a
ChannelContext (handling ChannelKind.FUNCTION and ChannelKind.CLASS_TOKEN) is
non-exhaustive and can return undefined; update the switch to handle unknown/new
enum values by adding a default branch that throws a clear error (or an
exhaustive-check/assert) so the method always returns a ChannelContext or fails
fast; reference the switch on ChannelKind, the ChannelFunctionTokenRecord ->
provide() path that constructs a FunctionChannelContext, and the class-based
path using this.record.provide to guide where to add the default/exhaustive
check.

In `@libs/sdk/src/common/interfaces/channel.interface.ts`:
- Around line 117-121: Replace the generic Error thrown in the default
onReply(reply, meta?) implementation with one of the SDK's specific
protocol-aware error classes (so the channel-reply tool receives an MCP/JSON-RPC
compliant error). Locate the onReply method (referencing this.channelName) and
throw the appropriate SDK error type that includes an MCP error code and
implements toJsonRpcError(); ensure the thrown error message retains the
existing context about twoWay: true and advises overriding onReply(), and
confirm the error class' toJsonRpcError() produces the expected JSON-RPC shape.

In `@libs/sdk/src/scope/scope.instance.ts`:
- Around line 112-116: The stored _channelTeardown currently has type () => void
and is invoked fire-and-forget in Scope.shutdown(), but the helper returns a
Promise-based teardown (() => Promise<void>) so shutdown may return before
teardown completes and rejections are unhandled; change _channelTeardown's type
to () => Promise<void>, assign the helper's teardown directly, and await/return
_channelTeardown() inside Scope.shutdown() (handle errors with try/catch or
propagate the rejection) so connectors disconnect and subscriptions are released
before shutdown resolves; update any related uses of _channelTeardown and the
fields _scopeChannels, _channelNotificationService, _channelEventBus to preserve
async teardown contract.
- Around line 577-586: registerChannelCapabilities is being called without the
agent/job emitters so channel types like "agent-completion" and "job-completion"
will never emit notifications; update the call site to pass the emitter
callbacks (the subscribe/emit handlers used elsewhere) into
registerChannelCapabilities or, if those emitters are not available, throw/fail
fast when those source types are present. Specifically, modify the call in
scope.instance.ts where registerChannelCapabilities({ providers:
this.scopeProviders, owner: scopeRef, channelsList: appChannels, channelsConfig,
notificationService: this.notificationService, flowRegistry: this.scopeFlows,
toolRegistry: this.scopeTools, logger: this.logger }) is invoked to include the
agent/job emitter subscriptions (or a reference to the agent/job emitter
functions) so that "agent-completion" and "job-completion" channels are wired,
or add a guard that checks appChannels for those source types and errors if
emitters are not provided.

---

Nitpick comments:
In `@libs/sdk/src/channel/channel.instance.ts`:
- Around line 28-38: The private field names in ChannelInstance (e.g.,
_providers, _channelNotificationService, _serviceContext, _replayBuffer,
_maxReplayEvents) use an underscore prefix which conflicts with the style guide;
rename these private fields to providers, channelNotificationService,
serviceContext, replayBuffer, and maxReplayEvents respectively, update all
assignments (constructor, initializers) and internal references within the class
to use the new names, and keep or add public getters with the intended exposed
names if needed so external API remains unchanged; ensure imports/exports and
any tests referencing the old private names are updated accordingly.
- Around line 200-202: When pushIncoming finds this._channelNotificationService
unset (the current check if (!this._channelNotificationService) { return; }) add
a warning log before returning; use the channel instance's logger (e.g.,
this.logger or this._logger) if available, otherwise fallback to console.warn,
and include identifying context (channel id/name or other instance properties)
and a clear message such as "pushIncoming called before
channelNotificationService initialized" to aid debugging.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8974f865-2066-42f9-ba28-3dcddf04fc9b

📥 Commits

Reviewing files that changed from the base of the PR and between 7af9aea and b7a3a5f.

📒 Files selected for processing (19)
  • apps/e2e/demo-e2e-channels/src/apps/channels/channels/messaging-service.channel.ts
  • apps/e2e/demo-e2e-channels/src/apps/channels/channels/replay-alert.channel.ts
  • apps/e2e/demo-e2e-channels/src/apps/channels/tools/list-reply-log.tool.ts
  • apps/e2e/demo-e2e-channels/src/apps/channels/tools/session-tools.tool.ts
  • apps/e2e/demo-e2e-channels/src/main.ts
  • libs/sdk/src/channel/channel-notification.service.ts
  • libs/sdk/src/channel/channel-scope.helper.ts
  • libs/sdk/src/channel/channel.instance.ts
  • libs/sdk/src/channel/channel.registry.ts
  • libs/sdk/src/channel/reply/channel-reply.tool.ts
  • libs/sdk/src/channel/sources/agent-completion.source.ts
  • libs/sdk/src/channel/sources/app-event.source.ts
  • libs/sdk/src/common/decorators/channel.decorator.ts
  • libs/sdk/src/common/interfaces/channel.interface.ts
  • libs/sdk/src/front-mcp/front-mcp.ts
  • libs/sdk/src/scope/scope.instance.ts
  • libs/sdk/src/transport/adapters/transport.local.adapter.ts
  • libs/skills/catalog/frontmcp-channels/examples/channel-sources/file-watcher.md
  • libs/skills/catalog/frontmcp-channels/references/channel-sources.md
✅ Files skipped from review due to trivial changes (2)
  • apps/e2e/demo-e2e-channels/src/apps/channels/tools/list-reply-log.tool.ts
  • libs/skills/catalog/frontmcp-channels/references/channel-sources.md
🚧 Files skipped from review as they are similar to previous changes (8)
  • libs/sdk/src/front-mcp/front-mcp.ts
  • apps/e2e/demo-e2e-channels/src/main.ts
  • apps/e2e/demo-e2e-channels/src/apps/channels/channels/replay-alert.channel.ts
  • libs/sdk/src/channel/sources/agent-completion.source.ts
  • libs/sdk/src/channel/sources/app-event.source.ts
  • libs/sdk/src/channel/reply/channel-reply.tool.ts
  • libs/skills/catalog/frontmcp-channels/examples/channel-sources/file-watcher.md
  • apps/e2e/demo-e2e-channels/src/apps/channels/channels/messaging-service.channel.ts

Comment thread libs/sdk/src/channel/channel-notification.service.ts
Comment thread libs/sdk/src/channel/channel-scope.helper.ts Outdated
Comment thread libs/sdk/src/channel/channel-scope.helper.ts
Comment thread libs/sdk/src/channel/channel.instance.ts
Comment thread libs/sdk/src/common/interfaces/channel.interface.ts
Comment thread libs/sdk/src/scope/scope.instance.ts Outdated
Comment thread libs/sdk/src/scope/scope.instance.ts
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 6, 2026

Performance Test Results

Status: ✅ All tests passed

Summary

Project Tests Passed Warnings Failed Leaks
✅ demo-e2e-agents 4 4 0 0 0
✅ demo-e2e-cache 11 11 0 0 0
✅ demo-e2e-codecall 4 4 0 0 0
✅ demo-e2e-config 4 4 0 0 0
✅ demo-e2e-direct 3 3 0 0 0
✅ demo-e2e-elicitation 1 1 0 0 0
✅ demo-e2e-errors 4 4 0 0 0
✅ demo-e2e-hooks 3 3 0 0 0
✅ demo-e2e-multiapp 4 4 0 0 0
✅ demo-e2e-notifications 3 3 0 0 0
✅ demo-e2e-openapi 2 2 0 0 0
✅ demo-e2e-providers 4 4 0 0 0
✅ demo-e2e-public 4 4 0 0 0
✅ demo-e2e-redis 15 15 0 0 0
✅ demo-e2e-remember 4 4 0 0 0
✅ demo-e2e-remote 5 5 0 0 0
✅ demo-e2e-serverless 2 2 0 0 0
✅ demo-e2e-skills 15 15 0 0 0
✅ demo-e2e-standalone 2 2 0 0 0
✅ demo-e2e-transport-recreation 3 3 0 0 0
✅ demo-e2e-ui 4 4 0 0 0

Total: 101 tests across 21 projects

📊 View full report in workflow run


Generated at: 2026-04-06T22:29:26.334Z
Commit: 190519bc

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
apps/e2e/demo-e2e-channels/src/apps/channels/tools/session-tools.tool.ts (1)

11-18: Replace unknown with concrete McpServerLike interface and remove cast

The structural interface at line 12 and the cast at lines 78-81 use unknown to bypass strict typing, which obscures the actual contract and allows test doubles to drift from the expected shape. Instead, define a concrete McpServerLike interface matching what the code needs and use it directly.

Suggested refactor
+interface McpServerLike {
+  notification(msg: { method: string; params: Record<string, unknown> }): void;
+}
+
 interface NotificationServiceLike {
-  registerServer(sessionId: string, server: unknown): void;
+  registerServer(sessionId: string, server: McpServerLike): void;
   unregisterServer(sessionId: string): void;
   setClientCapabilities(sessionId: string, capabilities: Record<string, unknown>): void;
   subscribeChannel(sessionId: string, channelName: string): boolean;
   unsubscribeChannel(sessionId: string, channelName: string): boolean;
   subscribeAllChannels(sessionId: string, channelNames: string[]): void;
 }
@@
-interface FakeServer {
-  notification(msg: { method: string; params: Record<string, unknown> }): void;
-}
+type FakeServer = McpServerLike;
@@
-    notifications.registerServer(
-      input.sessionId,
-      fakeServer as unknown as Parameters<typeof notifications.registerServer>[1],
-    );
+    notifications.registerServer(input.sessionId, fakeServer);

Per coding guidelines, "Follow existing TypeScript patterns and keep strict typing" and "maintain strict TypeScript correctness."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/e2e/demo-e2e-channels/src/apps/channels/tools/session-tools.tool.ts`
around lines 11 - 18, The NotificationServiceLike interface currently uses
unknown for the server parameter and tests cast to bypass typing; define a
concrete McpServerLike interface that declares the exact members the code uses
(e.g., methods/properties consumed by registerServer/unregisterServer flows) and
replace the unknown types in NotificationServiceLike.registerServer and any
other places using unknown with McpServerLike; remove the runtime casts (the
test double casts around registerServer usage) so callers and tests use
McpServerLike directly, ensuring all references (NotificationServiceLike,
registerServer, any test helpers) are updated to the new type.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/e2e/demo-e2e-channels/src/apps/channels/tools/session-tools.tool.ts`:
- Around line 203-207: Replace the generic Error and the non-null assertion:
when channel is missing, call this.fail with a specific MCP-style error instance
(e.g., a NotFound/ChannelNotFound error class that implements toJsonRpcError())
constructed with the channel name, instead of new Error(...); remove the
channel! non-null assertion and call channel.pushNotification only after the
channel existence guard (the this.fail branch never returns), using the existing
symbols channel, this.fail, pushNotification, and
input.channelName/input.content/input.targetSessionId to locate and update the
code.

In `@libs/sdk/package.json`:
- Line 83: The SDK currently statically imports vectoriadb in
memory-skill.provider.ts and instantiates TFIDFVectoria in the
MemorySkillProvider constructor, causing module load failures when vectoriadb is
not installed; either revert vectoriadb to a regular dependency in
libs/sdk/package.json or change MemorySkillProvider to lazy-load the module (use
dynamic import like const { TFIDFVectoria } = await import('vectoriadb') inside
the constructor/initialization path) so the export of MemorySkillProvider from
the SDK entry point no longer triggers a hard require at module load time;
alternatively move skill providers to a separate optional export path if you
want to keep it optional.

---

Nitpick comments:
In `@apps/e2e/demo-e2e-channels/src/apps/channels/tools/session-tools.tool.ts`:
- Around line 11-18: The NotificationServiceLike interface currently uses
unknown for the server parameter and tests cast to bypass typing; define a
concrete McpServerLike interface that declares the exact members the code uses
(e.g., methods/properties consumed by registerServer/unregisterServer flows) and
replace the unknown types in NotificationServiceLike.registerServer and any
other places using unknown with McpServerLike; remove the runtime casts (the
test double casts around registerServer usage) so callers and tests use
McpServerLike directly, ensuring all references (NotificationServiceLike,
registerServer, any test helpers) are updated to the new type.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 756254be-e5c9-4fca-a3e1-c8cd4089549a

📥 Commits

Reviewing files that changed from the base of the PR and between b7a3a5f and 7c6216e.

📒 Files selected for processing (5)
  • apps/e2e/demo-e2e-channels/src/apps/channels/tools/list-reply-log.tool.ts
  • apps/e2e/demo-e2e-channels/src/apps/channels/tools/session-tools.tool.ts
  • apps/e2e/demo-e2e-channels/src/apps/channels/tools/simulate-incoming.tool.ts
  • libs/sdk/package.json
  • plugins/plugin-cache/package.json
✅ Files skipped from review due to trivial changes (1)
  • plugins/plugin-cache/package.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/e2e/demo-e2e-channels/src/apps/channels/tools/list-reply-log.tool.ts
  • apps/e2e/demo-e2e-channels/src/apps/channels/tools/simulate-incoming.tool.ts

Comment thread apps/e2e/demo-e2e-channels/src/apps/channels/tools/session-tools.tool.ts Outdated
Comment thread libs/sdk/package.json
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
libs/sdk/src/channel/channel-scope.helper.ts (2)

89-102: Avoid as any casts; use proper type narrowing or generics.

Lines 94-95 use as any to bypass type checking for instance.metadata.source and agentEmitterSubscribe. This pattern is repeated for job-completion (lines 108-109). Consider defining discriminated union types for channel sources or using type guards to narrow the source type safely.

♻️ Example type-safe approach
// Define source type with discriminated union
type AgentCompletionSource = { type: 'agent-completion'; agentName?: string };

// Use type guard or assert
if (sourceType === 'agent-completion') {
  const agentSource = instance.metadata.source as AgentCompletionSource;
  const unsub = wireAgentCompletionSource(
    instance,
    agentSource,
    agentEmitterSubscribe,
    logger,
  );
  unsubscribers.push(unsub);
}

As per coding guidelines, "Use strict TypeScript with no any types without strong justification."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/sdk/src/channel/channel-scope.helper.ts` around lines 89 - 102, The code
uses `as any` for `instance.metadata.source` and `agentEmitterSubscribe` when
calling `wireAgentCompletionSource` (and similarly for the `job-completion`
branch), so add proper types and narrow the union instead of casting: define
discriminated union types for channel sources (e.g., an `AgentCompletionSource`
with `type: 'agent-completion'`) or implement a type guard that checks
`sourceType === 'agent-completion'` and then cast `instance.metadata.source` to
the specific source type, and ensure `agentEmitterSubscribe` is the correct
emitter type before passing to `wireAgentCompletionSource`; do the analogous
change for the `job-completion` branch (e.g., `wireJobCompletionSource`) so both
calls are fully typed without `any`.

154-170: Avoid as any for owner.ref; define a proper owner reference type.

Line 160 uses ref: toolDef as any when constructing the EntryOwnerRef. If the owner reference can accept various tool definition types, consider widening the ref field's type in EntryOwnerRef or using a type assertion to a more specific type.

♻️ Suggested improvement
           const toolInstance = new ToolInstance(toolRecord, providers, {
             kind: 'scope',
             id: `_channel:${instance.name}`,
-            ref: toolDef as any,
+            ref: toolDef,
           });

If EntryOwnerRef.ref doesn't accept ToolType, consider updating the type definition to ref: unknown or a union type that includes tool definitions.

As per coding guidelines, "Use strict TypeScript with no any types without strong justification."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/sdk/src/channel/channel-scope.helper.ts` around lines 154 - 170, The
code currently uses a loose cast `ref: toolDef as any` when constructing the
owner ref for ToolInstance; change the type instead of using any by widening
EntryOwnerRef.ref to accept the tool definition type (e.g., make
EntryOwnerRef.ref: unknown | ToolType | UnionOfToolDefs) or add a specific
ToolDef type to the union, then remove the `as any` cast in the tool creation
loop (the one that builds new ToolInstance with ref) and adjust any other
callers of EntryOwnerRef to comply with the new type; ensure normalizeTool,
ToolInstance constructor signature and providers usage remain type-compatible
after the EntryOwnerRef.ref type update.
libs/di/src/tokens/token.factory.ts (1)

10-14: JSDoc examples are now inaccurate after Symbol.for() change.

The examples show // => Symbol('MyApp:type:UserService') and // => Symbol('MyApp:meta:config'), but the implementation now returns Symbol.for(...) which has different semantics (globally shared vs. unique).

📝 Proposed documentation fix
  * // Create typed tokens
  * const serviceToken = tokens.type('UserService');
- * // => Symbol('MyApp:type:UserService')
+ * // => Symbol.for('MyApp:type:UserService')
  *
  * const metaToken = tokens.meta('config');
- * // => Symbol('MyApp:meta:config')
+ * // => Symbol.for('MyApp:meta:config')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/di/src/tokens/token.factory.ts` around lines 10 - 14, The JSDoc examples
are incorrect because the factory now uses Symbol.for() (global symbols) instead
of Symbol(), so update the examples in token.factory.ts to show the actual
return value syntax (e.g. Symbol.for('MyApp:type:UserService') and
Symbol.for('MyApp:meta:config')) and/or add a short note that tokens.type and
tokens.meta produce globally registered symbols via Symbol.for rather than
unique symbols; locate usages of tokens.type and tokens.meta in the file to
update the example strings and explanatory text accordingly.
libs/sdk/src/common/schemas/annotated-class.schema.ts (2)

4-17: Function definition placed before imports is unconventional.

The hasMetadataCompat helper function is defined at lines 10-17, but the import statements from ../tokens follow at lines 18-34. Standard TypeScript/JavaScript convention places all imports at the top of the file before any code definitions. This could cause confusion during maintenance.

♻️ Suggested restructure

Move the hasMetadataCompat function definition after all import statements (after line 42) to follow conventional module organization.

Also applies to: 18-34

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/sdk/src/common/schemas/annotated-class.schema.ts` around lines 4 - 17,
Move the helper function hasMetadataCompat so all import statements appear
before any code: relocate the entire hasMetadataCompat function definition to
after the existing imports (including the imports from ../tokens) to follow
conventional TS/JS module ordering and improve maintainability; ensure
references to Reflect and token usage remain intact after the move.

316-316: Inconsistent cast pattern for symbol-indexed property access.

Line 316 uses (v as unknown as Record<symbol, unknown>)[FrontMcpChannelTokens.type] while similar checks elsewhere (e.g., line 164, 179-181, 197, 220, 253, 269, 286) access the property directly with v[Token.type] without the intermediate cast.

For consistency, use the same pattern across all schemas.

♻️ Proposed fix for consistency
       // Function-style channel() builder
-      if ((v as unknown as Record<symbol, unknown>)[FrontMcpChannelTokens.type] !== undefined) {
+      if (v[FrontMcpChannelTokens.type] !== undefined) {
         return true;
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/sdk/src/common/schemas/annotated-class.schema.ts` at line 316, The check
on line using (v as unknown as Record<symbol,
unknown>)[FrontMcpChannelTokens.type] is inconsistent with other symbol-indexed
accesses (which use v[Token.type]); replace this expression with the same
pattern used elsewhere—access the symbol directly as
v[FrontMcpChannelTokens.type] (or if needed for TS indexing safety use (v as
any)[FrontMcpChannelTokens.type]) so the code matches the other checks for
symbol properties.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@libs/sdk/src/channel/channel.instance.ts`:
- Around line 91-102: The catch block in the connectService flow (inside the try
around ctx.onConnect() in channel.instance.ts) only logs the error and clears
this._serviceContext but does not re-throw, so callers like
registerChannelCapabilities get a false-success; update the catch to re-throw
the error after logging (or throw a wrapped Error including channel name and
original err) so the promise rejects and upstream rollback can occur; keep the
existing this._serviceContext = undefined and logger.error call but ensure the
error is propagated by re-throwing `err` (or a new Error that includes `err`)
from the catch.

---

Nitpick comments:
In `@libs/di/src/tokens/token.factory.ts`:
- Around line 10-14: The JSDoc examples are incorrect because the factory now
uses Symbol.for() (global symbols) instead of Symbol(), so update the examples
in token.factory.ts to show the actual return value syntax (e.g.
Symbol.for('MyApp:type:UserService') and Symbol.for('MyApp:meta:config')) and/or
add a short note that tokens.type and tokens.meta produce globally registered
symbols via Symbol.for rather than unique symbols; locate usages of tokens.type
and tokens.meta in the file to update the example strings and explanatory text
accordingly.

In `@libs/sdk/src/channel/channel-scope.helper.ts`:
- Around line 89-102: The code uses `as any` for `instance.metadata.source` and
`agentEmitterSubscribe` when calling `wireAgentCompletionSource` (and similarly
for the `job-completion` branch), so add proper types and narrow the union
instead of casting: define discriminated union types for channel sources (e.g.,
an `AgentCompletionSource` with `type: 'agent-completion'`) or implement a type
guard that checks `sourceType === 'agent-completion'` and then cast
`instance.metadata.source` to the specific source type, and ensure
`agentEmitterSubscribe` is the correct emitter type before passing to
`wireAgentCompletionSource`; do the analogous change for the `job-completion`
branch (e.g., `wireJobCompletionSource`) so both calls are fully typed without
`any`.
- Around line 154-170: The code currently uses a loose cast `ref: toolDef as
any` when constructing the owner ref for ToolInstance; change the type instead
of using any by widening EntryOwnerRef.ref to accept the tool definition type
(e.g., make EntryOwnerRef.ref: unknown | ToolType | UnionOfToolDefs) or add a
specific ToolDef type to the union, then remove the `as any` cast in the tool
creation loop (the one that builds new ToolInstance with ref) and adjust any
other callers of EntryOwnerRef to comply with the new type; ensure
normalizeTool, ToolInstance constructor signature and providers usage remain
type-compatible after the EntryOwnerRef.ref type update.

In `@libs/sdk/src/common/schemas/annotated-class.schema.ts`:
- Around line 4-17: Move the helper function hasMetadataCompat so all import
statements appear before any code: relocate the entire hasMetadataCompat
function definition to after the existing imports (including the imports from
../tokens) to follow conventional TS/JS module ordering and improve
maintainability; ensure references to Reflect and token usage remain intact
after the move.
- Line 316: The check on line using (v as unknown as Record<symbol,
unknown>)[FrontMcpChannelTokens.type] is inconsistent with other symbol-indexed
accesses (which use v[Token.type]); replace this expression with the same
pattern used elsewhere—access the symbol directly as
v[FrontMcpChannelTokens.type] (or if needed for TS indexing safety use (v as
any)[FrontMcpChannelTokens.type]) so the code matches the other checks for
symbol properties.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 386c6a20-2c8d-46dd-9499-e7783ede7eb0

📥 Commits

Reviewing files that changed from the base of the PR and between 7c6216e and 0f23617.

📒 Files selected for processing (9)
  • apps/e2e/demo-e2e-channels/src/apps/channels/tools/session-tools.tool.ts
  • libs/di/src/tokens/token.factory.ts
  • libs/sdk/src/channel/channel-scope.helper.ts
  • libs/sdk/src/channel/channel.instance.ts
  • libs/sdk/src/common/interfaces/channel.interface.ts
  • libs/sdk/src/common/schemas/annotated-class.schema.ts
  • libs/sdk/src/scope/scope.instance.ts
  • plugins/plugin-dashboard/package.json
  • plugins/plugin-remember/package.json
✅ Files skipped from review due to trivial changes (2)
  • plugins/plugin-dashboard/package.json
  • plugins/plugin-remember/package.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • libs/sdk/src/scope/scope.instance.ts
  • apps/e2e/demo-e2e-channels/src/apps/channels/tools/session-tools.tool.ts

Comment thread libs/sdk/src/channel/channel.instance.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (3)
apps/e2e/demo-e2e-channels/src/apps/channels/tools/session-tools.tool.ts (1)

207-209: ⚠️ Potential issue | 🟠 Major

Use a specific MCP error class instead of generic Error.

Line 208 throws new Error(...), which loses structured MCP/JSON-RPC error semantics expected by this codebase.

Suggested fix
+class ChannelNotFoundError extends Error {
+  readonly code = -32602;
+
+  constructor(channelName: string) {
+    super(`Channel "${channelName}" not found`);
+    this.name = 'ChannelNotFoundError';
+  }
+
+  toJsonRpcError() {
+    return { code: this.code, message: this.message };
+  }
+}
@@
     const channel = scope.channels?.findByName(input.channelName);
     if (!channel) {
-      throw new Error(`Channel "${input.channelName}" not found`);
+      this.fail(new ChannelNotFoundError(input.channelName));
     }
#!/bin/bash
# Verify existing error-handling conventions in adjacent tools and SDK context.
rg -n 'toJsonRpcError\(|class .*Error' apps/e2e/demo-e2e-channels/src/apps/channels/tools --type ts
rg -nC3 'throw new Error|this\.fail\(' apps/e2e/demo-e2e-channels/src/apps/channels/tools/session-tools.tool.ts
rg -nC3 'fail\(' libs/sdk/src --type ts

Based on learnings: Applies to **/*.ts — "Use specific error classes with MCP error codes instead of generic errors; implement toJsonRpcError() method for JSON-RPC compliance."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/e2e/demo-e2e-channels/src/apps/channels/tools/session-tools.tool.ts`
around lines 207 - 209, Replace the generic throw new Error(...) with the
project-specific MCP/JSON-RPC error type: construct and throw the MCP error
class used across the repo (e.g., MCPError/JsonRpcError) with an appropriate MCP
error code and message that includes input.channelName, and ensure the error
implements toJsonRpcError() if required by callers; locate the throw in
session-tools.tool.ts where channel and input.channelName are checked and swap
the generic Error for the MCP error instance so upstream JSON-RPC semantics are
preserved.
libs/sdk/src/scope/scope.instance.ts (1)

577-589: ⚠️ Potential issue | 🟠 Major

Don't leave agent-completion / job-completion channels inert.

This scope path never passes either subscribe callback, so any declared channel with those source types initializes successfully but can never emit. Either plumb the emitter subscriptions through here or fail fast when one of those source types is present.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/sdk/src/scope/scope.instance.ts` around lines 577 - 589, The channel
registration call to registerChannelCapabilities in scope.instance.ts currently
never provides agentEmitterSubscribe or jobEmitterSubscribe, so channels with
source types "agent-completion" or "job-completion" will be inert; update the
registerChannelCapabilities invocation to pass the appropriate subscribe
callbacks (e.g., agentEmitterSubscribe and jobEmitterSubscribe from the
agent/job registries or scope-level equivalents) alongside
providers/flowRegistry/toolRegistry/notificationService, or alternatively detect
those source types in this scope path and throw a clear error to fail fast when
either "agent-completion" or "job-completion" appears in appChannels; ensure you
reference registerChannelCapabilities, appChannels, and the emitter subscribe
function names so the subscriptions are wired or the initialization aborts.
libs/sdk/src/channel/channel-scope.helper.ts (1)

91-121: ⚠️ Potential issue | 🟠 Major

Run one shared best-effort cleanup path for all init/teardown failures.

Step 5 already installs live listeners, but rollback only happens when connectService() fails. If a later wire, flow registration, or reply-tool registration throws, initialization rejects with listeners still attached and no teardown handle to release them. Also, one throwing unsub() currently aborts the rest of cleanup. Please factor disconnect/unsubscribe/channelEventBus.clear() into a single best-effort helper and invoke it from an outer try/catch, the service rollback, and teardown().

Also applies to: 137-147, 177-220

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/sdk/src/channel/channel-scope.helper.ts` around lines 91 - 121, Create a
single best-effort cleanup helper (e.g., runBestEffortCleanup) that iterates the
unsubscribers array and calls each unsub() inside its own try/catch, then calls
channelEventBus.clear(), and use that helper from the outer initialization
try/catch, the connectService() failure rollback path, and teardown(); update
places that push results of wireAgentCompletionSource, wireJobCompletionSource,
wireAppEventSource (and other wiring like reply-tool/flow registration) to
always push their unsub function into the same unsubscribers array so the helper
can clean them up, and ensure any thrown unsub() does not abort remaining
cleanup.
🧹 Nitpick comments (2)
apps/e2e/demo-e2e-channels/src/apps/channels/tools/session-tools.tool.ts (1)

11-18: Use concrete FakeServer type instead of unknown for server registration.

Lines 12, 32, and 84 use unknown for MCP server protocol types, forcing a double-cast workaround. Replace the structural NotificationServiceLike.registerServer parameter with a concrete FakeServer type to eliminate the cast chain and maintain strict typing.

Suggested refactor
 interface NotificationServiceLike {
-  registerServer(sessionId: string, server: unknown): void;
+  registerServer(sessionId: string, server: FakeServer): void;
   unregisterServer(sessionId: string): void;
   setClientCapabilities(sessionId: string, capabilities: Record<string, unknown>): void;
   subscribeChannel(sessionId: string, channelName: string): boolean;
   unsubscribeChannel(sessionId: string, channelName: string): boolean;
   subscribeAllChannels(sessionId: string, channelNames: string[]): void;
 }

 interface FakeServer {
   notification(msg: { method: string; params: Record<string, unknown> }): void;
-  request(msg: { method: string; params?: unknown }): Promise<unknown>;
+  request(msg: { method: string; params?: unknown }): Promise<Record<string, unknown>>;
 }

-    notifications.registerServer(
-      input.sessionId,
-      fakeServer as unknown as Parameters<typeof notifications.registerServer>[1],
-    );
+    notifications.registerServer(input.sessionId, fakeServer);

Per coding guidelines: "For MCP protocol outputs, prefer concrete MCP result types rather than unknown" and "Follow existing TypeScript patterns and keep strict typing."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/e2e/demo-e2e-channels/src/apps/channels/tools/session-tools.tool.ts`
around lines 11 - 18, Change the NotificationServiceLike.registerServer
parameter from unknown to the concrete FakeServer type and update all
implementations and callers to use FakeServer (remove the double-cast
workaround). Specifically, replace the server parameter type in
NotificationServiceLike.registerServer with FakeServer, import/declare the
FakeServer type in this module, and update any functions or classes that
implement NotificationServiceLike or call registerServer (and other spots that
previously used unknown for MCP server protocol types on lines referenced) to
accept/return FakeServer so the casts are no longer required.
libs/sdk/src/channel/channel-scope.helper.ts (1)

92-95: Remove the any escapes from source and tool wiring.

These casts sidestep the discriminated source/tool types on a protocol-facing path, so contract drift will only show up at runtime. Please thread the concrete channel source and tool entry types through this helper instead.

As per coding guidelines, "Do not introduce any without clear justification" and "Use strict TypeScript with no anytypes without strong justification; useunknowninstead ofany for generic type defaults".

Also applies to: 106-109, 157-160

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/sdk/src/channel/channel-scope.helper.ts` around lines 92 - 95, The
helper currently bypasses TypeScript's discriminated source/tool types by
casting instance.metadata.source and agentEmitterSubscribe to any when calling
wireAgentCompletionSource (and the similar casts at the other occurrences around
lines 106-109 and 157-160); update the helper signature to accept properly
generic/concrete channel source and tool entry types and propagate those
generics into the call to wireAgentCompletionSource and agentEmitterSubscribe so
you can pass instance.metadata.source and the emitter without any casts; remove
the any casts and adjust the helper's type parameters (or overloads) so
instance: ChannelInstance<SourceType, ToolType> (or equivalent existing types)
is used, or default to unknown rather than any if a generic default is needed,
ensuring all three call sites use the concrete typed parameters instead of any.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@apps/e2e/demo-e2e-channels/src/apps/channels/tools/session-tools.tool.ts`:
- Around line 207-209: Replace the generic throw new Error(...) with the
project-specific MCP/JSON-RPC error type: construct and throw the MCP error
class used across the repo (e.g., MCPError/JsonRpcError) with an appropriate MCP
error code and message that includes input.channelName, and ensure the error
implements toJsonRpcError() if required by callers; locate the throw in
session-tools.tool.ts where channel and input.channelName are checked and swap
the generic Error for the MCP error instance so upstream JSON-RPC semantics are
preserved.

In `@libs/sdk/src/channel/channel-scope.helper.ts`:
- Around line 91-121: Create a single best-effort cleanup helper (e.g.,
runBestEffortCleanup) that iterates the unsubscribers array and calls each
unsub() inside its own try/catch, then calls channelEventBus.clear(), and use
that helper from the outer initialization try/catch, the connectService()
failure rollback path, and teardown(); update places that push results of
wireAgentCompletionSource, wireJobCompletionSource, wireAppEventSource (and
other wiring like reply-tool/flow registration) to always push their unsub
function into the same unsubscribers array so the helper can clean them up, and
ensure any thrown unsub() does not abort remaining cleanup.

In `@libs/sdk/src/scope/scope.instance.ts`:
- Around line 577-589: The channel registration call to
registerChannelCapabilities in scope.instance.ts currently never provides
agentEmitterSubscribe or jobEmitterSubscribe, so channels with source types
"agent-completion" or "job-completion" will be inert; update the
registerChannelCapabilities invocation to pass the appropriate subscribe
callbacks (e.g., agentEmitterSubscribe and jobEmitterSubscribe from the
agent/job registries or scope-level equivalents) alongside
providers/flowRegistry/toolRegistry/notificationService, or alternatively detect
those source types in this scope path and throw a clear error to fail fast when
either "agent-completion" or "job-completion" appears in appChannels; ensure you
reference registerChannelCapabilities, appChannels, and the emitter subscribe
function names so the subscriptions are wired or the initialization aborts.

---

Nitpick comments:
In `@apps/e2e/demo-e2e-channels/src/apps/channels/tools/session-tools.tool.ts`:
- Around line 11-18: Change the NotificationServiceLike.registerServer parameter
from unknown to the concrete FakeServer type and update all implementations and
callers to use FakeServer (remove the double-cast workaround). Specifically,
replace the server parameter type in NotificationServiceLike.registerServer with
FakeServer, import/declare the FakeServer type in this module, and update any
functions or classes that implement NotificationServiceLike or call
registerServer (and other spots that previously used unknown for MCP server
protocol types on lines referenced) to accept/return FakeServer so the casts are
no longer required.

In `@libs/sdk/src/channel/channel-scope.helper.ts`:
- Around line 92-95: The helper currently bypasses TypeScript's discriminated
source/tool types by casting instance.metadata.source and agentEmitterSubscribe
to any when calling wireAgentCompletionSource (and the similar casts at the
other occurrences around lines 106-109 and 157-160); update the helper signature
to accept properly generic/concrete channel source and tool entry types and
propagate those generics into the call to wireAgentCompletionSource and
agentEmitterSubscribe so you can pass instance.metadata.source and the emitter
without any casts; remove the any casts and adjust the helper's type parameters
(or overloads) so instance: ChannelInstance<SourceType, ToolType> (or equivalent
existing types) is used, or default to unknown rather than any if a generic
default is needed, ensuring all three call sites use the concrete typed
parameters instead of any.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7c4c19ec-22ba-45de-aca3-1466849b806f

📥 Commits

Reviewing files that changed from the base of the PR and between 7c6216e and 54591c2.

📒 Files selected for processing (15)
  • apps/e2e/demo-e2e-channels/src/apps/channels/tools/session-tools.tool.ts
  • libs/di/src/__tests__/token.factory.spec.ts
  • libs/di/src/tokens/token.factory.ts
  • libs/observability/package.json
  • libs/sdk/src/channel/channel-scope.helper.ts
  • libs/sdk/src/channel/channel.instance.ts
  • libs/sdk/src/common/interfaces/channel.interface.ts
  • libs/sdk/src/common/schemas/annotated-class.schema.ts
  • libs/sdk/src/scope/scope.instance.ts
  • nx.json
  • plugins/plugin-approval/package.json
  • plugins/plugin-cache/package.json
  • plugins/plugin-dashboard/package.json
  • plugins/plugin-feature-flags/package.json
  • plugins/plugin-remember/package.json
✅ Files skipped from review due to trivial changes (7)
  • plugins/plugin-feature-flags/package.json
  • libs/di/src/tokens/token.factory.ts
  • plugins/plugin-cache/package.json
  • plugins/plugin-approval/package.json
  • plugins/plugin-remember/package.json
  • libs/observability/package.json
  • plugins/plugin-dashboard/package.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • libs/sdk/src/common/schemas/annotated-class.schema.ts
  • libs/sdk/src/channel/channel.instance.ts

@frontegg-david frontegg-david merged commit 9961bf1 into main Apr 6, 2026
29 checks passed
@frontegg-david frontegg-david deleted the support-claude-channels branch April 6, 2026 23:01
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