feat(n8n): propagate originating-conversation routing into workflow generator#7164
Merged
Merged
Conversation
…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.
Contributor
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
…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.
4cae1c5 to
975dc2c
Compare
lalalune
added a commit
that referenced
this pull request
May 3, 2026
feat(n8n): propagate originating-conversation routing into workflow generator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 Factsline. 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: extendN8nWorkflowGenerateRequestwith optionalbridgeConversationId.AutomationsViewalready 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: whenbridgeConversationIdis present, read the originating conversation's tail inbound message metadata viaruntime.getMemories({ roomId, tableName: \"messages\", count: 12 }), derive aTriggerContext(Discord channelId/guildId, Telegram chatId/threadId, Slack channelId/teamId), and thread it intoservice.generateWorkflowDraft(prompt, { triggerContext }). The helper reads both the canonicalmetadata.discord.{channelId,guildId}sub-object and the legacy flatdiscordChannelId/discordServerIdfields — pre-existing schema gap (canonical wins when present, flat is the fallback so nothing today breaks).n8n-runtime-context-provider.ts: extendRuntimeContextProviderInputto accept the trigger context, render it as a fact line:Same pattern for Telegram chats and Slack channels. Empty/missing routing data → no fact line.
Backward compatibility
bridgeConversationId(no triggerContext threading, baseline behavior).optsarg ongenerateWorkflowDraftis unused — see elizaos-plugins/plugin-n8n-workflow#26).Depends on
Test plan
Discord Sendnode —channelIdequals the originating Discord channel id, not blank, not a placeholder.Telegram SendnodechatIdequals the originating Telegram chat id.bridgeConversationIdin the request → behavior unchanged.Out of scope (follow-up)
originChannelContexton the workflow's conversation metadata so re-runs without a fresh inbound message still target the same channel.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-providerservice (with tests) and registers it during agent boot.The previously-flagged P1 (
fromIdused as TelegramchatIdfallback) has been correctly addressed — the code now skips Telegram routing rather than guess. ThediscordWebhookApi/googleOAuth2Apiinconsistency has also been resolved. Remaining notes are P2 quality items (memory ordering comment and missingtriggerContexttest 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
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 JSONComments Outside Diff (4)
packages/app-core/src/api/n8n-routes.ts, line 111-117 (link)fromIdis notchatIdin Telegram group contextsmeta.fromIdis 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. UsingfromIdas the fallbackchatIdwill 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.chatIdisn't populated, the safest fallback isundefined(return no Telegram trigger context) rather than a value that may route to the wrong entity.packages/app-core/src/services/n8n-runtime-context-provider.ts, line 641-654 (link)discordWebhookApiandgoogleOAuth2Apisilently unresolvableBoth
discordWebhookApiandgoogleOAuth2Apiappear inMILADY_SUPPORTED_CRED_TYPESbut have no corresponding entry inCRED_TYPE_FACTS. The guardif (!meta) continueat line 958 silently skips them, so they are never advertised insupportedCredentialseven when the user has configured them. Either add entries toCRED_TYPE_FACTSor remove these types fromMILADY_SUPPORTED_CRED_TYPESto keep the two sets consistent.packages/app-core/src/services/n8n-runtime-context-provider.test.ts, line 351 (link)triggerContextpropagationThe test file exercises Discord facts, Gmail facts, credential filtering, caching, and network failures — but there are no test cases for the new
triggerContext/formatTriggerContextFactpath 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.packages/app-core/src/api/n8n-routes.ts, line 84-89 (link)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