Skip to content

Commit 6483374

Browse files
dcramerclaude
andcommitted
fix(plugins): Reserve core feature names for manifest-only plugins
Runtime validation only saw registrations with runtime hooks, so an inline or YAML manifest could still claim memory or briefs and enter the plugin catalog next to the core feature. Check reserved names at the shared manifest registration boundary as well. Refs #1861 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent a530f80 commit 6483374

2 files changed

Lines changed: 33 additions & 1 deletion

File tree

‎packages/junior/src/chat/plugins/registry.ts‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { readFileSync, readdirSync, statSync } from "node:fs";
22
import path from "node:path";
33
import type { CredentialBroker } from "@/chat/credentials/broker";
44
import { pluginRoots } from "@/chat/discovery";
5+
import { isCoreFeatureName } from "@/chat/plugins/core-features";
56
import { logInfo, logWarn, setSpanAttributes } from "@/chat/logging";
67
import { parseInlinePluginManifest, parsePluginManifest } from "./manifest";
78
import { createOAuthBearerBroker } from "./auth/oauth-bearer-broker";
@@ -108,6 +109,11 @@ function registerPluginManifest(
108109
if (state.pluginsByName.has(manifest.name)) {
109110
throw new Error(`Duplicate plugin name "${manifest.name}"`);
110111
}
112+
if (isCoreFeatureName(manifest.name)) {
113+
throw new Error(
114+
`Plugin name "${manifest.name}" is reserved for a Junior core feature`,
115+
);
116+
}
111117

112118
for (const domain of providerDomains(manifest)) {
113119
const owner = state.domainToPlugin.get(domain);
@@ -511,7 +517,9 @@ export function createPluginCatalogRuntime(): PluginCatalogRuntime {
511517
commands.push({
512518
cmd: command.cmd,
513519
...(command.args ? { args: [...command.args] } : undefined),
514-
...(command.sudo !== undefined ? { sudo: command.sudo } : undefined),
520+
...(command.sudo !== undefined
521+
? { sudo: command.sudo }
522+
: undefined),
515523
});
516524
}
517525
}

‎packages/junior/tests/unit/plugins/core-features.test.ts‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ import {
77
validatePlugins,
88
} from "@/chat/plugins/agent-hooks";
99
import { getCoreFeatures, setCoreFeatures } from "@/chat/plugins/core-features";
10+
import { createPluginCatalogRuntime } from "@/chat/plugins/registry";
11+
import {
12+
defineJuniorPlugins,
13+
pluginCatalogConfigFromPluginSet,
14+
} from "@/plugins";
1015

1116
afterEach(() => {
1217
setCoreFeatures([]);
@@ -76,5 +81,24 @@ describe("core features", () => {
7681
expect(() => validatePlugins([plugin])).toThrow(
7782
'Plugin name "briefs" is reserved for a Junior core feature',
7883
);
84+
85+
// Manifest-only plugins skip runtime validation and enter the catalog.
86+
const catalog = createPluginCatalogRuntime();
87+
catalog.setConfig(
88+
pluginCatalogConfigFromPluginSet(
89+
defineJuniorPlugins([
90+
defineJuniorPlugin({
91+
manifest: {
92+
name: "memory",
93+
displayName: "Other Memory",
94+
description: "Not core memory",
95+
},
96+
}),
97+
]),
98+
),
99+
);
100+
expect(() => catalog.getProviders()).toThrow(
101+
'Plugin name "memory" is reserved for a Junior core feature',
102+
);
79103
});
80104
});

0 commit comments

Comments
 (0)