Skip to content

feat(n8n): propagate originating-conversation routing into workflow generator#7164

Merged
lalalune merged 4 commits into
elizaOS:developfrom
2-A-M:milady/n8n-trigger-context
Apr 29, 2026
Merged

feat(n8n): propagate originating-conversation routing into workflow generator#7164
lalalune merged 4 commits into
elizaOS:developfrom
2-A-M:milady/n8n-trigger-context

Conversation

@2-A-M
Copy link
Copy Markdown
Contributor

@2-A-M 2-A-M commented Apr 28, 2026

Summary

When a workflow is generated from inside a platform conversation (e.g. a Discord DM or a Telegram chat), surface the originating channel or chat to the LLM as a ## Runtime Facts line. The user can say "post the result back to this channel" and the generated send node targets the right Discord channel ID / Telegram chat ID without having to name it — closing the long-tail of the same NL ergonomics goal that motivated #7134's missing-credentials banner.

End-to-end wiring

  • client-types-chat.ts: extend N8nWorkflowGenerateRequest with optional bridgeConversationId. AutomationsView already had the id in scope (it uses it to bind the workflow to the originating conversation); now also forwards it on the generation request.

  • n8n-routes.ts: when bridgeConversationId is present, read the originating conversation's tail inbound message metadata via runtime.getMemories({ roomId, tableName: \"messages\", count: 12 }), derive a TriggerContext (Discord channelId/guildId, Telegram chatId/threadId, Slack channelId/teamId), and thread it into service.generateWorkflowDraft(prompt, { triggerContext }). The helper reads both the canonical metadata.discord.{channelId,guildId} sub-object and the legacy flat discordChannelId / discordServerId fields — pre-existing schema gap (canonical wins when present, flat is the fallback so nothing today breaks).

  • n8n-runtime-context-provider.ts: extend RuntimeContextProviderInput to accept the trigger context, render it as a fact line:

    This workflow was prompted from a Discord conversation in #general (id 9876543210) within "Cozy Devs" (id 1234567890). When the user references "this channel" or "back to here", target that channel ID.

    Same pattern for Telegram chats and Slack channels. Empty/missing routing data → no fact line.

Backward compatibility

  • Routes still work without bridgeConversationId (no triggerContext threading, baseline behavior).
  • Plugin still works with hosts that don't pass triggerContext (the optional opts arg on generateWorkflowDraft is unused — see elizaos-plugins/plugin-n8n-workflow#26).

Depends on

Test plan

  • From a Discord DM: prompt "every weekday at 9am post the day's calendar agenda back to this channel". Inspect the deployed workflow's Discord Send node — channelId equals the originating Discord channel id, not blank, not a placeholder.
  • Same prompt from a Telegram chat → Telegram Send node chatId equals the originating Telegram chat id.
  • No bridgeConversationId in the request → behavior unchanged.

Out of scope (follow-up)

  • Persist originChannelContext on the workflow's conversation metadata so re-runs without a fresh inbound message still target the same channel.
  • Switch upstream plugin-discord/telegram from flat metadata fields to the canonical nested metadata.discord.{channelId,guildId,messageId} shape — this PR's helper handles both transitionally.

Greptile Summary

This PR wires originating-conversation routing (Discord channel/guild, Telegram chat, Slack channel) into the n8n workflow generator so the LLM can resolve "post back to this channel" without the user supplying an ID. It also introduces the n8n-runtime-context-provider service (with tests) and registers it during agent boot.

The previously-flagged P1 (fromId used as Telegram chatId fallback) has been correctly addressed — the code now skips Telegram routing rather than guess. The discordWebhookApi/googleOAuth2Api inconsistency has also been resolved. Remaining notes are P2 quality items (memory ordering comment and missing triggerContext test coverage).

Confidence Score: 5/5

Safe to merge; no blocking issues remain — all P1s from the previous review have been addressed

The Telegram fromId P1 is fixed and the discordWebhookApi/googleOAuth2Api P2 is also resolved. All remaining findings are P2: a misleading memory-ordering comment, a trivially redundant inner conditional, and missing test coverage for the new triggerContext path. None of these block correctness.

packages/app-core/src/api/n8n-routes.ts (memory ordering comment), packages/app-core/src/services/n8n-runtime-context-provider.test.ts (missing triggerContext tests)

Important Files Changed

Filename Overview
packages/app-core/src/api/n8n-routes.ts Adds buildTriggerContextFromConversation helper and threads triggerContext into generateWorkflowDraft; the Telegram fromId P1 has been addressed but memory ordering logic has a misleading comment
packages/app-core/src/services/n8n-runtime-context-provider.ts New service file; surfaces Discord/Gmail/triggerContext facts to the n8n workflow generator; well-structured with caching and defensive fallbacks
packages/app-core/src/services/n8n-runtime-context-provider.test.ts New test file covering registration, Discord facts, Gmail facts, credential filtering, network failures, and caching — but no test cases for the new triggerContext / formatTriggerContextFact path
packages/app-core/src/runtime/eliza.ts Registers n8n runtime-context provider via ensureN8nRuntimeContextProvider; pattern mirrors existing bridge initializers; clean lifecycle management
packages/app-core/src/api/client-types-chat.ts Adds optional bridgeConversationId field to N8nWorkflowGenerateRequest; straightforward and well-documented
packages/app-core/src/components/pages/AutomationsView.tsx Forwards bridgeConversationId into the workflow generation request; minimal one-line change using existing in-scope variable

Sequence Diagram

sequenceDiagram
    participant Client as AutomationsView (client)
    participant Route as n8n-routes (server)
    participant Runtime as AgentRuntime
    participant CtxProvider as N8nRuntimeContextProvider
    participant Service as plugin-n8n-workflow

    Client->>Route: POST /generate {prompt, bridgeConversationId}
    Route->>Runtime: getMemories({roomId: bridgeConversationId, count: 12})
    Runtime-->>Route: Memory[] (messages)
    Note over Route: buildTriggerContextFromConversation()<br/>finds first non-agent message,<br/>reads metadata.discord / metadata.telegram / metadata.slack
    Route-->>Route: TriggerContext {source, discord/telegram/slack}
    Route->>Service: generateWorkflowDraft(prompt, {triggerContext})
    Service->>CtxProvider: getRuntimeContext({userId, relevantNodes, triggerContext})
    CtxProvider-->>Service: {facts: [This workflow was prompted from Discord...], supportedCredentials: [...]}
    Service-->>Route: WorkflowDraft (with channel IDs filled in)
    Route-->>Client: WorkflowDraft JSON
Loading

Comments Outside Diff (4)

  1. packages/app-core/src/api/n8n-routes.ts, line 111-117 (link)

    P1 fromId is not chatId in Telegram group contexts

    meta.fromId is the Telegram sender's user ID, not the chat ID. In a private DM the two happen to be the same, but in a group chat or channel the chat ID is a distinct negative integer. Using fromId as the fallback chatId will cause the generated workflow to target the individual user's DM inbox instead of the originating group, silently breaking the "post back to this channel" feature for the majority of Telegram team-chat scenarios.

    If the canonical metadata.telegram.chatId isn't populated, the safest fallback is undefined (return no Telegram trigger context) rather than a value that may route to the wrong entity.

  2. packages/app-core/src/services/n8n-runtime-context-provider.ts, line 641-654 (link)

    P2 discordWebhookApi and googleOAuth2Api silently unresolvable

    Both discordWebhookApi and googleOAuth2Api appear in MILADY_SUPPORTED_CRED_TYPES but have no corresponding entry in CRED_TYPE_FACTS. The guard if (!meta) continue at line 958 silently skips them, so they are never advertised in supportedCredentials even when the user has configured them. Either add entries to CRED_TYPE_FACTS or remove these types from MILADY_SUPPORTED_CRED_TYPES to keep the two sets consistent.

  3. packages/app-core/src/services/n8n-runtime-context-provider.test.ts, line 351 (link)

    P2 No tests for triggerContext propagation

    The test file exercises Discord facts, Gmail facts, credential filtering, caching, and network failures — but there are no test cases for the new triggerContext / formatTriggerContextFact path that this PR adds. Adding at least one test per platform (Discord channel, Telegram chat, Slack channel, and the empty-context case) would close the coverage gap and guard against regressions in the fact-line wording.

  4. packages/app-core/src/api/n8n-routes.ts, line 84-89 (link)

    P2 Memory ordering not defensively handled

    The comment notes that runtime.getMemories "typically returns most-recent-first" but also says the code "defensively handles either order." In practice, memories.find(m => m.entityId !== runtime.agentId) returns the first matching element — if the API returns oldest-first, this yields the oldest inbound message rather than the most recent one. For an active conversation this could mean routing to a stale channel/chat ID. Consider sorting by a timestamp field (if available) or explicitly documenting the assumed ordering so a future reader knows when this assumption breaks.

Reviews (2): Last reviewed commit: "fix(n8n): drop Telegram fromId fallback ..." | Re-trigger Greptile

…rd guilds/channels + Gmail email to the workflow generator

Registers a service of type `n8n_runtime_context_provider` so the patched
`@elizaos/plugin-n8n-workflow` (RuntimeContextProvider extension point)
can pull live connector facts into the workflow-generation prompt:

- **Discord facts**: enumerates the bot's joined guilds + their text
  channels via the Discord REST API, emitting one fact line per guild
  (`Discord guild "Cozy Devs" (id …) channels: #general (id …), #alerts
  (id …).`). 5-minute REST cache keeps generate→modify regeneration
  bursts cheap. Network failures degrade to empty facts; never block
  generation.
- **Gmail fact**: surfaces the connected Gmail address so the LLM
  substitutes the real value instead of `<your-email-here>`.
- **Supported credentials**: only advertises cred types that the host's
  optional `credProvider.resolve()` confirms have data right now (so we
  don't promise a credential the user hasn't wired up yet). Without a
  credProvider, falls back to "config has connector token" heuristics.

Together with the prompt hardening shipped in plugin-n8n-workflow#25,
this closes the placeholder-id gap that previously made the LLM emit
`guildId: "={{YOUR_SERVER_ID}}"` when the runtime already knew the real
ID.

Wire-up in `runtime/eliza.ts` follows the same hot-reload pattern as
the other n8n bridges. The provider is optional from the plugin's
perspective: when not registered, the prompt simply omits the
`## Available Credentials` and `## Runtime Facts` sections.

Includes 8 unit tests (`n8n-runtime-context-provider.test.ts`).

Depends on: elizaos-plugins/plugin-n8n-workflow#25 at runtime — host
compiles fine without the plugin upgrade, but the prompt hardening only
takes effect once the plugin's RuntimeContextProvider extension point
ships.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 28, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5482b208-c32b-471e-ba34-55651bc0d7cd

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

2-A-M added 3 commits April 28, 2026 12:39
…om supported set

Both types were listed in MILADY_SUPPORTED_CRED_TYPES but had no entry in
CRED_TYPE_FACTS, so they passed the first guard in
computeSupportedCredentials() and then immediately dropped at the
`!meta` continue. Net effect: silently empty supportedCredentials for
Discord webhook workflows and generic Google OAuth (Greptile P1).

Drop them from the supported set instead of inventing fact entries —
Discord workflows go through the bot API path (discordBotApi) and the
specific google*OAuth2Api types cover the actual nodes we surface.
Comment block now flags the constraint so future additions stay in
sync with CRED_TYPE_FACTS.
…enerator

When the workflow is generated from inside a platform conversation
(e.g. a Discord DM or a Telegram chat), surface the originating channel
or chat to the LLM as a `## Runtime Facts` line so the user can say
"post the result back to this channel" and the generated send node
targets the right ID without naming it.

End-to-end wiring:

- `client-types-chat.ts`: extend `N8nWorkflowGenerateRequest` with
  optional `bridgeConversationId`. AutomationsView already had the id
  in scope (it uses it to bind the workflow to the originating
  conversation); now also forwards it on the generation request.

- `n8n-routes.ts`: when `bridgeConversationId` is present, read the
  originating conversation's tail inbound message metadata via
  `runtime.getMemories({ roomId, tableName: "messages", count: 12 })`,
  derive a `TriggerContext` (Discord channelId/guildId, Telegram
  chatId/threadId, Slack channelId/teamId), and thread it into
  `service.generateWorkflowDraft(prompt, { triggerContext })`. The
  helper reads BOTH the canonical `metadata.discord.{channelId,guildId}`
  sub-object AND the legacy flat `discordChannelId` / `discordServerId`
  fields — pre-existing schema gap; canonical wins when present, flat
  is the fallback so nothing today breaks.

- `n8n-runtime-context-provider.ts`: extend `RuntimeContextProviderInput`
  to accept the trigger context, render it as a fact line:

      This workflow was prompted from a Discord conversation in
      #general (id 9876543210) within "Cozy Devs" (id 1234567890).
      When the user references "this channel" or "back to here",
      target that channel ID.

  Same pattern for Telegram chats and Slack channels. Empty / missing
  routing data → no fact line.

Backward compatibility:
- Routes still work without `bridgeConversationId` (no triggerContext
  threading, baseline behavior).
- Plugin still works with hosts that don't pass triggerContext (the
  optional `opts` arg on `generateWorkflowDraft` is unused — see
  elizaos-plugins/plugin-n8n-workflow#26).

Out of scope (follow-up):
- Persist `originChannelContext` on the workflow's conversation metadata
  for re-runs without a fresh inbound message.
- Switch upstream plugin-discord/telegram from flat metadata fields to
  the canonical nested `metadata.discord.{channelId,guildId,messageId}`
  shape.

Depends on: elizaos-plugins/plugin-n8n-workflow#26 (TriggerContext on
RuntimeContextProviderInput) at runtime; this PR's host code compiles
and falls through cleanly until the plugin upgrade is wired.
`meta.fromId` is the Telegram sender's *user* id, which equals the chat
id only in private 1:1 DMs. In group chats / channels the chat id is a
distinct (typically negative) integer. Falling back to fromId silently
routed group-chat workflows back to the user's DM instead of the group
(Greptile P1).

Drop the fallback. Only use the canonical `metadata.telegram.chatId`.
If the upstream Telegram plugin hasn't populated that yet (pre-existing
schema gap — flat `metadata.fromId` is the de-facto inbound shape today
for Telegram), Telegram routing is skipped rather than guessed. Discord
and Slack routing are unaffected.
@2-A-M 2-A-M force-pushed the milady/n8n-trigger-context branch from 4cae1c5 to 975dc2c Compare April 28, 2026 15:40
@lalalune lalalune merged commit 87c9ef6 into elizaOS:develop Apr 29, 2026
3 checks passed
lalalune added a commit that referenced this pull request May 3, 2026
feat(n8n): propagate originating-conversation routing into workflow generator
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants