diff --git a/src/channels/plugins/account-helpers.test.ts b/src/channels/plugins/account-helpers.test.ts new file mode 100644 index 00000000000..121aed38f9b --- /dev/null +++ b/src/channels/plugins/account-helpers.test.ts @@ -0,0 +1,71 @@ +import { describe, expect, it } from "vitest"; +import type { OpenClawConfig } from "../../config/config.js"; +import { createAccountListHelpers } from "./account-helpers.js"; + +const { listConfiguredAccountIds, listAccountIds, resolveDefaultAccountId } = + createAccountListHelpers("testchannel"); + +function cfg(accounts?: Record | null): OpenClawConfig { + if (accounts === null) { + return { channels: { testchannel: {} } } as unknown as OpenClawConfig; + } + if (accounts === undefined) { + return {} as unknown as OpenClawConfig; + } + return { channels: { testchannel: { accounts } } } as unknown as OpenClawConfig; +} + +describe("createAccountListHelpers", () => { + describe("listConfiguredAccountIds", () => { + it("returns empty for missing config", () => { + expect(listConfiguredAccountIds({} as OpenClawConfig)).toEqual([]); + }); + + it("returns empty when no accounts key", () => { + expect(listConfiguredAccountIds(cfg(null))).toEqual([]); + }); + + it("returns empty for empty accounts object", () => { + expect(listConfiguredAccountIds(cfg({}))).toEqual([]); + }); + + it("filters out empty keys", () => { + expect(listConfiguredAccountIds(cfg({ "": {}, a: {} }))).toEqual(["a"]); + }); + + it("returns account keys", () => { + expect(listConfiguredAccountIds(cfg({ work: {}, personal: {} }))).toEqual([ + "work", + "personal", + ]); + }); + }); + + describe("listAccountIds", () => { + it('returns ["default"] for empty config', () => { + expect(listAccountIds({} as OpenClawConfig)).toEqual(["default"]); + }); + + it('returns ["default"] for empty accounts', () => { + expect(listAccountIds(cfg({}))).toEqual(["default"]); + }); + + it("returns sorted ids", () => { + expect(listAccountIds(cfg({ z: {}, a: {}, m: {} }))).toEqual(["a", "m", "z"]); + }); + }); + + describe("resolveDefaultAccountId", () => { + it('returns "default" when present', () => { + expect(resolveDefaultAccountId(cfg({ default: {}, other: {} }))).toBe("default"); + }); + + it("returns first sorted id when no default", () => { + expect(resolveDefaultAccountId(cfg({ beta: {}, alpha: {} }))).toBe("alpha"); + }); + + it('returns "default" for empty config', () => { + expect(resolveDefaultAccountId({} as OpenClawConfig)).toBe("default"); + }); + }); +}); diff --git a/src/channels/plugins/account-helpers.ts b/src/channels/plugins/account-helpers.ts new file mode 100644 index 00000000000..406faa44f0c --- /dev/null +++ b/src/channels/plugins/account-helpers.ts @@ -0,0 +1,31 @@ +import type { OpenClawConfig } from "../../config/config.js"; +import { DEFAULT_ACCOUNT_ID } from "../../routing/session-key.js"; + +export function createAccountListHelpers(channelKey: string) { + function listConfiguredAccountIds(cfg: OpenClawConfig): string[] { + const channel = cfg.channels?.[channelKey]; + const accounts = (channel as Record | undefined)?.accounts; + if (!accounts || typeof accounts !== "object") { + return []; + } + return Object.keys(accounts as Record).filter(Boolean); + } + + function listAccountIds(cfg: OpenClawConfig): string[] { + const ids = listConfiguredAccountIds(cfg); + if (ids.length === 0) { + return [DEFAULT_ACCOUNT_ID]; + } + return ids.toSorted((a, b) => a.localeCompare(b)); + } + + function resolveDefaultAccountId(cfg: OpenClawConfig): string { + const ids = listAccountIds(cfg); + if (ids.includes(DEFAULT_ACCOUNT_ID)) { + return DEFAULT_ACCOUNT_ID; + } + return ids[0] ?? DEFAULT_ACCOUNT_ID; + } + + return { listConfiguredAccountIds, listAccountIds, resolveDefaultAccountId }; +} diff --git a/src/plugin-sdk/index.ts b/src/plugin-sdk/index.ts index 9e1dfd73d8a..b801863f0f4 100644 --- a/src/plugin-sdk/index.ts +++ b/src/plugin-sdk/index.ts @@ -1,3 +1,4 @@ +export { createAccountListHelpers } from "../channels/plugins/account-helpers.js"; export { CHANNEL_MESSAGE_ACTION_NAMES } from "../channels/plugins/message-action-names.js"; export { BLUEBUBBLES_ACTIONS,