From 6fa0027c610ab3d11747940bcf36f7c1caa53fab Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 22 Mar 2026 19:48:21 +0000 Subject: [PATCH] refactor: simplify tlon and discord setup accounts --- .../discord/src/setup-account-state.test.ts | 16 +++ extensions/discord/src/setup-account-state.ts | 15 +- extensions/tlon/src/channel.ts | 5 +- extensions/tlon/src/types.test.ts | 81 +++++++++++ extensions/tlon/src/types.ts | 130 ++++++++++-------- 5 files changed, 179 insertions(+), 68 deletions(-) create mode 100644 extensions/tlon/src/types.test.ts diff --git a/extensions/discord/src/setup-account-state.test.ts b/extensions/discord/src/setup-account-state.test.ts index 12951b86311..259ce133f4d 100644 --- a/extensions/discord/src/setup-account-state.test.ts +++ b/extensions/discord/src/setup-account-state.test.ts @@ -1,10 +1,26 @@ import { describe, expect, it } from "vitest"; import { inspectDiscordSetupAccount, + listDiscordSetupAccountIds, resolveDiscordSetupAccountConfig, } from "./setup-account-state.js"; describe("discord setup account state", () => { + it("lists normalized setup account ids plus the implicit default account", () => { + expect( + listDiscordSetupAccountIds({ + channels: { + discord: { + accounts: { + Work: { token: "work-token" }, + alerts: { token: "alerts-token" }, + }, + }, + }, + }), + ).toEqual(["alerts", "default", "work"]); + }); + it("resolves setup account config when account key casing differs from normalized id", () => { const resolved = resolveDiscordSetupAccountConfig({ cfg: { diff --git a/extensions/discord/src/setup-account-state.ts b/extensions/discord/src/setup-account-state.ts index b2c555593d9..963e2d0e9c8 100644 --- a/extensions/discord/src/setup-account-state.ts +++ b/extensions/discord/src/setup-account-state.ts @@ -1,4 +1,5 @@ import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id"; +import { listCombinedAccountIds } from "openclaw/plugin-sdk/account-resolution"; import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; import { hasConfiguredSecretInput, @@ -43,13 +44,13 @@ function inspectConfiguredToken(value: unknown): { export function listDiscordSetupAccountIds(cfg: OpenClawConfig): string[] { const accounts = cfg.channels?.discord?.accounts; - const ids = - accounts && typeof accounts === "object" && !Array.isArray(accounts) - ? Object.keys(accounts) - .map((accountId) => normalizeAccountId(accountId)) - .filter(Boolean) - : []; - return [...new Set([DEFAULT_ACCOUNT_ID, ...ids])]; + return listCombinedAccountIds({ + configuredAccountIds: + accounts && typeof accounts === "object" && !Array.isArray(accounts) + ? Object.keys(accounts).map((accountId) => normalizeAccountId(accountId)) + : [], + implicitAccountId: DEFAULT_ACCOUNT_ID, + }); } export function resolveDefaultDiscordSetupAccountId(cfg: OpenClawConfig): string { diff --git a/extensions/tlon/src/channel.ts b/extensions/tlon/src/channel.ts index 2035a04b7c9..cda034011d1 100644 --- a/extensions/tlon/src/channel.ts +++ b/extensions/tlon/src/channel.ts @@ -43,9 +43,8 @@ const tlonSetupWizardProxy = createTlonSetupWizardBase({ const tlonConfigAdapter = createHybridChannelConfigAdapter({ sectionKey: TLON_CHANNEL_ID, - listAccountIds: (cfg: OpenClawConfig) => listTlonAccountIds(cfg), - resolveAccount: (cfg: OpenClawConfig, accountId?: string | null) => - resolveTlonAccount(cfg, accountId ?? undefined), + listAccountIds: listTlonAccountIds, + resolveAccount: resolveTlonAccount, defaultAccountId: () => "default", clearBaseFields: ["ship", "code", "url", "name"], preserveSectionOnDefaultDelete: true, diff --git a/extensions/tlon/src/types.test.ts b/extensions/tlon/src/types.test.ts new file mode 100644 index 00000000000..911facae1f8 --- /dev/null +++ b/extensions/tlon/src/types.test.ts @@ -0,0 +1,81 @@ +import { describe, expect, it } from "vitest"; +import type { OpenClawConfig } from "../api.js"; +import { listTlonAccountIds, resolveTlonAccount } from "./types.js"; + +describe("tlon account helpers", () => { + it("lists named accounts and the implicit default account", () => { + const cfg = { + channels: { + tlon: { + ship: "~zod", + accounts: { + Work: { ship: "~bus" }, + alerts: { ship: "~nec" }, + }, + }, + }, + } as OpenClawConfig; + + expect(listTlonAccountIds(cfg)).toEqual(["alerts", "default", "work"]); + }); + + it("merges named account config over channel defaults", () => { + const resolved = resolveTlonAccount( + { + channels: { + tlon: { + name: "Base", + ship: "~zod", + url: "https://urbit.example.com", + code: "base-code", + dmAllowlist: ["~nec"], + groupInviteAllowlist: ["~bus"], + defaultAuthorizedShips: ["~marzod"], + accounts: { + Work: { + name: "Work", + code: "work-code", + dmAllowlist: ["~rovnys"], + }, + }, + }, + }, + } as OpenClawConfig, + "work", + ); + + expect(resolved.accountId).toBe("work"); + expect(resolved.name).toBe("Work"); + expect(resolved.ship).toBe("~zod"); + expect(resolved.url).toBe("https://urbit.example.com"); + expect(resolved.code).toBe("work-code"); + expect(resolved.dmAllowlist).toEqual(["~rovnys"]); + expect(resolved.groupInviteAllowlist).toEqual(["~bus"]); + expect(resolved.defaultAuthorizedShips).toEqual(["~marzod"]); + expect(resolved.configured).toBe(true); + }); + + it("keeps the default account on channel-level config only", () => { + const resolved = resolveTlonAccount( + { + channels: { + tlon: { + ship: "~zod", + url: "https://urbit.example.com", + code: "base-code", + accounts: { + default: { + ship: "~ignored", + code: "ignored-code", + }, + }, + }, + }, + } as OpenClawConfig, + "default", + ); + + expect(resolved.ship).toBe("~zod"); + expect(resolved.code).toBe("base-code"); + }); +}); diff --git a/extensions/tlon/src/types.ts b/extensions/tlon/src/types.ts index ce4deaeca68..4e1e7c8fd6b 100644 --- a/extensions/tlon/src/types.ts +++ b/extensions/tlon/src/types.ts @@ -1,5 +1,30 @@ +import { + DEFAULT_ACCOUNT_ID, + listCombinedAccountIds, + normalizeAccountId, + resolveMergedAccountConfig, +} from "openclaw/plugin-sdk/account-resolution"; import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; +type TlonAccountConfig = { + name?: string; + enabled?: boolean; + ship?: string; + url?: string; + code?: string; + allowPrivateNetwork?: boolean; + groupChannels?: string[]; + dmAllowlist?: string[]; + groupInviteAllowlist?: string[]; + autoDiscoverChannels?: boolean; + showModelSignature?: boolean; + autoAcceptDmInvites?: boolean; + autoAcceptGroupInvites?: boolean; + defaultAuthorizedShips?: string[]; + ownerShip?: string; + accounts?: Record; +}; + export type TlonResolvedAccount = { accountId: string; name: string | null; @@ -22,33 +47,38 @@ export type TlonResolvedAccount = { ownerShip: string | null; }; +function resolveTlonChannelConfig(cfg: OpenClawConfig): TlonAccountConfig | undefined { + return cfg.channels?.tlon as TlonAccountConfig | undefined; +} + +function resolveMergedTlonAccountConfig( + cfg: OpenClawConfig, + accountId: string, +): Record & TlonAccountConfig { + const channel = resolveTlonChannelConfig(cfg); + if (accountId === DEFAULT_ACCOUNT_ID) { + return (channel ?? {}) as Record & TlonAccountConfig; + } + return resolveMergedAccountConfig & TlonAccountConfig>({ + channelConfig: (channel ?? {}) as Record & TlonAccountConfig, + accounts: channel?.accounts as + | Record & TlonAccountConfig>> + | undefined, + accountId, + normalizeAccountId, + }); +} + export function resolveTlonAccount( cfg: OpenClawConfig, accountId?: string | null, ): TlonResolvedAccount { - const base = cfg.channels?.tlon as - | { - name?: string; - enabled?: boolean; - ship?: string; - url?: string; - code?: string; - allowPrivateNetwork?: boolean; - groupChannels?: string[]; - dmAllowlist?: string[]; - groupInviteAllowlist?: string[]; - autoDiscoverChannels?: boolean; - showModelSignature?: boolean; - autoAcceptDmInvites?: boolean; - autoAcceptGroupInvites?: boolean; - ownerShip?: string; - accounts?: Record>; - } - | undefined; + const resolvedAccountId = normalizeAccountId(accountId); + const base = resolveTlonChannelConfig(cfg); if (!base) { return { - accountId: accountId || "default", + accountId: resolvedAccountId, name: null, enabled: false, configured: false, @@ -68,42 +98,26 @@ export function resolveTlonAccount( }; } - const useDefault = !accountId || accountId === "default"; - const account = useDefault ? base : base.accounts?.[accountId]; - - const ship = (account?.ship ?? base.ship ?? null) as string | null; - const url = (account?.url ?? base.url ?? null) as string | null; - const code = (account?.code ?? base.code ?? null) as string | null; - const allowPrivateNetwork = (account?.allowPrivateNetwork ?? base.allowPrivateNetwork ?? null) as - | boolean - | null; - const groupChannels = (account?.groupChannels ?? base.groupChannels ?? []) as string[]; - const dmAllowlist = (account?.dmAllowlist ?? base.dmAllowlist ?? []) as string[]; - const groupInviteAllowlist = (account?.groupInviteAllowlist ?? - base.groupInviteAllowlist ?? - []) as string[]; - const autoDiscoverChannels = (account?.autoDiscoverChannels ?? - base.autoDiscoverChannels ?? - null) as boolean | null; - const showModelSignature = (account?.showModelSignature ?? base.showModelSignature ?? null) as - | boolean - | null; - const autoAcceptDmInvites = (account?.autoAcceptDmInvites ?? base.autoAcceptDmInvites ?? null) as - | boolean - | null; - const autoAcceptGroupInvites = (account?.autoAcceptGroupInvites ?? - base.autoAcceptGroupInvites ?? - null) as boolean | null; - const ownerShip = (account?.ownerShip ?? base.ownerShip ?? null) as string | null; - const defaultAuthorizedShips = ((account as Record)?.defaultAuthorizedShips ?? - (base as Record)?.defaultAuthorizedShips ?? - []) as string[]; + const merged = resolveMergedTlonAccountConfig(cfg, resolvedAccountId); + const ship = (merged.ship ?? null) as string | null; + const url = (merged.url ?? null) as string | null; + const code = (merged.code ?? null) as string | null; + const allowPrivateNetwork = (merged.allowPrivateNetwork ?? null) as boolean | null; + const groupChannels = (merged.groupChannels ?? []) as string[]; + const dmAllowlist = (merged.dmAllowlist ?? []) as string[]; + const groupInviteAllowlist = (merged.groupInviteAllowlist ?? []) as string[]; + const autoDiscoverChannels = (merged.autoDiscoverChannels ?? null) as boolean | null; + const showModelSignature = (merged.showModelSignature ?? null) as boolean | null; + const autoAcceptDmInvites = (merged.autoAcceptDmInvites ?? null) as boolean | null; + const autoAcceptGroupInvites = (merged.autoAcceptGroupInvites ?? null) as boolean | null; + const ownerShip = (merged.ownerShip ?? null) as string | null; + const defaultAuthorizedShips = (merged.defaultAuthorizedShips ?? []) as string[]; const configured = Boolean(ship && url && code); return { - accountId: accountId || "default", - name: (account?.name ?? base.name ?? null) as string | null, - enabled: (account?.enabled ?? base.enabled ?? true) !== false, + accountId: resolvedAccountId, + name: (merged.name ?? null) as string | null, + enabled: merged.enabled !== false, configured, ship, url, @@ -122,12 +136,12 @@ export function resolveTlonAccount( } export function listTlonAccountIds(cfg: OpenClawConfig): string[] { - const base = cfg.channels?.tlon as - | { ship?: string; accounts?: Record> } - | undefined; + const base = resolveTlonChannelConfig(cfg); if (!base) { return []; } - const accounts = base.accounts ?? {}; - return [...(base.ship ? ["default"] : []), ...Object.keys(accounts)]; + return listCombinedAccountIds({ + configuredAccountIds: Object.keys(base.accounts ?? {}).map(normalizeAccountId), + implicitAccountId: base.ship ? DEFAULT_ACCOUNT_ID : undefined, + }); }