feat: collapse plugin runtime to one tier behind a plugin SDK

This commit is contained in:
LukeGus committed 2026-09-22 14:11:50 -05:00
1 parent 6e058f6e9e
commit 4758016f41
96 files changed
+34631 -8551

No files matched your search

+93 -414
View File
@@ -1,8 +1,9 @@
/**
* The real bundled ssh-terminal plugin, as it ships.
* The bundled plugins, as they ship.
*
* This reads plugins/ in the repo rather than a fixture: the manifest is a
* checked-in artifact, and a typo in it would otherwise only show up at boot.
* This reads plugins/ in the repo rather than a fixture: the manifests are
* checked-in artifacts, and a typo in one would otherwise only show up when
* the server tried to boot.
*/
import { describe, expect, it, vi } from "vitest";
@@ -10,11 +11,7 @@ import fs from "node:fs";
import path from "node:path";
import { getBundledPluginsDir } from "../../plugins/paths.js";
import { parseManifest } from "../../plugins/manifest.js";
import {
isFirstParty,
runsInProcess,
TRANSPORT_OWNER_CAPABILITY,
} from "../../plugins/first-party.js";
import { isKnownCapability } from "@termix/plugin-sdk/capabilities";
vi.mock("../../utils/logger.js", () => ({
pluginLogger: {
@@ -26,443 +23,125 @@ vi.mock("../../utils/logger.js", () => ({
},
}));
function readBundledManifest(pluginId: string): Record<string, unknown> {
const file = path.join(getBundledPluginsDir(), pluginId, "manifest.json");
return JSON.parse(fs.readFileSync(file, "utf8"));
const BUNDLED_DIR = getBundledPluginsDir();
function bundledIds(): string[] {
return fs
.readdirSync(BUNDLED_DIR, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.map((entry) => entry.name)
.filter((id) => fs.existsSync(path.join(BUNDLED_DIR, id, "manifest.json")));
}
describe("bundled ssh-terminal plugin", () => {
it("ships a directory with a manifest and both entry points", () => {
const dir = path.join(getBundledPluginsDir(), "ssh-terminal");
function readManifest(pluginId: string): Record<string, unknown> {
return JSON.parse(
fs.readFileSync(path.join(BUNDLED_DIR, pluginId, "manifest.json"), "utf8"),
);
}
expect(fs.existsSync(path.join(dir, "manifest.json"))).toBe(true);
expect(fs.existsSync(path.join(dir, "backend", "index.mjs"))).toBe(true);
expect(fs.existsSync(path.join(dir, "frontend", "index.mjs"))).toBe(true);
const ids = bundledIds();
describe("bundled plugins", () => {
it("finds the bundled plugin directory", () => {
expect(ids.length).toBeGreaterThan(0);
});
it("has a manifest that passes the real validator", () => {
const { manifest, errors } = parseManifest(
readBundledManifest("ssh-terminal"),
);
it.each(ids)("%s has a manifest that parses", (id) => {
const { manifest, errors } = parseManifest(readManifest(id));
expect(errors).toEqual([]);
expect(manifest?.id).toBe("ssh-terminal");
expect(manifest?.category).toBe("Terminal");
expect(manifest?.id).toBe(id);
});
it("declares the transport-owner capability and qualifies for the tier", () => {
const { manifest } = parseManifest(readBundledManifest("ssh-terminal"));
it.each(ids)("%s declares only catalog capabilities", (id) => {
const { manifest } = parseManifest(readManifest(id));
expect(manifest?.permissions).toContain(TRANSPORT_OWNER_CAPABILITY);
expect(isFirstParty("ssh-terminal")).toBe(true);
expect(runsInProcess("ssh-terminal", manifest!.permissions)).toBe(true);
for (const capability of manifest?.capabilities ?? []) {
expect(isKnownCapability(capability)).toBe(true);
}
});
it("contributes the terminal tab the shell registers", () => {
const { manifest } = parseManifest(readBundledManifest("ssh-terminal"));
const tab = manifest?.contributes?.tabs?.[0];
it.each(ids)("%s ships the entry points its manifest names", (id) => {
const { manifest } = parseManifest(readManifest(id));
const dir = path.join(BUNDLED_DIR, id);
// The shell keys tab content off this id, so it has to stay "terminal".
expect(tab?.id).toBe("terminal");
expect(tab?.openFrom).toContain("rail");
expect(fs.existsSync(path.join(dir, manifest!.backend!))).toBe(true);
expect(fs.existsSync(path.join(dir, manifest!.frontend!))).toBe(true);
});
it("exports activate and deactivate from its backend entry", () => {
const entry = path.join(
getBundledPluginsDir(),
"ssh-terminal",
"backend",
"index.mjs",
);
const source = fs.readFileSync(entry, "utf8");
// process:transport-owner was the v1 marker for the in-process tier. There
// is one tier now, so nothing should still be asking for it.
it.each(ids)("%s does not declare a v1 permission", (id) => {
const raw = readManifest(id);
// A first-party plugin owns a listening port, so deactivate is not
// optional here the way it is for an ordinary plugin.
expect(source).toMatch(/export async function activate/);
expect(source).toMatch(/export async function deactivate/);
expect(raw.permissions).toBeUndefined();
expect(raw.sidecars).toBeUndefined();
expect(JSON.stringify(raw)).not.toContain("transport-owner");
});
});
describe("bundled docker plugin", () => {
it("ships a directory with a manifest and both entry points", () => {
const dir = path.join(getBundledPluginsDir(), "docker");
expect(fs.existsSync(path.join(dir, "manifest.json"))).toBe(true);
expect(fs.existsSync(path.join(dir, "backend", "index.mjs"))).toBe(true);
expect(fs.existsSync(path.join(dir, "frontend", "index.mjs"))).toBe(true);
describe("declared dependencies", () => {
it("names a plugin that is actually bundled", () => {
for (const id of ids) {
const { manifest } = parseManifest(readManifest(id));
for (const dependency of Object.keys(manifest?.dependencies ?? {})) {
expect(ids).toContain(dependency);
}
}
});
it("has a manifest that passes the real validator", () => {
const { manifest, errors } = parseManifest(readBundledManifest("docker"));
// plugins/ai/backend/tools/executor.ts imports automations' routes
// directly, so the loader has to start automations first. B18 replaces the
// import with a service call and this becomes optional.
it("records ai's dependency on automations", () => {
if (!ids.includes("ai")) return;
expect(errors).toEqual([]);
expect(manifest?.id).toBe("docker");
expect(manifest?.category).toBe("Infrastructure");
});
const { manifest } = parseManifest(readManifest("ai"));
it("declares the transport-owner capability and qualifies for the tier", () => {
const { manifest } = parseManifest(readBundledManifest("docker"));
expect(manifest?.permissions).toContain(TRANSPORT_OWNER_CAPABILITY);
expect(isFirstParty("docker")).toBe(true);
expect(runsInProcess("docker", manifest!.permissions)).toBe(true);
});
it("contributes the docker tab the shell registers", () => {
const { manifest } = parseManifest(readBundledManifest("docker"));
const tab = manifest?.contributes?.tabs?.[0];
// The shell keys tab content off this id, so it has to stay "docker".
expect(tab?.id).toBe("docker");
expect(tab?.openFrom).toContain("host-context-menu");
});
it("declares the enableDocker host capability", () => {
const { manifest } = parseManifest(readBundledManifest("docker"));
expect(manifest?.contributes?.hostCapability?.key).toBe("enableDocker");
});
it("exports activate and deactivate from its backend entry", () => {
const entry = path.join(
getBundledPluginsDir(),
"docker",
"backend",
"index.mjs",
);
const source = fs.readFileSync(entry, "utf8");
expect(source).toMatch(/export async function activate/);
expect(source).toMatch(/export async function deactivate/);
expect(manifest?.dependencies?.automations).toBeTruthy();
});
});
describe("bundled host-metrics plugin", () => {
it("ships a directory with a manifest and both entry points", () => {
const dir = path.join(getBundledPluginsDir(), "host-metrics");
describe("plugins that own a port", () => {
// Every one of these had its server created at module scope at some point.
// The rule is that it belongs inside activate, or disable leaves the port
// held and re-enable fails.
it.each(["ssh-terminal", "docker", "host-metrics", "remote-desktop"])(
"%s creates no listener at module scope",
(id) => {
if (!ids.includes(id)) return;
expect(fs.existsSync(path.join(dir, "manifest.json"))).toBe(true);
expect(fs.existsSync(path.join(dir, "backend", "index.mjs"))).toBe(true);
expect(fs.existsSync(path.join(dir, "frontend", "index.mjs"))).toBe(true);
});
const entry = path.join(BUNDLED_DIR, id, "backend", "index.mjs");
const source = fs.readFileSync(entry, "utf8");
const moduleScope = source.slice(
0,
source.indexOf("export async function activate"),
);
it("has a manifest that passes the real validator", () => {
const { manifest, errors } = parseManifest(
readBundledManifest("host-metrics"),
);
expect(moduleScope).not.toMatch(/\.listen\(/);
expect(moduleScope).not.toMatch(/new WebSocketServer/);
},
);
expect(errors).toEqual([]);
expect(manifest?.id).toBe("host-metrics");
expect(manifest?.category).toBe("Monitoring");
});
it("docker does not register a process signal handler", () => {
if (!ids.includes("docker")) return;
it("declares the transport-owner capability and qualifies for the tier", () => {
const { manifest } = parseManifest(readBundledManifest("host-metrics"));
// Core owns shutdown: gracefulShutdown calls shutdownPlugins, which runs
// every plugin's deactivate. A plugin calling process.exit() itself
// skipped the rest of that sequence.
for (const file of ["index.mjs", "console.ts"]) {
const candidate = path.join(BUNDLED_DIR, "docker", "backend", file);
if (!fs.existsSync(candidate)) continue;
expect(manifest?.permissions).toContain(TRANSPORT_OWNER_CAPABILITY);
expect(isFirstParty("host-metrics")).toBe(true);
expect(runsInProcess("host-metrics", manifest!.permissions)).toBe(true);
});
// Comments are stripped first: the rule is worth explaining in prose
// right where it applies, and that prose names the thing it forbids.
const code = fs
.readFileSync(candidate, "utf8")
.replace(/\/\*[\s\S]*?\*\//g, "")
.replace(/^\s*\/\/.*$/gm, "");
it("contributes the host-metrics tab the shell registers", () => {
const { manifest } = parseManifest(readBundledManifest("host-metrics"));
const tab = manifest?.contributes?.tabs?.[0];
// The shell keys tab content off this id, so it has to stay "host-metrics".
expect(tab?.id).toBe("host-metrics");
expect(tab?.openFrom).toContain("host-context-menu");
});
it("exports activate and deactivate from its backend entry", () => {
const entry = path.join(
getBundledPluginsDir(),
"host-metrics",
"backend",
"index.mjs",
);
const source = fs.readFileSync(entry, "utf8");
expect(source).toMatch(/export async function activate/);
expect(source).toMatch(/export async function deactivate/);
});
});
describe("bundled remote-desktop plugin", () => {
it("ships a directory with a manifest and both entry points", () => {
const dir = path.join(getBundledPluginsDir(), "remote-desktop");
expect(fs.existsSync(path.join(dir, "manifest.json"))).toBe(true);
expect(fs.existsSync(path.join(dir, "backend", "index.mjs"))).toBe(true);
expect(fs.existsSync(path.join(dir, "frontend", "index.mjs"))).toBe(true);
});
it("has a manifest that passes the real validator", () => {
const { manifest, errors } = parseManifest(
readBundledManifest("remote-desktop"),
);
expect(errors).toEqual([]);
expect(manifest?.id).toBe("remote-desktop");
expect(manifest?.category).toBe("Terminal");
});
it("declares the transport-owner capability and qualifies for the tier", () => {
const { manifest } = parseManifest(readBundledManifest("remote-desktop"));
expect(manifest?.permissions).toContain(TRANSPORT_OWNER_CAPABILITY);
expect(isFirstParty("remote-desktop")).toBe(true);
expect(runsInProcess("remote-desktop", manifest!.permissions)).toBe(true);
});
it("contributes the rdp/vnc/telnet tabs the shell registers", () => {
const { manifest } = parseManifest(readBundledManifest("remote-desktop"));
const tabIds = manifest?.contributes?.tabs?.map((tab) => tab.id);
expect(tabIds).toEqual(["rdp", "vnc", "telnet"]);
});
it("declares three host capabilities as an array", () => {
const { manifest } = parseManifest(readBundledManifest("remote-desktop"));
const hostCapability = manifest?.contributes?.hostCapability;
expect(Array.isArray(hostCapability)).toBe(true);
expect(
(hostCapability as Array<{ key: string }>).map((entry) => entry.key),
).toEqual(["enableRdp", "enableVnc", "enableTelnet"]);
});
it("exports activate and deactivate from its backend entry", () => {
const entry = path.join(
getBundledPluginsDir(),
"remote-desktop",
"backend",
"index.mjs",
);
const source = fs.readFileSync(entry, "utf8");
expect(source).toMatch(/export async function activate/);
expect(source).toMatch(/export async function deactivate/);
});
});
describe("bundled fleets plugin", () => {
it("ships a directory with a manifest and both entry points", () => {
const dir = path.join(getBundledPluginsDir(), "fleets");
expect(fs.existsSync(path.join(dir, "manifest.json"))).toBe(true);
expect(fs.existsSync(path.join(dir, "backend", "index.mjs"))).toBe(true);
expect(fs.existsSync(path.join(dir, "frontend", "index.mjs"))).toBe(true);
});
it("has a manifest that passes the real validator", () => {
const { manifest, errors } = parseManifest(readBundledManifest("fleets"));
expect(errors).toEqual([]);
expect(manifest?.id).toBe("fleets");
expect(manifest?.category).toBe("Infrastructure");
});
it("declares the transport-owner capability and qualifies for the tier", () => {
const { manifest } = parseManifest(readBundledManifest("fleets"));
expect(manifest?.permissions).toContain(TRANSPORT_OWNER_CAPABILITY);
expect(isFirstParty("fleets")).toBe(true);
expect(runsInProcess("fleets", manifest!.permissions)).toBe(true);
});
it("exports activate and deactivate from its backend entry", () => {
const entry = path.join(
getBundledPluginsDir(),
"fleets",
"backend",
"index.mjs",
);
const source = fs.readFileSync(entry, "utf8");
expect(source).toMatch(/export async function activate/);
expect(source).toMatch(/export async function deactivate/);
});
});
describe("bundled automations plugin", () => {
it("ships a directory with a manifest and both entry points", () => {
const dir = path.join(getBundledPluginsDir(), "automations");
expect(fs.existsSync(path.join(dir, "manifest.json"))).toBe(true);
expect(fs.existsSync(path.join(dir, "backend", "index.mjs"))).toBe(true);
expect(fs.existsSync(path.join(dir, "frontend", "index.mjs"))).toBe(true);
});
it("has a manifest that passes the real validator", () => {
const { manifest, errors } = parseManifest(
readBundledManifest("automations"),
);
expect(errors).toEqual([]);
expect(manifest?.id).toBe("automations");
expect(manifest?.category).toBe("Infrastructure");
});
it("declares the transport-owner capability and qualifies for the tier", () => {
const { manifest } = parseManifest(readBundledManifest("automations"));
expect(manifest?.permissions).toContain(TRANSPORT_OWNER_CAPABILITY);
expect(isFirstParty("automations")).toBe(true);
expect(runsInProcess("automations", manifest!.permissions)).toBe(true);
});
it("exports activate and deactivate from its backend entry", () => {
const entry = path.join(
getBundledPluginsDir(),
"automations",
"backend",
"index.mjs",
);
const source = fs.readFileSync(entry, "utf8");
expect(source).toMatch(/export async function activate/);
expect(source).toMatch(/export async function deactivate/);
});
});
describe("bundled network-topology plugin", () => {
it("ships a directory with a manifest and both entry points", () => {
const dir = path.join(getBundledPluginsDir(), "network-topology");
expect(fs.existsSync(path.join(dir, "manifest.json"))).toBe(true);
expect(fs.existsSync(path.join(dir, "backend", "index.mjs"))).toBe(true);
expect(fs.existsSync(path.join(dir, "frontend", "index.mjs"))).toBe(true);
});
it("has a manifest that passes the real validator", () => {
const { manifest, errors } = parseManifest(
readBundledManifest("network-topology"),
);
expect(errors).toEqual([]);
expect(manifest?.id).toBe("network-topology");
expect(manifest?.category).toBe("Infrastructure");
});
it("declares the transport-owner capability and qualifies for the tier", () => {
const { manifest } = parseManifest(readBundledManifest("network-topology"));
expect(manifest?.permissions).toContain(TRANSPORT_OWNER_CAPABILITY);
expect(isFirstParty("network-topology")).toBe(true);
expect(runsInProcess("network-topology", manifest!.permissions)).toBe(true);
});
it("contributes the network_graph tab the shell registers", () => {
const { manifest } = parseManifest(readBundledManifest("network-topology"));
const tab = manifest?.contributes?.tabs?.[0];
// The shell keys tab content off this id, so it has to stay "network_graph".
expect(tab?.id).toBe("network_graph");
expect(tab?.openFrom).toContain("rail");
});
it("exports activate and deactivate from its backend entry", () => {
const entry = path.join(
getBundledPluginsDir(),
"network-topology",
"backend",
"index.mjs",
);
const source = fs.readFileSync(entry, "utf8");
expect(source).toMatch(/export async function activate/);
expect(source).toMatch(/export async function deactivate/);
});
});
describe("bundled workspaces plugin", () => {
it("ships a directory with a manifest and both entry points", () => {
const dir = path.join(getBundledPluginsDir(), "workspaces");
expect(fs.existsSync(path.join(dir, "manifest.json"))).toBe(true);
expect(fs.existsSync(path.join(dir, "backend", "index.mjs"))).toBe(true);
expect(fs.existsSync(path.join(dir, "frontend", "index.mjs"))).toBe(true);
});
it("has a manifest that passes the real validator", () => {
const { manifest, errors } = parseManifest(
readBundledManifest("workspaces"),
);
expect(errors).toEqual([]);
expect(manifest?.id).toBe("workspaces");
expect(manifest?.category).toBe("Productivity");
});
it("declares the transport-owner capability and qualifies for the tier", () => {
const { manifest } = parseManifest(readBundledManifest("workspaces"));
expect(manifest?.permissions).toContain(TRANSPORT_OWNER_CAPABILITY);
expect(isFirstParty("workspaces")).toBe(true);
expect(runsInProcess("workspaces", manifest!.permissions)).toBe(true);
});
it("exports activate and deactivate from its backend entry", () => {
const entry = path.join(
getBundledPluginsDir(),
"workspaces",
"backend",
"index.mjs",
);
const source = fs.readFileSync(entry, "utf8");
expect(source).toMatch(/export async function activate/);
expect(source).toMatch(/export async function deactivate/);
});
});
describe("bundled web-endpoint plugin", () => {
it("ships a directory with a manifest and both entry points", () => {
const dir = path.join(getBundledPluginsDir(), "web-endpoint");
expect(fs.existsSync(path.join(dir, "manifest.json"))).toBe(true);
expect(fs.existsSync(path.join(dir, "backend", "index.mjs"))).toBe(true);
expect(fs.existsSync(path.join(dir, "frontend", "index.mjs"))).toBe(true);
});
it("has a manifest that passes the real validator", () => {
const { manifest, errors } = parseManifest(
readBundledManifest("web-endpoint"),
);
expect(errors).toEqual([]);
expect(manifest?.id).toBe("web-endpoint");
expect(manifest?.category).toBe("Networking");
});
it("declares the transport-owner capability and qualifies for the tier", () => {
const { manifest } = parseManifest(readBundledManifest("web-endpoint"));
expect(manifest?.permissions).toContain(TRANSPORT_OWNER_CAPABILITY);
expect(isFirstParty("web-endpoint")).toBe(true);
expect(runsInProcess("web-endpoint", manifest!.permissions)).toBe(true);
});
// The column this points at stays on core's ssh_data table, so a typo here
// would silently detach the host-editor checkbox from its storage.
it("contributes the enableWebUi host capability", () => {
const { manifest } = parseManifest(readBundledManifest("web-endpoint"));
expect(manifest?.contributes?.hostCapability).toMatchObject({
key: "enableWebUi",
});
});
it("exports activate and deactivate from its backend entry", () => {
const entry = path.join(
getBundledPluginsDir(),
"web-endpoint",
"backend",
"index.mjs",
);
const source = fs.readFileSync(entry, "utf8");
expect(source).toMatch(/export async function activate/);
expect(source).toMatch(/export async function deactivate/);
expect(code).not.toMatch(/process\.on\(\s*["']SIG/);
expect(code).not.toMatch(/process\.exit\(/);
}
});
});