Skip to content

feat(node): initialize with dynamic plugins#366

Merged
rapids-bot[bot] merged 13 commits into
NVIDIA:mainfrom
bbednarski9:bbednarski/node-dynamic-plugin-host
Jul 14, 2026
Merged

feat(node): initialize with dynamic plugins#366
rapids-bot[bot] merged 13 commits into
NVIDIA:mainfrom
bbednarski9:bbednarski/node-dynamic-plugin-host

Conversation

@bbednarski9

@bbednarski9 bbednarski9 commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Overview

This adds an owned Node binding for explicit native and worker dynamic-plugin activation. The shared process-wide activation lifecycle landed in #364, and discovery-aware configuration layering landed in #418. This PR now contains only the Node binding and its tests.

Static-only harness-native applications keep using plugin.initialize() / plugin.clear(). initializeWithDynamicPlugins(...) requires at least one dynamic specification. For harness-native activation, the supplied config is layered over discovered plugins.toml configuration; static components from that effective config initialize before components appended by the dynamic plugins.

  • I confirm this contribution is my own work, or I have the right to submit it under this project's license.
  • I searched existing issues and open pull requests, and this does not duplicate existing work.

Details

  • Add initializeWithDynamicPlugins(config, specs) with a dedicated JavaScript/TypeScript specification shape for native and worker plugins.
  • Return an owned activation exposing report, active, async close(), and [Symbol.asyncDispose]() for deterministic await using cleanup.
  • Define active as handle state: it becomes false when teardown begins and does not by itself guarantee that process-wide ownership has been released after a teardown failure.
  • Share one detached close operation across repeated and concurrent callers; every caller observes the same teardown result.
  • Run cleanup off the JavaScript thread and retain defensive finalization when callers omit explicit close.
  • Preserve existing static plugin.initialize() and plugin.clear() behavior. Empty dynamic activation is rejected without claiming process ownership, so static initialization remains available.
  • Keep complete core validation, manifest, load, ownership, and teardown diagnostics in rejected promises.
  • Add neutral native and worker fixture coverage for discovered and explicit static configuration, managed LLM/tool callbacks, callback absence after close, partial-load rollback, conflicts, repeated/concurrent close, structured disposal, cancellation-safe cleanup, and garbage collection.

Where should the reviewer start?

Start with crates/node/src/api/mod.rs for N-API ownership and cleanup, then crates/node/plugin.d.ts and crates/node/plugin.js for the initializeWithDynamicPlugins public contract and deliberate absence of an old-name alias. The lifecycle cases are in crates/node/tests/dynamic_plugin_tests.mjs.

Validation

  • npm run build-debug
  • Dynamic-plugin Node tests — 9 passed
  • Full Node suite — 263 passed
  • npm run check:docstrings --workspace crates/node
  • cargo fmt --all -- --check
  • cargo test -p nemo-relay-node
  • cargo clippy -p nemo-relay-node --all-targets -- -D warnings
  • Generated native/wrapper export consistency and stale-name checks passed
  • Changed-file Prettier checks passed
  • git diff --check

Related Issues: (use one of the action keywords Closes / Fixes / Resolves / Relates to)

Relates to #364.
Relates to #418.

Summary by CodeRabbit

  • New Features

    • Added a Node.js API to activate and manage dynamically resolved plugins (initializeWithDynamicPlugins).
    • Supports native and worker dynamic plugins with per-plugin identifiers, optional environment targeting, and per-plugin JSON config.
    • Returns an owned activation handle exposing active status and a configuration validation report.
    • Provides deterministic, idempotent lifecycle teardown via close() and async disposal (Symbol.asyncDispose).
  • Bug Fixes

    • Improved dynamic plugin cleanup for idempotent and concurrent shutdown, including recovery after rejected activations.
  • Tests

    • Added comprehensive dynamic plugin host tests covering lifecycle, disposal, concurrency, and finalization behavior (native + worker).

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Adds a Node API for explicitly activating dynamic Rust and worker plugins, returning an owned async-disposable handle with reports, active state, and deterministic teardown. N-API bindings implement lifecycle management, while tests cover routing, errors, disposal, concurrency, and garbage collection.

Changes

Node dynamic plugin activation

Layer / File(s) Summary
Node activation API contract
crates/node/plugin.d.ts, crates/node/plugin.js
Defines dynamic activation specs and owned activation handles, adds async-disposable typings, and exports initializeWithDynamicPlugins.
Native activation lifecycle
crates/node/src/api/mod.rs
Adds N-API activation and spec conversion, validation reports, active-state access, asynchronous idempotent teardown, async disposal, and finalizer handling.
Activation lifecycle validation
crates/node/tests/dynamic_plugin_tests.mjs
Builds native and worker fixtures and tests activation, routing, diagnostics, explicit and concurrent closure, async disposal, rejected activation recovery, and garbage-collection finalization.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant NodeCaller
  participant initializeWithDynamicPlugins
  participant CorePluginHostActivation
  participant DynamicPluginActivation
  NodeCaller->>initializeWithDynamicPlugins: config and activation specs
  initializeWithDynamicPlugins->>CorePluginHostActivation: activate converted specs
  CorePluginHostActivation-->>initializeWithDynamicPlugins: activation and validation report
  initializeWithDynamicPlugins-->>NodeCaller: activation handle
  NodeCaller->>DynamicPluginActivation: close() or Symbol.asyncDispose
  DynamicPluginActivation->>CorePluginHostActivation: asynchronous teardown
  CorePluginHostActivation-->>DynamicPluginActivation: teardown completion
  DynamicPluginActivation-->>NodeCaller: Promise<void>
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title uses valid Conventional Commits format and accurately summarizes the new dynamic plugin initialization API.
Description check ✅ Passed The description includes Overview, Details, reviewer start, checklist items, validation, and related issues, so it is mostly complete.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@github-actions github-actions Bot added size:XL PR is extra large Feature a new feature lang:js PR changes/introduces Javascript/Typescript code lang:rust PR changes/introduces Rust code labels Jul 6, 2026
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

@AteebNoOne AteebNoOne left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks for putting this together! I took a look at the JavaScript bindings and left a quick suggestion on the JSDoc typing to help with intellisense. Looking forward to seeing this land!

Comment thread crates/node/plugin.js Outdated
Comment thread crates/node/plugin.js
@willkill07 willkill07 added this to the 0.6 milestone Jul 6, 2026
@bbednarski9
bbednarski9 force-pushed the bbednarski/node-dynamic-plugin-host branch from a4ecdcc to baa5cd8 Compare July 7, 2026 00:32
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

License Diff

Compared against origin/main.

Lockfile license changes

Lockfile License Changes

Rust

Added

  • None

Removed

  • None

Updated/Changed

  • None

Node

Added

  • None

Removed

  • None

Updated/Changed

  • None

Python

Added

  • None

Removed

  • None

Updated/Changed

  • None
Status output
[license-diff] selected languages: rust, node, python
[license-diff] generating current inventory
[license-diff] current: generating Rust inventory
[license-diff] current: Rust inventory complete (378 packages)
[license-diff] current: generating Node inventory
[license-diff] current: Node inventory complete (363 packages)
[license-diff] current: generating Python inventory
[license-diff] current: Python inventory complete (105 packages)
[license-diff] current inventory complete
[license-diff] checking out base ref origin/main into a temporary worktree
[license-diff] base: generating Rust inventory
[license-diff] base: Rust inventory complete (378 packages)
[license-diff] base: generating Node inventory
[license-diff] base: Node inventory complete (363 packages)
[license-diff] base: generating Python inventory
[license-diff] base: Python inventory complete (105 packages)
[license-diff] base inventory complete
[license-diff] removing temporary base worktree
[license-diff] comparing inventories
[license-diff] rendering Markdown output
[license-diff] done

@bbednarski9
bbednarski9 force-pushed the bbednarski/node-dynamic-plugin-host branch from baa5cd8 to bce713b Compare July 7, 2026 14:17
@bbednarski9
bbednarski9 force-pushed the bbednarski/node-dynamic-plugin-host branch from bce713b to 0931dc0 Compare July 7, 2026 19:55
@bbednarski9
bbednarski9 force-pushed the bbednarski/node-dynamic-plugin-host branch from 0931dc0 to 7f9ac76 Compare July 13, 2026 16:36
@bbednarski9
bbednarski9 marked this pull request as ready for review July 13, 2026 16:49
@bbednarski9
bbednarski9 requested a review from a team as a code owner July 13, 2026 16:49

@mnajafian-nv mnajafian-nv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks, this is in good shape overall. I left one Node-surface comment on the public activation-handle contract.

Comment thread crates/node/plugin.d.ts Outdated

@zhongxuanwang-nv zhongxuanwang-nv left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think overall this is good. Thanks Bryan!

Comment thread crates/node/plugin.js Outdated
@bbednarski9
bbednarski9 force-pushed the bbednarski/node-dynamic-plugin-host branch from 7f9ac76 to 11e2dd4 Compare July 14, 2026 04:16
@github-actions github-actions Bot added size:L PR is large and removed size:XL PR is extra large labels Jul 14, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/node/plugin.js`:
- Around line 84-86: Replace the broad Array<object> annotation for the specs
parameter with a dedicated JSDoc typedef that mirrors the plugin specification
shape declared in plugin.d.ts. Define the typedef near the related activation
API and reference it from the specs parameter so plain-JS consumers receive
IntelliSense and validation before native calls.

In `@crates/node/tests/dynamic_plugin_tests.mjs`:
- Around line 310-325: Update the cleanup logic surrounding the Promise.race and
process.kill calls so errors from the main test operation are not overwritten by
cleanup failures. In the outer finally block, record non-ESRCH process.kill
errors instead of throwing immediately, complete both firstClose and secondClose
cleanup, then propagate the recorded cleanup error only when no earlier error is
already being propagated.
🪄 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: ASSERTIVE

Plan: Enterprise

Run ID: caefdc9d-8feb-4902-b05c-745799c645f1

📥 Commits

Reviewing files that changed from the base of the PR and between 8889361 and 11e2dd4.

📒 Files selected for processing (4)
  • crates/node/plugin.d.ts
  • crates/node/plugin.js
  • crates/node/src/api/mod.rs
  • crates/node/tests/dynamic_plugin_tests.mjs
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: Check / Run
  • GitHub Check: Preview docs
🧰 Additional context used
📓 Path-based instructions (15)
crates/node/**/*.{js,ts,jsx,tsx,json}

📄 CodeRabbit inference engine (.agents/skills/test-node-binding/SKILL.md)

Format changed Node files with npm run format --workspace=nemo-relay-node

Files:

  • crates/node/plugin.d.ts
  • crates/node/plugin.js
crates/node/**/*.{ts,tsx,d.ts}

📄 CodeRabbit inference engine (.agents/skills/test-node-binding/SKILL.md)

Use npm run check:docstrings --workspace=nemo-relay-node to validate public API docstring checks when surface docs changed

Files:

  • crates/node/plugin.d.ts
crates/node/**/*.{js,mjs,cjs,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use camelCase for Node.js public APIs.

Files:

  • crates/node/plugin.d.ts
  • crates/node/plugin.js
  • crates/node/tests/dynamic_plugin_tests.mjs
**/*.{rs,py,js,mjs,cjs,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{rs,py,js,mjs,cjs,ts,tsx}: Use Json = serde_json::Value in Rust-facing runtime APIs where the existing code expects JSON payloads.
Use Result<T> with FlowError in core runtime paths, and keep errors explicit and binding-appropriate at the wrapper layer.
Keep async behavior on the existing tokio-based model; bindings should preserve callback and future lifetimes rather than blocking or hiding async work unexpectedly.

Files:

  • crates/node/plugin.d.ts
  • crates/node/plugin.js
  • crates/node/src/api/mod.rs
  • crates/node/tests/dynamic_plugin_tests.mjs
**/*.{rs,py,go,js,ts,c,h}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Use language-appropriate naming conventions: Rust snake_case, C FFI exports prefixed nemo_relay_, Go PascalCase, Node.js camelCase, and Python snake_case.

Files:

  • crates/node/plugin.d.ts
  • crates/node/plugin.js
  • crates/node/src/api/mod.rs
**/*.{rs,go,js,ts}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Add the SPDX license header to all Rust, Go, JavaScript, and TypeScript source files using the corresponding // comment form.

Files:

  • crates/node/plugin.d.ts
  • crates/node/plugin.js
  • crates/node/src/api/mod.rs
**/*.{py,go,js,ts}

📄 CodeRabbit inference engine (.agents/skills/maintain-observability/SKILL.md)

Keep Python, Go, and Node.js config objects and subscriber/exporter methods aligned so all bindings expose the same logical knobs and semantics.

Files:

  • crates/node/plugin.d.ts
  • crates/node/plugin.js
**/*

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

**/*: Format changed files with the language-native formatter before the final lint/test pass.
If dynamic plugin behavior changed, use maintain-dynamic-plugins and include the native SDK, worker protocol, Python SDK, docs, packaging, and Codecov surfaces in the validation plan.
If code changes alter APIs, bindings, commands, paths, packaging behavior, observability/adaptive semantics, or documented best practices, update any dependent maintainer or consumer skills in the same branch.
During iteration, prefer uv run pre-commit run --files <changed files...>.
Before review or handoff, run uv run pre-commit run --all-files.

Files:

  • crates/node/plugin.d.ts
  • crates/node/plugin.js
  • crates/node/src/api/mod.rs
  • crates/node/tests/dynamic_plugin_tests.mjs
**/*.{rs,py,go,js,ts}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

If a language surface changed, always run that language's test target even when Rust core did not change.

Files:

  • crates/node/plugin.d.ts
  • crates/node/plugin.js
  • crates/node/src/api/mod.rs
crates/{python,ffi,node}/**/*

⚙️ CodeRabbit configuration file

crates/{python,ffi,node}/**/*: Treat binding changes as public API changes. Check for parity with the other language bindings, FFI ownership/lifetime safety,
callback error propagation, stable type conversion, and consistent async/stream semantics.
Flag changes that update one binding without corresponding tests or documentation for the same surface elsewhere.

Files:

  • crates/node/plugin.d.ts
  • crates/node/plugin.js
  • crates/node/src/api/mod.rs
  • crates/node/tests/dynamic_plugin_tests.mjs
**/*.rs

📄 CodeRabbit inference engine (.agents/skills/prepare-pr/SKILL.md)

**/*.rs: Any Rust change must run just test-rust
Any Rust change must run cargo fmt --all
Any Rust change must run cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Run cargo fmt --all for all FFI work since it is Rust work
Run just test-rust to validate FFI changes
Run cargo clippy --workspace --all-targets -- -D warnings to enforce strict linting on FFI work

When Rust files changed as part of Go work, also run cargo fmt --all, just test-rust, and cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Run cargo fmt --all when Rust files are changed as part of Node work
Run cargo clippy --workspace --all-targets -- -D warnings when Rust files are changed as part of Node work
Run just test-rust when Rust files are changed as part of Node work

When changing the core Rust runtime or Rust-facing API surface, format Rust code with cargo fmt (rustfmt defaults), keep cargo clippy -- -D warnings clean, and satisfy cargo deny check per deny.toml.

**/*.rs: If any Rust code changed, always run just test-rust.
If any Rust code changed, also run cargo fmt --all.
If any Rust code changed, also run cargo clippy --workspace --all-targets -- -D warnings.
For Rust changes headed for review, run cargo fmt --all and cargo clippy --workspace --all-targets -- -D warnings even if relying on pre-commit.

Files:

  • crates/node/src/api/mod.rs
**/*.{rs,py}

📄 CodeRabbit inference engine (AGENTS.md)

Follow binding naming conventions in Rust and Python: use snake_case.

Files:

  • crates/node/src/api/mod.rs
{crates/python/src/py_api/mod.rs,python/nemo_relay/**/*.py,python/nemo_relay/**/*.pyi,go/nemo_relay/**/*.go,crates/node/src/api/**/*.rs}

📄 CodeRabbit inference engine (.agents/skills/add-binding-feature/SKILL.md)

Update the language-native bindings for every exposed surface in Python, Go, and Node.js.

Files:

  • crates/node/src/api/mod.rs
{crates/**/src/**/*.rs,python/**/*.py}

📄 CodeRabbit inference engine (.agents/skills/maintain-dynamic-plugins/SKILL.md)

Do not add tests under src; Rust tests belong in crate tests/ trees, and Python SDK tests belong under python/tests.

Files:

  • crates/node/src/api/mod.rs
{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}

⚙️ CodeRabbit configuration file

{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}: Tests should cover the behavior promised by the changed API surface, including error paths and cross-request isolation where relevant.
Prefer assertions on lifecycle events, scope stacks, middleware ordering, and binding parity over shallow smoke tests.

Files:

  • crates/node/tests/dynamic_plugin_tests.mjs
🧠 Learnings (1)
📚 Learning: 2026-05-07T18:04:44.387Z
Learnt from: mnajafian-nv
Repo: NVIDIA/NeMo-Flow PR: 67
File: integrations/openclaw/src/modules.ts:1-2
Timestamp: 2026-05-07T18:04:44.387Z
Learning: In NVIDIA/NeMo-Flow, TypeScript source files should use `//` line comments for SPDX headers (e.g., `// SPDX-FileCopyrightText: ...` and `// SPDX-License-Identifier: ...`) rather than C-style block comments (`/* ... */`). The repo’s copyright checker enforces this mapping, so `//` SPDX headers in `.ts` files should not be flagged as a style violation.

Applied to files:

  • crates/node/plugin.d.ts
🪛 Biome (2.5.3)
crates/node/tests/dynamic_plugin_tests.mjs

[error] 321-321: Unsafe usage of 'throw'.

(lint/correctness/noUnsafeFinally)

🔇 Additional comments (7)
crates/node/plugin.js (2)

91-93: 🎯 Functional Correctness

Verify base plugins.toml isn't silently skipped, matching the PR-365 pattern.

A past reviewer flagged this exact spot as resembling a bug in PR-365 where a code path skipped loading the base plugins.toml. activateDynamicPlugins requires callers to pass config explicitly with no mention of default-manifest discovery; please confirm this is intentional and matches how other bindings (Python/Go) and the CLI handle the base config for dynamic activation, so Node doesn't diverge in a way that silently drops static plugins.

#!/bin/bash
rg -n -i 'plugins\.toml' --type-not lock -g '!target' -g '!node_modules'

178-178: LGTM!

crates/node/plugin.d.ts (1)

48-72: LGTM!

Also applies to: 325-340

crates/node/src/api/mod.rs (3)

49-52: LGTM!

Also applies to: 4014-4038, 4149-4170, 4172-4213, 4215-4244


4014-4038: 🗄️ Data Integrity & Integration

Confirm Python/Go binding parity for this new activation-spec surface.

NodeDynamicPluginActivationSpec introduces a new Node-facing shape for DynamicPluginActivationSpec. This PR's file set doesn't include Python (crates/python/src/py_api/mod.rs, python/nemo_relay/**) or Go (go/nemo_relay/**) counterparts. Per the binding-parity path instruction, please confirm an equivalent surface is planned/tracked for those bindings (even if sequenced in a later stacked PR) so they don't silently diverge.

As per path instructions, "Treat binding changes as public API changes. Check for parity with the other language bindings... Flag changes that update one binding without corresponding tests or documentation for the same surface elsewhere."


4084-4147: 🎯 Functional Correctness

No issue: this workspace targets edition 2024 and pins Rust 1.93.0, so if log_finalizer_error && let Err(error) = &result is supported.

			> Likely an incorrect or invalid review comment.
crates/node/tests/dynamic_plugin_tests.mjs (1)

1-181: LGTM!

Also applies to: 183-195, 197-291, 333-474

Comment thread crates/node/plugin.js Outdated
Comment thread crates/node/tests/dynamic_plugin_tests.mjs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
crates/node/tests/dynamic_plugin_tests.mjs (1)

351-384: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Move the throw statements outside finally to fully satisfy Biome's noUnsafeFinally.

The error-priority logic is now correct (operation error wins over cleanup error), but Biome still flags lines 380 and 383 since the throws are syntactically inside finally. Since the try/catch already fully absorbs any operation error before reaching finally, nothing else can throw past it — the two throw statements can be hoisted to just after the try/finally block with no behavior change, clearing the lint error outright.

🧹 Proposed fix to satisfy Biome without changing behavior
-        if (operationFailed) {
-          throw operationError;
-        }
-        if (cleanupFailed) {
-          throw cleanupError;
-        }
+      }
+      if (operationFailed) {
+        throw operationError;
+      }
+      if (cleanupFailed) {
+        throw cleanupError;
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/node/tests/dynamic_plugin_tests.mjs` around lines 351 - 384, Move the
operation and cleanup error throws out of the finally block in the surrounding
Promise.race test flow, placing them immediately after the try/finally
completes. Preserve the existing priority where operationError is thrown before
cleanupError, while keeping cleanup and error capture inside finally unchanged.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@crates/node/tests/dynamic_plugin_tests.mjs`:
- Around line 351-384: Move the operation and cleanup error throws out of the
finally block in the surrounding Promise.race test flow, placing them
immediately after the try/finally completes. Preserve the existing priority
where operationError is thrown before cleanupError, while keeping cleanup and
error capture inside finally unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 65a92e1d-c046-46bb-accf-71032a24b7f6

📥 Commits

Reviewing files that changed from the base of the PR and between a09bf25 and a754d2c.

📒 Files selected for processing (3)
  • crates/node/plugin.d.ts
  • crates/node/src/api/mod.rs
  • crates/node/tests/dynamic_plugin_tests.mjs
📜 Review details
🧰 Additional context used
📓 Path-based instructions (15)
crates/node/**/*.{js,ts,jsx,tsx,json}

📄 CodeRabbit inference engine (.agents/skills/test-node-binding/SKILL.md)

Format changed Node files with npm run format --workspace=nemo-relay-node

Files:

  • crates/node/plugin.d.ts
crates/node/**/*.{ts,tsx,d.ts}

📄 CodeRabbit inference engine (.agents/skills/test-node-binding/SKILL.md)

Use npm run check:docstrings --workspace=nemo-relay-node to validate public API docstring checks when surface docs changed

Files:

  • crates/node/plugin.d.ts
crates/node/**/*.{js,mjs,cjs,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use camelCase for Node.js public APIs.

Files:

  • crates/node/plugin.d.ts
  • crates/node/tests/dynamic_plugin_tests.mjs
**/*.{rs,py,js,mjs,cjs,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{rs,py,js,mjs,cjs,ts,tsx}: Use Json = serde_json::Value in Rust-facing runtime APIs where the existing code expects JSON payloads.
Use Result<T> with FlowError in core runtime paths, and keep errors explicit and binding-appropriate at the wrapper layer.
Keep async behavior on the existing tokio-based model; bindings should preserve callback and future lifetimes rather than blocking or hiding async work unexpectedly.

Files:

  • crates/node/plugin.d.ts
  • crates/node/tests/dynamic_plugin_tests.mjs
  • crates/node/src/api/mod.rs
**/*.{rs,py,go,js,ts,c,h}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Use language-appropriate naming conventions: Rust snake_case, C FFI exports prefixed nemo_relay_, Go PascalCase, Node.js camelCase, and Python snake_case.

Files:

  • crates/node/plugin.d.ts
  • crates/node/src/api/mod.rs
**/*.{rs,go,js,ts}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Add the SPDX license header to all Rust, Go, JavaScript, and TypeScript source files using the corresponding // comment form.

Files:

  • crates/node/plugin.d.ts
  • crates/node/src/api/mod.rs
**/*.{py,go,js,ts}

📄 CodeRabbit inference engine (.agents/skills/maintain-observability/SKILL.md)

Keep Python, Go, and Node.js config objects and subscriber/exporter methods aligned so all bindings expose the same logical knobs and semantics.

Files:

  • crates/node/plugin.d.ts
**/*

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

**/*: Format changed files with the language-native formatter before the final lint/test pass.
If dynamic plugin behavior changed, use maintain-dynamic-plugins and include the native SDK, worker protocol, Python SDK, docs, packaging, and Codecov surfaces in the validation plan.
If code changes alter APIs, bindings, commands, paths, packaging behavior, observability/adaptive semantics, or documented best practices, update any dependent maintainer or consumer skills in the same branch.
During iteration, prefer uv run pre-commit run --files <changed files...>.
Before review or handoff, run uv run pre-commit run --all-files.

Files:

  • crates/node/plugin.d.ts
  • crates/node/tests/dynamic_plugin_tests.mjs
  • crates/node/src/api/mod.rs
**/*.{rs,py,go,js,ts}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

If a language surface changed, always run that language's test target even when Rust core did not change.

Files:

  • crates/node/plugin.d.ts
  • crates/node/src/api/mod.rs
crates/{python,ffi,node}/**/*

⚙️ CodeRabbit configuration file

crates/{python,ffi,node}/**/*: Treat binding changes as public API changes. Check for parity with the other language bindings, FFI ownership/lifetime safety,
callback error propagation, stable type conversion, and consistent async/stream semantics.
Flag changes that update one binding without corresponding tests or documentation for the same surface elsewhere.

Files:

  • crates/node/plugin.d.ts
  • crates/node/tests/dynamic_plugin_tests.mjs
  • crates/node/src/api/mod.rs
{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}

⚙️ CodeRabbit configuration file

{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}: Tests should cover the behavior promised by the changed API surface, including error paths and cross-request isolation where relevant.
Prefer assertions on lifecycle events, scope stacks, middleware ordering, and binding parity over shallow smoke tests.

Files:

  • crates/node/tests/dynamic_plugin_tests.mjs
**/*.rs

📄 CodeRabbit inference engine (.agents/skills/prepare-pr/SKILL.md)

**/*.rs: Any Rust change must run just test-rust
Any Rust change must run cargo fmt --all
Any Rust change must run cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Run cargo fmt --all for all FFI work since it is Rust work
Run just test-rust to validate FFI changes
Run cargo clippy --workspace --all-targets -- -D warnings to enforce strict linting on FFI work

When Rust files changed as part of Go work, also run cargo fmt --all, just test-rust, and cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Run cargo fmt --all when Rust files are changed as part of Node work
Run cargo clippy --workspace --all-targets -- -D warnings when Rust files are changed as part of Node work
Run just test-rust when Rust files are changed as part of Node work

When changing the core Rust runtime or Rust-facing API surface, format Rust code with cargo fmt (rustfmt defaults), keep cargo clippy -- -D warnings clean, and satisfy cargo deny check per deny.toml.

**/*.rs: If any Rust code changed, always run just test-rust.
If any Rust code changed, also run cargo fmt --all.
If any Rust code changed, also run cargo clippy --workspace --all-targets -- -D warnings.
For Rust changes headed for review, run cargo fmt --all and cargo clippy --workspace --all-targets -- -D warnings even if relying on pre-commit.

Files:

  • crates/node/src/api/mod.rs
**/*.{rs,py}

📄 CodeRabbit inference engine (AGENTS.md)

Follow binding naming conventions in Rust and Python: use snake_case.

Files:

  • crates/node/src/api/mod.rs
{crates/python/src/py_api/mod.rs,python/nemo_relay/**/*.py,python/nemo_relay/**/*.pyi,go/nemo_relay/**/*.go,crates/node/src/api/**/*.rs}

📄 CodeRabbit inference engine (.agents/skills/add-binding-feature/SKILL.md)

Update the language-native bindings for every exposed surface in Python, Go, and Node.js.

Files:

  • crates/node/src/api/mod.rs
{crates/**/src/**/*.rs,python/**/*.py}

📄 CodeRabbit inference engine (.agents/skills/maintain-dynamic-plugins/SKILL.md)

Do not add tests under src; Rust tests belong in crate tests/ trees, and Python SDK tests belong under python/tests.

Files:

  • crates/node/src/api/mod.rs
🧠 Learnings (1)
📚 Learning: 2026-05-07T18:04:44.387Z
Learnt from: mnajafian-nv
Repo: NVIDIA/NeMo-Flow PR: 67
File: integrations/openclaw/src/modules.ts:1-2
Timestamp: 2026-05-07T18:04:44.387Z
Learning: In NVIDIA/NeMo-Flow, TypeScript source files should use `//` line comments for SPDX headers (e.g., `// SPDX-FileCopyrightText: ...` and `// SPDX-License-Identifier: ...`) rather than C-style block comments (`/* ... */`). The repo’s copyright checker enforces this mapping, so `//` SPDX headers in `.ts` files should not be flagged as a style violation.

Applied to files:

  • crates/node/plugin.d.ts
🪛 Biome (2.5.3)
crates/node/tests/dynamic_plugin_tests.mjs

[error] 380-380: Unsafe usage of 'throw'.

(lint/correctness/noUnsafeFinally)


[error] 383-383: Unsafe usage of 'throw'.

(lint/correctness/noUnsafeFinally)

🔇 Additional comments (7)
crates/node/plugin.d.ts (1)

4-5: LGTM!

crates/node/src/api/mod.rs (5)

93-104: LGTM! Correctly patches Symbol.asyncDispose onto the prototype after napi-rs registers exports, and removes the string-named shim — resolves the prior "redundant async_dispose shim" concern (now also verified by the '[Symbol.asyncDispose]' in prototype assertion added in dynamic_plugin_tests.mjs).


4222-4230: LGTM! Doc comment now clarifies the string-named shim is purely for TS declaration generation and is stripped at runtime — addresses the earlier duplicate-shim review feedback.


4247-4268: 🗄️ Data Integrity & Integration

Verify Python/Go binding parity for activate_dynamic_plugins.

This adds a new public dynamic-plugin-activation surface only to the Node binding. Per coding guidelines, exposed surfaces should get corresponding Python (crates/python/src/py_api/mod.rs, python/nemo_relay/**) and Go (go/nemo_relay/**) bindings, and path instructions call for flagging bindings updated without parity elsewhere. Confirm this is intentionally Node-only for this stack (tracked in a later stacked PR) rather than a gap.

As per coding guidelines, "Update the language-native bindings for every exposed surface in Python, Go, and Node.js," and per path instructions, "Flag changes that update one binding without corresponding tests or documentation for the same surface elsewhere."

Sources: Coding guidelines, Path instructions


4098-4161: LGTM! Teardown coordination (spawn + panic-catch + leak-on-spawn-failure fallback), report()/active()/close() getters, the spec From conversion, and the new imports look correct and consistent with the documented ownership/lifetime design.

Also applies to: 4187-4220, 4028-4051, 24-25, 49-52


4139-4141: 🎯 Functional Correctness

This let-chain is supported by the workspace toolchain. The repo pins Rust 1.93.0 and edition 2024, so this syntax compiles here.

			> Likely an incorrect or invalid review comment.
crates/node/tests/dynamic_plugin_tests.mjs (1)

298-298: LGTM! Correctly verifies the string-named [Symbol.asyncDispose] shim was stripped at runtime.

@bbednarski9

Copy link
Copy Markdown
Contributor Author

Review scope follow-up: the binding-parity observations are intentional sequencing, not an omitted surface in this PR. Python dynamic activation is covered by #365 and FFI/Go by #368, both built on the shared core activation API. This PR remains scoped to the Node binding. The base plugins.toml concern is addressed through #418 and consumed here via the discovery-aware activation path.

@willkill07

Copy link
Copy Markdown
Member

/ok to test 553c2d6

@willkill07 willkill07 removed the DO NOT MERGE PR should not be merged; see PR for details label Jul 14, 2026
rapids-bot Bot pushed a commit that referenced this pull request Jul 14, 2026
## Overview

Adds a shared harness-native activation path that layers explicit plugin configuration over the default discovered plugins.toml files before loading dynamic plugins. The existing exact activation path remains available for hosts such as the Relay CLI that already resolved their configuration.

## Details

- Reuse the same one-time config resolution used by static initialization.
- Add PluginHostActivation::activate_with_discovered_config for language and FFI bindings.
- Keep PluginHostActivation::activate exact and free of implicit discovery.
- Validate that a file-configured static component and a dynamic component activate in one owned transaction.

## Validation

- cargo test -p nemo-relay --test native_plugin_integration (28 passed)
- cargo clippy -p nemo-relay --all-targets -- -D warnings
- cargo fmt --all -- --check
- git diff --check

## Related Issues

Relates to #365, #366, and #368.


## Summary by CodeRabbit

* **New Features**
  * Added a plugin activation entrypoint that layers discovered `plugins.toml` configuration with dynamically provided plugin components.
* **Refactor**
  * Improved configuration resolution by centralizing how default/discovered configuration is merged with provided settings before activation.
* **Bug Fixes**
  * Ensured activation cleanup correctly deregisters the discovered static-base plugin kind after clearing, without impacting dynamic registrations.
* **Tests**
  * Added an end-to-end integration test covering combined discovered + dynamic activation and verifying post-clear deregistration.

Authors:
  - Bryan Bednarski (https://github.com/bbednarski9)

Approvers:
  - Will Killian (https://github.com/willkill07)

URL: #418
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
@bbednarski9
bbednarski9 force-pushed the bbednarski/node-dynamic-plugin-host branch from 553c2d6 to 8735e6d Compare July 14, 2026 13:46
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
@bbednarski9 bbednarski9 changed the title feat(node): activate dynamic plugins feat(node): initialize with dynamic plugins Jul 14, 2026
@bbednarski9

Copy link
Copy Markdown
Contributor Author

/merge

@rapids-bot
rapids-bot Bot merged commit 0c567d4 into NVIDIA:main Jul 14, 2026
74 of 76 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feature a new feature lang:js PR changes/introduces Javascript/Typescript code lang:rust PR changes/introduces Rust code size:L PR is large

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants