mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 19:00:45 +00:00
fix: scope read-only channel ids
This commit is contained in:
@@ -187,6 +187,35 @@ describe("listReadOnlyChannelPluginsForConfig", () => {
|
||||
expect(fs.existsSync(fullMarker)).toBe(false);
|
||||
});
|
||||
|
||||
it("matches setup-only plugins by manifest-owned channel ids when plugin id differs", () => {
|
||||
const { pluginDir, fullMarker, setupMarker } = writeExternalSetupChannelPlugin({
|
||||
pluginId: "external-chat-plugin",
|
||||
channelId: "external-chat",
|
||||
setupChannelId: "external-chat-plugin",
|
||||
});
|
||||
const plugins = listReadOnlyChannelPluginsForConfig(
|
||||
{
|
||||
channels: {
|
||||
"external-chat": { token: "configured" },
|
||||
},
|
||||
plugins: {
|
||||
load: { paths: [pluginDir] },
|
||||
allow: ["external-chat-plugin"],
|
||||
},
|
||||
} as never,
|
||||
{
|
||||
env: { ...process.env },
|
||||
includePersistedAuthState: false,
|
||||
},
|
||||
);
|
||||
|
||||
const plugin = plugins.find((entry) => entry.id === "external-chat");
|
||||
expect(plugin?.meta.id).toBe("external-chat");
|
||||
expect(plugin?.meta.blurb).toBe("setup entry");
|
||||
expect(fs.existsSync(setupMarker)).toBe(true);
|
||||
expect(fs.existsSync(fullMarker)).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps configured external channels visible when no setup entry exists", () => {
|
||||
const { pluginDir, fullMarker, setupMarker } = writeExternalSetupChannelPlugin({
|
||||
setupEntry: false,
|
||||
|
||||
@@ -67,6 +67,58 @@ function addChannelPlugins(
|
||||
}
|
||||
}
|
||||
|
||||
function cloneChannelPluginForChannelId(plugin: ChannelPlugin, channelId: string): ChannelPlugin {
|
||||
if (plugin.id === channelId && plugin.meta.id === channelId) {
|
||||
return plugin;
|
||||
}
|
||||
return {
|
||||
...plugin,
|
||||
id: channelId,
|
||||
meta: {
|
||||
...plugin.meta,
|
||||
id: channelId,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function addSetupChannelPlugins(
|
||||
byId: Map<string, ChannelPlugin>,
|
||||
setups: Iterable<{
|
||||
pluginId: string;
|
||||
plugin: ChannelPlugin;
|
||||
}>,
|
||||
options: {
|
||||
ownedMissingChannelIdsByPluginId: ReadonlyMap<string, readonly string[]>;
|
||||
},
|
||||
): void {
|
||||
for (const setup of setups) {
|
||||
const ownedMissingChannelIds = options.ownedMissingChannelIdsByPluginId.get(setup.pluginId);
|
||||
if (!ownedMissingChannelIds || ownedMissingChannelIds.length === 0) {
|
||||
continue;
|
||||
}
|
||||
if (ownedMissingChannelIds.includes(setup.plugin.id)) {
|
||||
addChannelPlugins(byId, [setup.plugin], {
|
||||
onlyIds: new Set(ownedMissingChannelIds),
|
||||
allowOverwrite: false,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (setup.plugin.id !== setup.pluginId) {
|
||||
continue;
|
||||
}
|
||||
addChannelPlugins(
|
||||
byId,
|
||||
ownedMissingChannelIds.map((channelId) =>
|
||||
cloneChannelPluginForChannelId(setup.plugin, channelId),
|
||||
),
|
||||
{
|
||||
onlyIds: new Set(ownedMissingChannelIds),
|
||||
allowOverwrite: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function resolveReadOnlyWorkspaceDir(
|
||||
cfg: OpenClawConfig,
|
||||
options: ReadOnlyChannelPluginOptions,
|
||||
@@ -189,6 +241,18 @@ export function resolveReadOnlyChannelPluginsForConfig(
|
||||
cache: options.cache,
|
||||
});
|
||||
if (externalPluginIds.length > 0) {
|
||||
const missingChannelIdSet = new Set(missingConfiguredChannelIds);
|
||||
const ownedMissingChannelIdsByPluginId = new Map(
|
||||
externalManifestRecords
|
||||
.filter((record) => externalPluginIds.includes(record.id))
|
||||
.map(
|
||||
(record) =>
|
||||
[
|
||||
record.id,
|
||||
record.channels.filter((channelId) => missingChannelIdSet.has(channelId)),
|
||||
] as const,
|
||||
),
|
||||
);
|
||||
const registry = loadOpenClawPlugins({
|
||||
config: cfg,
|
||||
activationSourceConfig: options.activationSourceConfig ?? cfg,
|
||||
@@ -201,14 +265,9 @@ export function resolveReadOnlyChannelPluginsForConfig(
|
||||
requireSetupEntryForSetupOnlyChannelPlugins: true,
|
||||
onlyPluginIds: externalPluginIds,
|
||||
});
|
||||
addChannelPlugins(
|
||||
byId,
|
||||
registry.channelSetups.map((setup) => setup.plugin),
|
||||
{
|
||||
onlyIds: new Set(missingConfiguredChannelIds),
|
||||
allowOverwrite: false,
|
||||
},
|
||||
);
|
||||
addSetupChannelPlugins(byId, registry.channelSetups, {
|
||||
ownedMissingChannelIdsByPluginId,
|
||||
});
|
||||
}
|
||||
|
||||
const plugins = [...byId.values()];
|
||||
|
||||
@@ -74,6 +74,24 @@ describe("command secret targets module import", () => {
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "external-chat-plugin",
|
||||
secrets: {
|
||||
secretTargetRegistryEntries: [
|
||||
{
|
||||
id: "channels.external-chat.token",
|
||||
targetType: "channels.external-chat.token",
|
||||
configFile: "openclaw.json",
|
||||
pathPattern: "channels.external-chat.token",
|
||||
secretShape: "secret_input",
|
||||
expectedResolvedValue: "string",
|
||||
includeInPlan: true,
|
||||
includeInConfigure: true,
|
||||
includeInAudit: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
vi.doMock("../secrets/target-registry.js", () => ({
|
||||
@@ -86,9 +104,13 @@ describe("command secret targets module import", () => {
|
||||
|
||||
const mod = await import("./command-secret-targets.js");
|
||||
const targets = mod.getStatusCommandSecretTargetIds({
|
||||
channels: { telegram: { botToken: "123456:ABCDEF" } },
|
||||
channels: {
|
||||
"external-chat": { token: "configured" },
|
||||
telegram: { botToken: "123456:ABCDEF" },
|
||||
},
|
||||
});
|
||||
|
||||
expect(targets.has("channels.external-chat.token")).toBe(true);
|
||||
expect(targets.has("channels.telegram.botToken")).toBe(true);
|
||||
expect(targets.has("channels.telegram.gatewayToken")).toBe(false);
|
||||
expect(targets.has("channels.telegram.gatewayTokenRef")).toBe(false);
|
||||
|
||||
@@ -75,7 +75,6 @@ function getChannelSecretTargetIds(): string[] {
|
||||
}
|
||||
|
||||
function isScopedChannelSecretTargetEntry(params: {
|
||||
pluginId: string;
|
||||
entry: {
|
||||
id: string;
|
||||
configFile?: string;
|
||||
@@ -83,7 +82,11 @@ function isScopedChannelSecretTargetEntry(params: {
|
||||
refPathPattern?: string;
|
||||
};
|
||||
}): boolean {
|
||||
const allowedPrefix = `channels.${params.pluginId}.`;
|
||||
const channelId = /^channels\.([^.]+)\./.exec(params.entry.id)?.[1];
|
||||
if (!channelId) {
|
||||
return false;
|
||||
}
|
||||
const allowedPrefix = `channels.${channelId}.`;
|
||||
return (
|
||||
params.entry.id.startsWith(allowedPrefix) &&
|
||||
params.entry.configFile === "openclaw.json" &&
|
||||
@@ -104,7 +107,7 @@ function getConfiguredChannelSecretTargetIds(
|
||||
includePersistedAuthState: false,
|
||||
})) {
|
||||
for (const entry of plugin.secrets?.secretTargetRegistryEntries ?? []) {
|
||||
if (isScopedChannelSecretTargetEntry({ pluginId: plugin.id, entry })) {
|
||||
if (isScopedChannelSecretTargetEntry({ entry })) {
|
||||
targetIds.add(entry.id);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user