n8n-nodes-adeu
v1.10.1
Published
n8n community node for Adeu — apply AI-driven tracked changes, generate diffs, and sanitize Microsoft Word (.docx) documents.
Maintainers
Readme
n8n-nodes-adeu
An n8n community node for Adeu — the AI-native Virtual DOM for Microsoft Word.
🆕 New in Upcomming release:
Apply Editsnow supports a Dry Run mode that previews edits without committing them. AI Agents can use it as a self-correction primitive to verify anchors before issuing a real edit. See the Dry Run section and the$fromAIrecipe below. Existing workflows must hand-add theDry_Runbinding — n8n caches$fromAIexpressions per workflow and does not retroactively update them on package upgrades.
This node bridges the gap between Large Language Models (LLMs) and Microsoft Word. It translates complex OpenXML (.docx) files into token-efficient Markdown, allows AI models to reason over legal or technical text, and translates the AI's JSON output back into native Word Tracked Changes and Comments — all completely in-process, without your documents ever leaving the n8n runtime.
📦 Installation
Depending on your self-hosted n8n configuration, you can install this node via the UI, environment variables, or manually.
Method 1: GUI Installation (Recommended)
- In n8n, go to Settings > Community Nodes.
- Select Install.
- Enter
n8n-nodes-adeuin the Enter npm package name field. - Check I understand the risks of installing unverified code from a public source.
- Select Install.
Method 2: Environment Variables
For automated deployments, you can bootstrap your n8n instance with a fixed set of packages via environment variables:
export N8N_COMMUNITY_PACKAGES_MANAGED_BY_ENV=true
export N8N_COMMUNITY_PACKAGES='[{"name":"n8n-nodes-adeu"}]'Note: Enabling this makes the Community Nodes settings UI read-only and will automatically uninstall any packages not listed in the JSON array.
Method 3: Manual Installation (Queue Mode)
If your n8n instance runs in queue mode or you prefer terminal installation, you can install the node manually:
docker exec -it n8n sh
mkdir -p ~/.n8n/nodes
cd ~/.n8n/nodes
npm i n8n-nodes-adeuRestart your n8n instance after installation.
🚀 Key Capabilities
- CriticMarkup Projection: Translates existing Word tracked changes into standard Markdown (
{++inserted++},{--deleted--}). - Semantic Appendix: Automatically extracts defined terms, cross-references, and potential typos to give LLMs deeper context.
- Native Redlining: Apply
modify,accept,reject, andreplyactions directly to the OOXML tree. - Document Sanitization: Strip metadata, auto-accept markup, and apply read-only locks before sending to counterparties.
⚙️ Operations
The node exposes one resource (Document) with five operations:
1. Extract Markdown
Projects a .docx file into LLM-friendly Markdown.
- Input:
.docxbinary. - Output: JSON
{ markdown, fileName, cleanView }. - Clean View toggle:
False(Raw View): Shows all pending tracked changes via CriticMarkup. Best for resolving counterparty edits.True(Clean View): Simulates an "Accept All" state, hiding markup. Best for generating net-new redlines on a clean baseline.
2. Apply Edits
Applies a JSON array of DocumentChange operations back to the Word document as tracked changes and comments.
- Input:
.docxbinary + achangesJSON array (read from an upstream node or defined inline). - Output: A new redlined
.docxbinary + JSON application stats. - Atomic Batch Validation: Adeu pre-validates the entire array of edits before touching the document. If even one edit is invalid (e.g., target text not found, ambiguous match), the engine safely rejects the entire batch to prevent partial or corrupted document states.
🔍 Dry Run Mode (Self-Correction for AI Agents)
Apply Edits accepts an optional Dry Run boolean (default false). When enabled:
- Every edit is validated and simulated in-memory.
- The outgoing JSON contains a
stats.editsarray where each entry includesstatus(applied/failed), acritic_markuppreview (~30 chars of surrounding context showing exactly where the change would land), aclean_textpost-accept preview, awarningfield (e.g. for punctuation-anchored targets prone to tokenization splits), and anerrorfield if validation failed. - The document is not modified. No redlined
.docxbinary is produced. NoredlinedBinaryIdis stashed in workflow static data. The incoming binary passes through unchanged on the outgoing item, so downstream nodes that expected continuity do not break. - Critical behavioral difference: in a wet run (
Dry Run=false), the engine throws aBatchValidationErroratomically on the first invalid edit. In a dry run, invalid edits return asfailedentries in theeditsarray instead — making dry-run also useful as a probe for whether an edit will succeed. - The downstream
Hydrate Tool Outputnode naturally short-circuits during a dry run (no static-data entry to read), so the existing AI Agent pipeline correctly skips the file-write step.
When the AI should use it: as a self-correction primitive for uncertain anchors (long quotes, legal terminology, possible duplicate phrases). The agent dry-runs, inspects the critic_markup preview, then re-calls with Dry_Run=false to commit. The system prompt in the example workflow tells the LLM explicitly to use dry-run sparingly — every dry run is an extra round trip.
3. Generate Diff
Produces a sub-word level @@ Word Patch @@ diff between two versions of a document.
- Input: Two
.docxbinaries on the same item (e.g.,dataanddata2). - Output: JSON
{ diff, originalFileName, modifiedFileName }.
4. Finalize Document
Prepares a document for signature or external distribution.
- Modes:
Full: Strips all metadata and requires all tracked changes/comments to be resolved (or auto-accepted).Keep Markup: Strips metadata but preserves visible tracked changes. Allows you to override theAuthorname (e.g., change "Adeu AI" to "My Law Firm").Baseline: Only strips background noise (RSIDs, proof errors) without touching metadata.
- Protection: Can inject a native Word "Read-Only" lock into the document settings.
5. Hydrate Tool Output (The "Hydration" Note)
Because n8n's AI Agent tool wrapper intercepts and strips all binary data from tool outputs, files generated inside an AI loop cannot reach downstream nodes directly.
- What it does: This operation is placed immediately downstream of the AI Agent on the main workflow execution line. It reads the stashed metadata pointer left by the last execution of
apply_edits, retrieves the raw file stream directly from n8n's secure binary storage, and attaches a fresh binary buffer onto the outgoing item. - Output Path Construction: It supports an optional output path template (e.g.,
C:\path\to\folder\{baseName}_{timestamp}.docx) to resolve path strings inside TypeScript. This avoids expression-parsing and escape issues when configuring downstream Write File nodes on Windows.
🧠 The DocumentChange Schema
To use the Apply Edits operation, your LLM must output a JSON array of objects matching this schema.
| Type | Required Fields | Description |
| :--- | :--- | :--- |
| modify | target_text, new_text | Replaces baseline text. Use the comment field to attach a comment bubble. |
| accept | target_id | Accepts an existing tracked change (e.g., Chg:123). |
| reject | target_id | Rejects an existing tracked change. |
| reply | target_id, text | Replies to an existing comment (e.g., Com:456). |
| insert_row | target_text, position, cells | Inserts a new table row above or below the target cell text. |
| delete_row | target_text | Deletes the table row containing the target text. |
Example LLM Output:
[
{
"type": "reject",
"target_id": "Chg:12",
"comment": "We cannot accept 60-day terms."
},
{
"type": "modify",
"target_text": "within thirty (30) days",
"new_text": "within forty-five (45) days",
"comment": "Compromise per our playbook."
}
]🔄 Handling Cumulative & Multi-Turn Edits (The Binary ID Pattern)
When an AI Agent applies edits, receives feedback, and needs to make another round of changes, loading from the original node name (e.g., 'Read Binary File') would discard the modifications just made. To allow the model to chain consecutive edits seamlessly, the node utilizes an explicit state pointer pipeline:
- First Tool Call: The LLM loads from the baseline. It sets
Source_Node_Nameto the canvas node (e.g.,'Read Binary File') and leavesSource_Binary_Idblank. - Intermediate Output: The
apply_editstool applies changes and returns a uniqueredlinedBinaryId(representing the immutable state of that edit) back in the JSON payload to the LLM. - Subsequent Tool Calls: If the LLM needs to make further changes on top of its prior work, it must set
Source_Binary_Idto the ID string returned by the previous call. The node's backend dynamically detects this ID, bypasses the upstream node name, and pulls the intermediate document directly from storage to apply the new changes cumulatively. - Handoff: On every successful execution, the node overwrites a global static pointer (
adeu_last_redlined) with the newest ID. When the AI Agent finishes its entire chat turn, the downstreamHydrate Tool Outputnode reads this pointer to output the final, fully-cumulative document.
🏗️ Typical Pipeline
[ Gmail Trigger (Incoming Doc) ]
│
▼
[ Adeu: Extract Markdown ]
│
▼
[ AI Node (LLM) ] ← Outputs a JSON array of `DocumentChange` objects
│
▼
[ Adeu: Apply Edits ] ← Pre-validates and writes redlines atomically
│
▼
[ Gmail: Reply with Doc ]💡 Prompting Best Practices for LLMs
To achieve the highest batch success rate when prompting models like Gemini, GPT-4o, or Claude to generate edits:
- Enforce Exact Matching: Instruct the LLM: "The
target_textmust be copied EXACTLY from the source document — including identical punctuation, spacing, and capitalization." - Short but Unique: Instruct the LLM: "Keep
target_textshort, but ensure it is unique enough to not match multiple locations in the document." - No Fake Markup: Instruct the LLM: "Do NOT include CriticMarkup tags like
{++or{--in yournew_text. The engine will apply the redline tracking automatically." - Mind the Overlap Constraint: Adeu's engine strictly prevents
modify(text-replace) edits from overlapping with or targeting text that is already inside a pending tracked change. Instruct the LLM: "You cannotmodifytext that is wrapped in counterparty tracking markup. You mustacceptorrejecttheir change using its ID."
🤖 AI Agent Tool Setup: $fromAI Recipes
When wiring this node into an AI Agent as a tool, n8n auto-generates $fromAI() expressions for AI-bindable fields. The second argument of $fromAI is the only per-parameter schema description the LLM actually receives — but n8n does not propagate node-source description metadata into that slot. Auto-generated stubs look like:
{{ $fromAI('Changes__JSON_', ``, 'string') }}The empty backticks mean the LLM sees no schema for that field and will hallucinate the structure.
To apply any recipe below: Open the tool node → click the target field → disable "Let the model define this parameter" (it locks the field to n8n's auto-generated empty-description stub) → switch to Expression mode → paste the recipe. The $fromAI() call inside your expression still binds the field to the LLM — you're just bypassing the auto-stub so you can supply a richer schema description.
Stub caching gotcha: Once a
$fromAIexpression is saved into a workflow, n8n caches it permanently in that workflow's JSON. Updating this package does not retroactively update expressions in existing workflows — you must hand-edit them or delete and re-add the tool node.
What you do NOT bind to the LLM
Some fields are plumbing — they configure which input/output port the node uses, not semantic content. Plumbing fields belong in the node editor; binding them to $fromAI lets the LLM produce confusing errors unrelated to the user's actual request.
Set these manually in the node editor — do NOT use $fromAI:
Document Source(fromInputvsfromNode) — workflow topology decision.Input Binary Property— wiring decision; downstream of the source node, not per-call.Output Binary Property(Apply Edits, Finalize Document) — names where the outgoing binary lands on the workflow item. Downstream nodes need this fixed; an LLM pickingoutput_dataone call andresultthe next would break the pipeline. Default'data'is almost always correct.Edits Source(Apply Edits) — controls whether the node reads the changes array from theChanges (JSON)field on the node itself (defineBelow) or from a property on the upstream item (fromInputJson). For AI Agent workflows, set this toDefine Belowin the editor. This is what activates theChanges (JSON)field as the LLM's entry point — the recipe in the Apply Edits section below populates that field via$fromAI, and the LLM hands its generatedChanges_JSONstring directly to the tool as a call argument. ThefromInputJsonbranch is only for deterministic pipelines where an upstream non-AI node has pre-populated achangesproperty on the item.
AI Agents cannot pass binary .docx data through JSON arguments anyway — that's why fromNode exists: it resolves the binary from a named upstream node (e.g. Read Binary File, Gmail Trigger) at execution time. The trigger source is $fromAI-bindable below because a system prompt can legitimately offer the LLM a choice between multiple binary-producing nodes.
Extract Markdown
Source Node Name (when Document Source is From Another Node):
={{ $fromAI('Source_Node_Name', `Exact name of the workflow node that produced the .docx binary (string, case-sensitive, e.g. 'Read Binary File' or 'Gmail Trigger'). Must match the node label in the canvas exactly. If your system prompt specifies which node holds the document, always use that name.`, 'string', 'Read Binary File') }}Source Binary ID (when Document Source is From Another Node):
={{ $fromAI('Source_Binary_Id', `Optional string. If you are inspecting a document that you have already modified during this conversation, pass the 'redlinedBinaryId' from the previous tool output here to view the updated draft. Leave empty on the first call to load from the baseline node name.`, 'string', '') }}Clean View:
={{ $fromAI('Clean_View', `Boolean. Set false (default) to surface all pending tracked changes as CriticMarkup tags {++ins++}, {--del--}, {>>comment<<} — use when reviewing counterparty edits or any document with pending markup. Set true to project the document as if all tracked changes were accepted (simulates Accept All) — use only when generating net-new redlines against a clean baseline.`, 'boolean', false) }}Apply Edits
Source Node Name (when Document Source is From Another Node):
={{ $fromAI('Source_Node_Name', `Exact name of the workflow node that produced the .docx binary (string, case-sensitive). Must match the node label in the canvas exactly. If your system prompt specifies which node holds the document, always use that name.`, 'string', 'Read Binary File') }}Source Binary ID (when Document Source is From Another Node):
={{ $fromAI('Source_Binary_Id', `Optional string. If you are doing consecutive edits on the same document during this conversation, pass the 'redlinedBinaryId' from the previous tool output here to continue editing the updated draft. Leave blank on your first tool call.`, 'string', '') }}Author:
={{ $fromAI('Author', `Author name attached to every tracked change and comment produced by this batch (string, e.g. 'AI Reviewer' or 'Acme Legal AI'). Appears in Word's review pane as the author of every redline. Choose a name your end users will recognize as the AI reviewer.`, 'string', 'Adeu AI') }}Changes (JSON):
={{ $fromAI('Changes_JSON', `JSON-encoded string containing an array of DocumentChange objects. Each object is one of: {"type":"modify","target_text":"<verbatim from source>","new_text":"<replacement>","comment":"<optional>"} | {"type":"accept","target_id":"Chg:12","comment":"<optional>"} | {"type":"reject","target_id":"Chg:12","comment":"<optional>"} | {"type":"reply","target_id":"Com:45","text":"<reply>"} | {"type":"insert_row","target_text":"<cell text anchoring row>","position":"above" or "below","cells":["col1","col2"]} | {"type":"delete_row","target_text":"<cell text anchoring row>"}. RULES: target_text must be copied VERBATIM from the source including punctuation/whitespace/case and must uniquely anchor one location; never include CriticMarkup tags like {++ or {-- in new_text — the engine applies tracking automatically; use Chg:N and Com:N IDs exactly as surfaced by extract_markdown; the entire array must be a single JSON-encoded string. Atomic batch: if any single edit is invalid the whole array is rejected with an error telling you which edit failed — use that to self-correct on the next call.`, 'string') }}Return Markdown Output:
={{ $fromAI('Return_Markdown', `Boolean. When true (default), the tool returns the post-edit document as Markdown with CriticMarkup so you can verify what changed and reason about follow-up edits. Set false only to skip extraction when you are confident no follow-up review is needed.`, 'boolean', true) }}Dry Run:
={{ $fromAI('Dry_Run', `Boolean. Default false (commit edits to the document). Set true ONLY as a self-correction primitive when you are uncertain that a target_text anchor is unique or matches the source verbatim, OR when you want to preview a complex multi-edit batch before committing. A dry run validates and simulates every edit and returns a per-edit report containing 'status' (applied/failed), 'critic_markup' (a ~30-char CriticMarkup preview showing exactly where the change would land), 'clean_text' (the post-accept preview), 'warning' (punctuation/tokenization hints), and 'error' (if the edit failed) — but the document is NOT modified, no redlined binary is produced, and no static-data stash is written. Invalid edits in a dry run come back as failed entries in the array rather than throwing a BatchValidationError, so dry-run is also how you probe whether an edit will succeed without aborting the batch. After inspecting the preview, re-call with Dry_Run=false to commit. Do NOT default to true — every dry run is an extra round trip that costs the user time and tokens.`, 'boolean', false) }}Generate Diff
Original Source Node Name (when Original Document Source is From Another Node):
={{ $fromAI('Original_Source_Node_Name', `Exact name of the workflow node that produced the baseline (before) .docx binary (string, case-sensitive). Must match the node label exactly.`, 'string') }}Modified Source Node Name (when Modified Document Source is From Another Node):
={{ $fromAI('Modified_Source_Node_Name', `Exact name of the workflow node that produced the modified (after) .docx binary (string, case-sensitive). Must match the node label exactly. Must reference a different node from the original source — otherwise the diff will be empty.`, 'string') }}Clean View:
={{ $fromAI('Clean_View', `Boolean. Set true (recommended default) to compare the Accept All clean view of both documents — diffs reflect final content as if all tracked changes were accepted. Set false to diff the raw CriticMarkup-projected text including pending change markers — useful for auditing tracked-change differences themselves.`, 'boolean', true) }}Finalize Document
Source Node Name (when Document Source is From Another Node):
={{ $fromAI('Source_Node_Name', `Exact name of the workflow node that produced the .docx binary (string, case-sensitive). Must match the node label exactly.`, 'string', 'Read Binary File') }}Source Binary ID (when Document Source is From Another Node):
={{ $fromAI('Source_Binary_Id', `Optional string. If you are finalizing a document that has been consecutively edited during this loop, pass the 'redlinedBinaryId' from your last tool execution here. Leave blank to sanitize the original baseline file.`, 'string', '') }}Sanitize Mode:
={{ $fromAI('Sanitize_Mode', `One of 'baseline', 'full', or 'keep-markup'. 'full' (recommended for distribution) strips author metadata, RSIDs, paragraph IDs, and proof errors AND requires all tracked changes to be resolved — pair with Accept_All=true to auto-accept. 'keep-markup' strips metadata but preserves visible tracked changes and comments — use when sending markup for counterparty review; pair with Author_Override to rewrite author names. 'baseline' is minimal cleanup only (RSIDs and proof errors) — leaves tracked changes and metadata intact.`, 'string', 'full') }}Accept All Tracked Changes (only meaningful when Sanitize Mode is full):
={{ $fromAI('Accept_All', `Boolean. Only applies when Sanitize_Mode is 'full'. Set true to auto-accept all pending tracked changes before sanitization. Set false (default) to block finalization and raise an error if any pending tracked changes exist, forcing them to be resolved explicitly. If multiple distinct authors are detected in pending changes when true, the report will include a warning about potential silent smuggles.`, 'boolean', false) }}Author Override (only meaningful when Sanitize Mode is keep-markup):
={{ $fromAI('Author_Override', `Optional string. Only applies when Sanitize_Mode is 'keep-markup'. When set, replaces the author name on every preserved tracked change and comment with this value (e.g. 'Acme Legal'). Leave empty to keep original authors intact.`, 'string', '') }}Protection Mode:
={{ $fromAI('Protection_Mode', `One of 'none' or 'read_only'. 'none' (default) leaves the document unlocked. 'read_only' injects a native Word read-only enforcement flag into settings.xml — Word users see a read-only banner and cannot edit without explicitly unlocking. Use 'read_only' for distribution to signers or counterparties when you want to discourage casual edits.`, 'string', 'none') }}Tip: The default-value (4th) argument of
$fromAI()lets the LLM omit the parameter entirely and fall back to a sensible default. Use defaults aggressively on optional fields so the LLM only has to specify what actually varies per call.
🛠️ Error Handling & Troubleshooting
Because Adeu enforces Atomic Batch Validation, any error in the LLM's JSON will throw a NodeApiError and halt the node. The error message will tell you exactly which edit failed and why.
- "Target text not found": The LLM hallucinated a word, altered the spacing, or the text doesn't exist in the baseline document.
- "Ambiguous match": The LLM used a
target_text(like "the Company") that appears multiple times. The error details will show you the exact occurrences. Advise the LLM to include more surrounding context (e.g., "the Company shall indemnify"). - "Modification targets an active insertion...": The LLM tried to
modifytext that another author is currently tracking. Adeu explicitly blocks this to maintain virtual DOM integrity and clean redline threading. You mustacceptorrejectthat prior change first. - "Read-only elements": The LLM tried to modify structural items like cross-references or footnotes.
Tip: If you are running bulk processing workflows, you can enable n8n's "Continue On Fail" setting on the Apply Edits node. If the LLM generates a flawed batch, n8n will catch the error, output an { "error": "..." } JSON object for that specific document, and continue processing the rest of the files in your queue.
