fix(channels): require full token override for independence, trim whitespace tokens

- Named accounts must now override ALL base token fields (not just any
  one) to be considered independent. Partial overrides (e.g. only
  appToken when base has both botToken+appToken) are correctly treated
  as inheriting the base identity.
- Base token check now trims whitespace — '   ' is not a valid token.
- Added 2 test cases: partial override, whitespace-only tokens.

Addresses codex-connector P1+P2 on PR #30310.
This commit is contained in:
zeroaltitude 2026-03-03 21:46:59 -07:00
parent 3800f859d2
commit 4e626e91db
No known key found for this signature in database
GPG Key ID: 77592FB1C703882E
2 changed files with 40 additions and 4 deletions

View File

@ -88,7 +88,7 @@ describe("createAccountListHelpers", () => {
testchannel: {
botToken: "xoxb-base",
appToken: "xapp-base",
accounts: { tank: { botToken: "xoxb-tank" } },
accounts: { tank: { botToken: "xoxb-tank", appToken: "xapp-tank" } },
},
},
} as unknown as OpenClawConfig;
@ -108,6 +108,32 @@ describe("createAccountListHelpers", () => {
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: {

View File

@ -55,12 +55,22 @@ export function createAccountListHelpers(
// default would start a duplicate provider on the same credentials.
const channel = cfg.channels?.[channelKey];
const base = channel as Record<string, unknown> | undefined;
const hasBaseTokens = Boolean(base?.botToken || base?.appToken || base?.token);
if (hasBaseTokens) {
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>>;
const everyAccountHasOwnTokens = ids.every((id) => {
const acct = accounts[id];
return acct && Boolean(acct.botToken || acct.appToken || acct.token);
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