Skip to content

feat(plugins): dashboard UI for plugin management - [deferred to v4.0.0-rc1] #2386

Open
oyi77 wants to merge 2 commits into
diegosouzapw:release/v3.8.0from
oyi77:feat/plugin-dashboard-ui
Open

feat(plugins): dashboard UI for plugin management - [deferred to v4.0.0-rc1] #2386
oyi77 wants to merge 2 commits into
diegosouzapw:release/v3.8.0from
oyi77:feat/plugin-dashboard-ui

Conversation

@oyi77
Copy link
Copy Markdown
Contributor

@oyi77 oyi77 commented May 18, 2026

Summary

Adds plugin management dashboard UI (Wave 4 of #2060).

Depends on: #2385 (plugin backend core)

What's included:

  • /dashboard/plugins — plugin list with install, activate/deactivate, uninstall, scan
  • /dashboard/plugins/[name]/config — auto-generated config form from plugin's configSchema

Test plan

  • Dashboard loads at /dashboard/plugins
  • Plugin list displays correctly
  • Install from path works
  • Scan directory works
  • Activate/deactivate toggle works
  • Uninstall with confirmation works
  • Config page renders form from schema
  • Config save works

@oyi77 oyi77 requested a review from diegosouzapw as a code owner May 18, 2026 22:49
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a plugin system for OmniRoute, featuring lifecycle management, sandboxed execution via Node.js VM, and a dashboard UI. The review highlights critical issues in the plugin loader regarding ESM compatibility and unawaited promises, as well as performance overhead from dynamic imports in the chat core. Additionally, the changes violate repository standards regarding test coverage and centralized Zod schema placement. Feedback also suggests improving API request parsing and the plugin scanner's synchronization logic.

Comment thread src/lib/plugins/loader.ts
// Only allow specific safe modules
const allowed: Record<string, unknown> = {};
if (id === "crypto") {
allowed.crypto = require("crypto");
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.

high

Using require("crypto") will fail if the host environment is running in ESM mode (which is the case for this project). You should use the imported crypto module instead.

Suggested change
allowed.crypto = require("crypto");
allowed.crypto = crypto;

Comment thread src/lib/plugins/loader.ts
Comment on lines +114 to +117
vm.runInContext(wrapped, context, {
filename: entryPoint,
timeout: 10000, // 10s init timeout
});
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.

high

The wrapped source is an async function IIFE, which returns a Promise. vm.runInContext returns this promise, but it is not awaited. Consequently, the code following this block will execute before the plugin has finished initializing its exports, which will likely result in hooks being undefined when checked on line 134.

Suggested change
vm.runInContext(wrapped, context, {
filename: entryPoint,
timeout: 10000, // 10s init timeout
});
await vm.runInContext(wrapped, context, {
filename: entryPoint,
timeout: 10000, // 10s init timeout
});


// ── Plugin onRequest hook ──
try {
const { runOnRequest } = await import("@/lib/plugins/index");
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.

medium

Dynamic imports inside the main request handler (handleChatCore) introduce unnecessary overhead on every request. Consider moving this to a top-level import or implementing a lazy-loading pattern that caches the module after the first load to improve performance in this hot path.

} catch (error) {
// ── Plugin onError hook ──
try {
const { runOnError } = await import("@/lib/plugins/index");
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.

medium

Dynamic import of the plugin module inside the error handler. Similar to the onRequest hook, this should be optimized to avoid per-request overhead.

Comment thread src/lib/plugins/loader.ts
*/

import { readFile } from "fs/promises";
import * as vm from "vm";
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.

medium

Import the crypto module here to avoid using require inside the sandbox helper, which is incompatible with ESM environments.

Suggested change
import * as vm from "vm";
import * as vm from "vm";
import * as crypto from "crypto";

* Install a plugin from a source directory.
* Copies to plugin dir, validates manifest, registers in DB.
*/
async install(sourceDir: string): Promise<PluginRow> {
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.

medium

This PR adds significant new production logic for plugin management but lacks any associated tests. Per repository rule 2.9, all changes to production code must include tests to maintain quality and coverage.

References
  1. Always include tests when changing production code (src/, open-sse/, electron/, bin/). (link)

* Scan plugin directory and sync with DB.
* Discovers new plugins and marks missing ones.
*/
async scan(): Promise<{ discovered: number; errors: Array<{ name: string; error: string }> }> {
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.

medium

The scan() method only discovers new plugins and adds them to the database. It does not handle syncing existing plugins (e.g., updating metadata if the manifest changed) or marking plugins as missing/error if their directory was deleted from the filesystem. This could lead to stale data in the dashboard.

// ── Full manifest ──

export const PluginManifestSchema = z.object({
name: z
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.

medium

Plugin manifest schemas are defined locally in this file. According to repository rule 2.8, all Zod schemas used for input validation should be centralized in src/shared/validation/schemas.ts.

References
  1. Always validate inputs with Zod schemas from src/shared/validation/schemas.ts. (link)

*/
export async function PUT(request: NextRequest, { params }: { params: Promise<{ name: string }> }) {
const { name } = await params;
const body = await request.json();
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.

medium

Directly awaiting request.json() without error handling will cause a 500 error if the request body is empty or invalid JSON. It should be wrapped in a try-catch block to return a 400 Bad Request instead.

Suggested change
const body = await request.json();
let body;
try {
body = await request.json();
} catch (e) {
return NextResponse.json({ error: "Invalid JSON body" }, { status: 400, headers: CORS_HEADERS });
}

@oyi77
Copy link
Copy Markdown
Contributor Author

oyi77 commented May 18, 2026

Review feedback addressed in #2385 commit 9fb6d52. This PR inherits those fixes.

@diegosouzapw diegosouzapw changed the title feat(plugins): dashboard UI for plugin management feat(plugins): dashboard UI for plugin management - [deferred to v4.0.0-rc1] May 19, 2026
@diegosouzapw
Copy link
Copy Markdown
Owner

Keeping this open alongside #2385 for the v4.0.0-rc1 milestone — the dashboard UI for plugin management will be integrated together with the rest of the plugin suite. Thanks @oyi77!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants