Merge f086588599e86f8b5441e41e71adf409aeabb82a into 6b4c24c2e55b5b4013277bd799525086f6a0c40f
This commit is contained in:
commit
166e7cd446
@ -81,6 +81,129 @@ describe("createAccountListHelpers", () => {
|
||||
it("returns sorted ids", () => {
|
||||
expect(listAccountIds(cfg({ z: {}, a: {}, m: {} }))).toEqual(["a", "m", "z"]);
|
||||
});
|
||||
|
||||
it("includes default when ALL named accounts have their own tokens", () => {
|
||||
const config = {
|
||||
channels: {
|
||||
testchannel: {
|
||||
botToken: "xoxb-base",
|
||||
appToken: "xapp-base",
|
||||
accounts: { tank: { botToken: "xoxb-tank", appToken: "xapp-tank" } },
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
expect(listAccountIds(config)).toEqual(["default", "tank"]);
|
||||
});
|
||||
|
||||
it("includes default when normalized IDs differ from raw config keys", () => {
|
||||
const normalizedHelpers = createAccountListHelpers("testchannel", {
|
||||
normalizeAccountId: (id: string) => id.toLowerCase().replace(/\s+/g, "-"),
|
||||
});
|
||||
const config = {
|
||||
channels: {
|
||||
testchannel: {
|
||||
botToken: "xoxb-base",
|
||||
accounts: {
|
||||
"Router D": { botToken: "xoxb-router-d" },
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
// "Router D" normalizes to "router-d" — should still detect own token
|
||||
expect(normalizedHelpers.listAccountIds(config)).toEqual(["default", "router-d"]);
|
||||
});
|
||||
|
||||
it("does NOT inject default in mixed configs (some accounts inherit base tokens)", () => {
|
||||
const config = {
|
||||
channels: {
|
||||
testchannel: {
|
||||
botToken: "xoxb-base",
|
||||
accounts: { teamA: { botToken: "xoxb-own" }, teamB: {} },
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
// teamB inherits base tokens — injecting default would duplicate teamB
|
||||
expect(listAccountIds(config)).toEqual(["teamA", "teamB"]);
|
||||
});
|
||||
|
||||
it("does NOT inject default when account partially overrides (only appToken)", () => {
|
||||
const config = {
|
||||
channels: {
|
||||
testchannel: {
|
||||
botToken: "xoxb-base",
|
||||
appToken: "xapp-base",
|
||||
accounts: { teamA: { appToken: "xapp-own" } },
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
// teamA overrides appToken but inherits botToken — still same bot identity
|
||||
expect(listAccountIds(config)).toEqual(["teamA"]);
|
||||
});
|
||||
|
||||
it("does NOT inject default when base token is whitespace-only", () => {
|
||||
const config = {
|
||||
channels: {
|
||||
testchannel: {
|
||||
botToken: " ",
|
||||
accounts: { teamA: {} },
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
expect(listAccountIds(config)).toEqual(["teamA"]);
|
||||
});
|
||||
|
||||
it("does NOT inject default when named accounts inherit base tokens (avoids duplicates)", () => {
|
||||
const config = {
|
||||
channels: {
|
||||
testchannel: {
|
||||
botToken: "xoxb-base",
|
||||
appToken: "xapp-base",
|
||||
accounts: { tank: {} },
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
expect(listAccountIds(config)).toEqual(["tank"]);
|
||||
});
|
||||
|
||||
it("does NOT inject default when Discord named accounts inherit base token", () => {
|
||||
const config = {
|
||||
channels: {
|
||||
testchannel: {
|
||||
token: "discord-bot-token",
|
||||
accounts: { teamA: {} },
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
expect(listAccountIds(config)).toEqual(["teamA"]);
|
||||
});
|
||||
|
||||
it("does not duplicate default when already in accounts (case-insensitive)", () => {
|
||||
const config = {
|
||||
channels: {
|
||||
testchannel: {
|
||||
botToken: "xoxb-base",
|
||||
accounts: { Default: {}, tank: {} },
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
expect(listAccountIds(config)).toEqual(["Default", "tank"]);
|
||||
});
|
||||
|
||||
it("does not duplicate default when already in accounts", () => {
|
||||
const config = {
|
||||
channels: {
|
||||
testchannel: {
|
||||
botToken: "xoxb-base",
|
||||
accounts: { default: {}, tank: {} },
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
expect(listAccountIds(config)).toEqual(["default", "tank"]);
|
||||
});
|
||||
|
||||
it("does not include default when base has no tokens", () => {
|
||||
expect(listAccountIds(cfg({ tank: {} }))).toEqual(["tank"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveDefaultAccountId", () => {
|
||||
|
||||
@ -43,6 +43,66 @@ export function createAccountListHelpers(
|
||||
if (ids.length === 0) {
|
||||
return [DEFAULT_ACCOUNT_ID];
|
||||
}
|
||||
// Check whether any existing named account already normalizes to "default".
|
||||
const normalizedIds = ids.map(normalizeAccountId);
|
||||
if (normalizedIds.includes(DEFAULT_ACCOUNT_ID)) {
|
||||
return ids.toSorted((a, b) => a.localeCompare(b));
|
||||
}
|
||||
// If the base channel config has its own tokens (botToken/appToken/token),
|
||||
// only inject a default account when at least one named account carries its
|
||||
// own per-account auth. When every named account inherits the base tokens
|
||||
// (i.e. has no per-account botToken/appToken/token override), injecting
|
||||
// default would start a duplicate provider on the same credentials.
|
||||
const channel = cfg.channels?.[channelKey];
|
||||
const base = channel as Record<string, unknown> | undefined;
|
||||
const isTruthy = (v: unknown): boolean =>
|
||||
typeof v === "string" ? v.trim().length > 0 : Boolean(v);
|
||||
// Identify which token fields the base config provides.
|
||||
const baseTokenFields = (["botToken", "appToken", "token"] as const).filter((f) =>
|
||||
isTruthy(base?.[f]),
|
||||
);
|
||||
if (baseTokenFields.length > 0) {
|
||||
const accounts = (base?.accounts ?? {}) as Record<string, Record<string, unknown>>;
|
||||
// Use the caller-provided normalizer for the reverse map so it matches
|
||||
// the IDs returned by listConfiguredAccountIds().
|
||||
const normalizeId = options?.normalizeAccountId ?? normalizeAccountId;
|
||||
// Build reverse map from normalized ID → raw config key so we can look
|
||||
// up the account object even when normalizeAccountId transforms the keys.
|
||||
const rawKeys = Object.keys(accounts);
|
||||
const normalizedToRaw = new Map<string, string>();
|
||||
for (const key of rawKeys) {
|
||||
const normalized = normalizeId(key);
|
||||
if (normalized && !normalizedToRaw.has(normalized)) {
|
||||
normalizedToRaw.set(normalized, key);
|
||||
}
|
||||
}
|
||||
// Only consider enabled accounts — disabled accounts are skipped at
|
||||
// startup and must not prevent default injection for enabled ones.
|
||||
const enabledIds = ids.filter((id) => {
|
||||
const rawKey = normalizedToRaw.get(id) ?? id;
|
||||
const acct = accounts[rawKey];
|
||||
return !acct || acct["enabled"] !== false;
|
||||
});
|
||||
const everyAccountHasOwnTokens =
|
||||
enabledIds.length > 0 &&
|
||||
enabledIds.every((id) => {
|
||||
const rawKey = normalizedToRaw.get(id) ?? id;
|
||||
const acct = accounts[rawKey];
|
||||
if (!acct) {
|
||||
return false;
|
||||
}
|
||||
// An account is only independent if it overrides *every* base token
|
||||
// field. Partial overrides still inherit the remaining base tokens.
|
||||
return baseTokenFields.every((f) => isTruthy(acct[f]));
|
||||
});
|
||||
if (everyAccountHasOwnTokens) {
|
||||
// Every named account has distinct credentials, so default won't
|
||||
// collide with any of them — safe to inject.
|
||||
return [DEFAULT_ACCOUNT_ID, ...ids].toSorted((a, b) => a.localeCompare(b));
|
||||
}
|
||||
// At least one named account inherits base tokens — injecting default
|
||||
// would duplicate that account's credentials.
|
||||
}
|
||||
return ids.toSorted((a, b) => a.localeCompare(b));
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user