feat: prepare channel setup input before config writes

This commit is contained in:
Shakker
2026-07-16 23:56:29 +01:00
committed by Shakker
parent 7662b4bb8c
commit 13306d66e3
3 changed files with 108 additions and 1 deletions
+6
View File
@@ -84,6 +84,12 @@ export type ChannelSetupAdapter = {
accountId?: string;
input?: ChannelSetupInput;
}) => string;
prepareAccountConfigInput?: (params: {
cfg: OpenClawConfig;
accountId: string;
input: ChannelSetupInput;
runtime: RuntimeEnv;
}) => Promise<ChannelSetupInput> | ChannelSetupInput;
resolveBindingAccountId?: (params: {
cfg: OpenClawConfig;
agentId: string;
+92
View File
@@ -680,6 +680,98 @@ describe("channelsAddCommand", () => {
});
});
it("prepares setup input before validation, config writes, and post-write hooks", async () => {
const callOrder: string[] = [];
const beforePersistentEffect = vi.fn(async () => {
callOrder.push("authority");
});
const prepareAccountConfigInput = vi.fn(async ({ input }) => {
callOrder.push("prepare");
return {
...input,
token: "prepared-token",
workspace: "prepared-workspace",
};
});
const validateInput = vi.fn(({ input }) => {
callOrder.push("validate");
return input.token === "prepared-token" ? null : "input was not prepared";
});
const applyAccountConfig = vi.fn(({ cfg, input }) => {
callOrder.push("apply");
return {
...cfg,
channels: {
...cfg.channels,
"prepared-chat": {
enabled: true,
token: input.token,
workspace: input.workspace,
},
},
};
});
const afterAccountConfigWritten = vi.fn(({ input }) => {
callOrder.push("after");
expect(input).toMatchObject({
token: "prepared-token",
workspace: "prepared-workspace",
});
});
setActivePluginRegistry(
createTestRegistry([
{
pluginId: "prepared-chat",
plugin: {
...createChannelTestPluginBase({
id: "prepared-chat",
label: "Prepared Chat",
}),
setup: {
prepareAccountConfigInput,
validateInput,
applyAccountConfig,
afterAccountConfigWritten,
},
} as ChannelPlugin,
source: "test",
},
]),
);
configMocks.readConfigFileSnapshot.mockResolvedValue({ ...baseConfigSnapshot });
await channelsAddCommand(
{
channel: "prepared-chat",
account: "work",
code: "setup-code",
},
runtime,
{ hasFlags: true, beforePersistentEffect },
);
expect(callOrder).toEqual([
"authority",
"prepare",
"validate",
"apply",
"authority",
"authority",
"after",
]);
expect(prepareAccountConfigInput).toHaveBeenCalledWith({
cfg: baseConfigSnapshot.config,
accountId: "work",
input: { code: "setup-code" },
runtime,
});
expect(writtenChannel("prepared-chat")).toEqual({
enabled: true,
token: "prepared-token",
workspace: "prepared-workspace",
});
});
it("loads external channel setup snapshots for newly installed and existing plugins", async () => {
configMocks.readConfigFileSnapshot.mockResolvedValue({ ...baseConfigSnapshot });
setActivePluginRegistry(createTestRegistry());
+10 -1
View File
@@ -263,13 +263,22 @@ async function channelsAddCommandImpl(
runtime.exit(1);
return;
}
const input = buildChannelSetupInput(opts);
let input = buildChannelSetupInput(opts);
const accountId =
plugin.setup.resolveAccountId?.({
cfg: nextConfig,
accountId: opts.account,
input,
}) ?? normalizeAccountId(opts.account);
if (plugin.setup.prepareAccountConfigInput) {
await params?.beforePersistentEffect?.();
input = await plugin.setup.prepareAccountConfigInput({
cfg: nextConfig,
accountId,
input,
runtime,
});
}
const validationError = plugin.setup.validateInput?.({
cfg: nextConfig,