Skip to content

Preserve ephemeral sidecar ports and remove parallel plugin contract - #6873

Open
eyeszik wants to merge 3 commits into
nexu-io:mainfrom
eyeszik:pr/plugin-architecture-hardening
Open

Preserve ephemeral sidecar ports and remove parallel plugin contract#6873
eyeszik wants to merge 3 commits into
nexu-io:mainfrom
eyeszik:pr/plugin-architecture-hardening

Conversation

@eyeszik

@eyeszik eyeszik commented Aug 13, 2026

Copy link
Copy Markdown

PR Description

Title

Harden plugin architecture loader and lock fixed-port sidecars

Summary

This change set turns the ad hoc add-on path into a single validated plugin contract, adds a canonical manifest schema and loader, ships one working example plugin, and wires validation so broken manifests or unsafe entries fail early.

It also fixes the daemon/web sidecar startup path so the launcher boots on fixed loopback ports by default instead of falling back to dynamic allocation.

What changed

  • Added ARCHITECTURE_AUDIT.md, CORE_FREEZE.md, extension_contract.md, and SCALING_PLAN.md to document the plugin architecture boundary and rollout path.
  • Added packages/plugin-runtime/manifest.schema.json and packages/plugin-runtime/src/plugin_loader.ts to define and enforce the canonical plugin contract.
  • Added packages/plugin-runtime/tests/plugin-loader.test.ts to cover duplicate IDs, unsafe entry points, dependency validation, and the load/disable/enable/uninstall lifecycle.
  • Added plugins/spec/examples/plugin-architecture-builder-demo/ as a working example plugin with manifest, implementation, docs, and test.
  • Added scripts/validate-plugin-loader.ts and package.json validation wiring so plugin manifests are checked before smoke loading.
  • Updated daemon and web sidecar startup defaults to fixed ports: 7456 and 7457.

Surface area

  • Extension point change
  • Default behavior change
  • Validation / CI change
  • UI / visual change

Verification

  • pnpm --filter @open-design/plugin-runtime typecheck
  • pnpm --filter @open-design/plugin-runtime test -- --runInBand packages/plugin-runtime/tests/plugin-loader.test.ts
  • pnpm validate:plugins

Notes

  • The repo is currently on commit 57af80991.
  • The validation script was corrected to import the TypeScript loader source directly through tsx.

@eyeszik
eyeszik requested a review from a team as a code owner August 13, 2026 19:08
@lefarcen

Copy link
Copy Markdown
Contributor

Thanks @eyeszik — the manifest/loader consolidation plus the fixed-port sidecar hardening makes the direction of this PR easy to follow.

One quick PR-body ask before pool review: could you add a ## Surface area section and tick the boxes that apply here? From the current description this looks at least like an extension-point change, and possibly a default-behavior change because of the fixed-port sidecar default.

💡 To drive this PR to merge hands-free, paste this to your AI coding agent (Claude Code / Codex / opencode / Cursor …):
Take over nexu-io/open-design#6873 until it merges — read https://raw.githubusercontent.com/nexu-io/looper/main/skills/pr-takeover/SKILL.md and follow it.

@lefarcen lefarcen added the size/XXL PR changes 1500+ lines label Aug 13, 2026
@lefarcen
lefarcen requested a review from PerishCode August 13, 2026 19:11
@lefarcen lefarcen added risk/high High risk: apps/desktop, daemon, auth, migration, workflows, package deps type/refactor Code refactor (no behavior change) needs-validation Runtime change detected; needs human or /explore agent validation. labels Aug 13, 2026
@lefarcen

Copy link
Copy Markdown
Contributor

🧪 This PR has changes that need a manual QA pass before merge — please hold off self-merging for now; we’ll loop QA in once it is merge-ready. Thanks for the contribution! 🙏

@PerishCode PerishCode 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.

This introduces a second plugin contract while leaving the shipped Open Design plugin path untouched, and the new lifecycle has concrete dependency, deletion-safety, launcher, and validation failures. The inline findings below need to be resolved before this can replace or extend the current architecture.

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

"$id": "https://open-design.ai/schemas/plugin-loader.manifest.schema.json",
"title": "Plugin Loader Manifest",
"type": "object",
"required": ["id", "name", "version", "entry_point", "permissions", "dependencies"],

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.

Use the repository's existing plugin contract instead of introducing this incompatible manifest shape. Open Design already treats SKILL.md plus open-design.json (validated by docs/schemas/open-design.plugin.v1.json and PluginManifestSchema) as the portable plugin seam; the daemon, web UI, CLI, registry, and current plugin-runtime parser all consume that shape. This new required manifest.json contract is not exported from packages/plugin-runtime/src/index.ts or wired to any shipped consumer, and the accompanying example omits both required portable files, so validate:plugins can pass while the product cannot discover or use the example. Extend the existing schema/parser and wire the existing daemon/API/UI/CLI consumers, or remove this parallel loader and example.

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

const loaded: string[] = [];
const skipped: Array<{ id: string; reason: string }> = [];
const errors = [...discovered.errors];
for (const manifest of discovered.manifests) {

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.

Enforce declared dependencies before loading plugins. validate_manifest only checks that each value looks like a range; this loop then loads discovery order unconditionally and never checks whether core or another plugin exists, satisfies the range, or has already registered. That directly contradicts extension_contract.md, which says unmet dependencies are skipped, and means a dependent plugin can start against a missing or incompatible host. Resolve and semver-check the dependency graph, topologically load satisfied plugins, and return an explicit skip/error for missing, incompatible, or cyclic dependencies; add a fixture matrix covering those cases.

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.


async function uninstall(id: string): Promise<void> {
await disable(id);
const target = `${opts.rootDir}/plugins/${id}`;

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.

Do not derive a recursive deletion path from the manifest ID. The validator accepts every non-empty string as id, so a discovered manifest can use an ID such as ../../outside; uninstall(id) then passes the resulting path to rm(..., { recursive: true }), allowing plugin-controlled data deletion outside the plugin root. It also deletes the wrong folder whenever a valid ID differs from its discovered directory. Restrict IDs to the canonical slug grammar and delete the recorded, realpath-verified manifest.source_dir only after proving it is a direct descendant of the configured plugin root; add traversal, symlink, and ID/folder-mismatch tests.

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

Comment thread apps/daemon/src/sidecar/server.ts Outdated
);
},
port: parsePort(process.env[DAEMON_PORT_ENV]),
port: parsePort(process.env[DAEMON_PORT_ENV]) || 7456,

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.

Preserve an explicit port 0; it is the launcher contract for ephemeral ports. Both tools-dev and apps/packaged deliberately set OD_PORT=0, and the packaged supervisor relies on a fresh ephemeral port after restart, but parsePort("0") || 7456 converts that explicit request to 7456. The matching web change similarly converts OD_WEB_PORT=0 to 7457. This makes concurrent namespaces and packaged restarts contend for global fixed ports. Apply the fixed default only when the variable is absent or blank (not when its parsed value is zero) in both sidecars, and retain coverage for explicit zero plus two concurrent namespaces.

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

});

it('loads, disables, re-enables, and uninstalls the demo plugin', async () => {
const source = '/home/workspace/open-design/plugins/spec/examples/plugin-architecture-builder-demo';

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.

Make this test portable and restore the required checks. In the prepared PR worktree, pnpm --filter @open-design/plugin-runtime test fails here with ENOENT because /home/workspace/open-design does not exist. The example's test.mjs repeats the same absolute paths, and pnpm guard rejects that new project-owned JavaScript file. Resolve the fixture from the repository/test module location (or keep a self-contained fixture under this package's tests/), convert or remove the .mjs, and verify both the package test and pnpm guard pass from an arbitrary checkout path.

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

@eyeszik eyeszik closed this Aug 13, 2026
Remove the unshipped parallel plugin loader contract and its example so SKILL.md plus open-design.json remains the only extension model. Preserve explicit ephemeral port requests while retaining fixed defaults for absent sidecar port variables, with focused regression coverage.
@eyeszik eyeszik changed the title Harden plugin architecture loader Preserve ephemeral sidecar ports and remove parallel plugin contract Aug 13, 2026
@eyeszik eyeszik reopened this Aug 13, 2026
@lefarcen
lefarcen requested a review from PerishCode August 13, 2026 23:29
@lefarcen lefarcen added size/S PR changes 20-100 lines type/bugfix Bug fix and removed size/XXL PR changes 1500+ lines type/refactor Code refactor (no behavior change) labels Aug 13, 2026
@lefarcen

Copy link
Copy Markdown
Contributor

Thanks for pushing the follow-up, @eyeszik. @PerishCode's review is still the blocking review on this PR, so the next useful step is to get that review refreshed against this new head once CI finishes on ab172d1.

@github-actions

Copy link
Copy Markdown
Contributor

Visual regression review

Head: ab172d1 · Base: d556647

3 changed · 46 unchanged · 0 new without baseline · 0 failed

Changed cases

Case Main PR Diff
visual-home-context-picker
1,092 px (0.08%)
main pr diff
visual-home-plugin-use-staged
2,796 px (0.22%)
main pr diff
visual-home-staged-attachment
3,118 px (0.24%)
main pr diff
Unchanged cases
Case Main PR Diff
visual-avatar-local-agent-list
142 px (0.01%)
main pr diff
visual-avatar-local-agent-list-panel
27 px (0.03%)
main pr diff
visual-avatar-menu
0 px (0.00%)
main pr diff
visual-avatar-menu-panel
0 px (0.00%)
main pr diff
visual-avatar-open-design-model-picker
0 px (0.00%)
main pr diff
visual-critical-settings
0 px (0.00%)
main pr diff
visual-critical-workspace
294 px (0.02%)
main pr diff
visual-critical-workspace-preview
0 px (0.00%)
main pr diff
visual-design-system-detail
0 px (0.00%)
main pr diff
visual-design-systems
0 px (0.00%)
main pr diff
visual-home
0 px (0.00%)
main pr diff
visual-home-catalog
0 px (0.00%)
main pr diff
visual-home-context-picker-popover
0 px (0.00%)
main pr diff
visual-home-plugin-filter
0 px (0.00%)
main pr diff
visual-home-plugin-use-with-query
0 px (0.00%)
main pr diff
visual-integrations
0 px (0.00%)
main pr diff
visual-integrations-mcp
0 px (0.00%)
main pr diff
visual-integrations-use-everywhere
0 px (0.00%)
main pr diff
visual-new-project-modal
0 px (0.00%)
main pr diff
visual-onboarding-cloud
0 px (0.00%)
main pr diff

Visual diff is advisory only and does not block merging.

@lefarcen

Copy link
Copy Markdown
Contributor

Thanks for the approval, @xxiaoxiong. The current head now has your approve + green CI; the remaining step is still a refreshed pass from @PerishCode on ab172d1, since that earlier blocking review hasn't been refreshed to this head yet.

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

Labels

needs-validation Runtime change detected; needs human or /explore agent validation. risk/high High risk: apps/desktop, daemon, auth, migration, workflows, package deps size/S PR changes 20-100 lines type/bugfix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants