feat: ship the AI assistant as a first-party in-process plugin

Moves src/backend/ai and its routes into plugins/ai, mounted through a
small /ai dispatcher instead of its own port. Moves the ai-enabled and
ai-private-endpoints settings routes out of user-settings-routes.ts
into the plugin too, so it has no leftover core dependents.
This commit is contained in:
LukeGus committed 2026-09-21 15:20:05 -05:00
1 parent c58ccbfc3f
commit 504710b065
43 files changed
+568 -262

No files matched your search

+42
View File
@@ -49,6 +49,24 @@ function plugin(overrides: Partial<PluginSummary> = {}): PluginSummary {
};
}
function aiPlugin(overrides: Partial<PluginSummary> = {}): PluginSummary {
return plugin({
id: "ai",
name: "AI Assistant",
contributes: {
tabs: [
{
id: "ai",
titleKey: "nav.ai",
icon: "Sparkles",
openFrom: ["rail", "palette"],
},
],
},
...overrides,
});
}
describe("plugin state applied to the shell", () => {
afterEach(() => {
resetPluginState();
@@ -139,6 +157,30 @@ describe("plugin state applied to the shell", () => {
);
});
it("allows the ai tab while the plugin is enabled", () => {
applyPluginState([aiPlugin({ enabled: true })]);
expect(isTabTypeAvailable("ai")).toBe(true);
});
it("blocks the ai tab when the plugin is disabled", () => {
applyPluginState([aiPlugin({ enabled: false })]);
expect(isTabTypeAvailable("ai")).toBe(false);
});
it("allows the ai tab again when the plugin is re-enabled", () => {
applyPluginState([aiPlugin({ enabled: false })]);
expect(isTabTypeAvailable("ai")).toBe(false);
applyPluginState([aiPlugin({ enabled: true })]);
expect(isTabTypeAvailable("ai")).toBe(true);
});
it("disabling ai does not affect the terminal, and vice versa", () => {
applyPluginState([aiPlugin({ enabled: false }), plugin({ enabled: true })]);
expect(isTabTypeAvailable("ai")).toBe(false);
expect(isTabTypeAvailable("terminal")).toBe(true);
});
it("reports enablement, defaulting to true before the first load", () => {
expect(isPluginEnabled("ssh-terminal")).toBe(true);