Skip to content

Commit db0ebee

Browse files
849261680steipete
andauthored
fix(discord): keep default account online when adding named accounts (openclaw#96401)
* fix(discord): keep default SecretRef active with named accounts * fix(discord): align implicit default secret surfaces --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
1 parent 0008de2 commit db0ebee

2 files changed

Lines changed: 116 additions & 0 deletions

File tree

extensions/discord/src/secret-config-contract.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
collectNestedChannelFieldAssignments,
44
collectSimpleChannelFieldAssignments,
55
getChannelSurface,
6+
hasConfiguredSecretInputValue,
67
isBaseFieldActiveForChannelSurface,
78
isEnabledFlag,
89
isRecord,
@@ -93,6 +94,21 @@ export function collectRuntimeConfigAssignments(params: {
9394
return;
9495
}
9596
const { channel: discord, surface } = resolved;
97+
const hasImplicitDefault =
98+
surface.hasExplicitAccounts &&
99+
!surface.accounts.some(({ accountId }) => accountId === "default") &&
100+
[discord.token, params.context.env.DISCORD_BOT_TOKEN].some((value) =>
101+
hasConfiguredSecretInputValue(value, params.defaults),
102+
);
103+
if (hasImplicitDefault) {
104+
// Account discovery treats either token source as an implicit default. Keep it in
105+
// secret collection so named accounts cannot orphan the default's inherited refs.
106+
surface.accounts.push({
107+
accountId: "default",
108+
account: {},
109+
enabled: surface.channelEnabled,
110+
});
111+
}
96112
collectSimpleChannelFieldAssignments({
97113
channelKey: "discord",
98114
field: "token",

src/secrets/runtime-discord-surface.test.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
/** Tests Discord secret surfaces in runtime preparation. */
2+
import fs from "node:fs/promises";
3+
import path from "node:path";
24
import { describe, expect, it } from "vitest";
5+
import { withTempDir } from "../test-helpers/temp-dir.js";
36
import "./runtime-discord.test-support.ts";
47
import {
58
asConfig,
@@ -71,6 +74,103 @@ describe("secrets runtime snapshot discord surface", () => {
7174
);
7275
});
7376

77+
it.skipIf(process.platform === "win32")(
78+
"resolves the implicit default token when named Discord accounts are added",
79+
async () => {
80+
await withTempDir({ prefix: "openclaw-discord-secrets-" }, async (root) => {
81+
const secretsPath = path.join(root, "secrets.json");
82+
await fs.writeFile(
83+
secretsPath,
84+
JSON.stringify({
85+
discord: {
86+
defaultToken: "default-account-token",
87+
secondToken: "second-account-token",
88+
},
89+
}),
90+
"utf8",
91+
);
92+
await fs.chmod(secretsPath, 0o600);
93+
94+
const snapshot = await prepareSecretsRuntimeSnapshot({
95+
config: asConfig({
96+
secrets: {
97+
providers: {
98+
discord_file: {
99+
source: "file",
100+
path: secretsPath,
101+
mode: "json",
102+
},
103+
},
104+
},
105+
channels: {
106+
discord: {
107+
token: {
108+
source: "file",
109+
provider: "discord_file",
110+
id: "/discord/defaultToken",
111+
},
112+
accounts: {
113+
second: {
114+
enabled: true,
115+
token: {
116+
source: "file",
117+
provider: "discord_file",
118+
id: "/discord/secondToken",
119+
},
120+
},
121+
},
122+
},
123+
},
124+
}),
125+
agentDirs: ["/tmp/openclaw-agent-main"],
126+
loadAuthStore: () => loadAuthStoreWithProfiles({}),
127+
});
128+
129+
expect(snapshot.config.channels?.discord?.token).toBe("default-account-token");
130+
expect(snapshot.config.channels?.discord?.accounts?.second?.token).toBe(
131+
"second-account-token",
132+
);
133+
expect(snapshot.warnings.map((warning) => warning.path)).not.toContain(
134+
"channels.discord.token",
135+
);
136+
});
137+
},
138+
);
139+
140+
it("keeps inherited refs active for an env-backed implicit default", async () => {
141+
const snapshot = await prepareSecretsRuntimeSnapshot({
142+
config: asConfig({
143+
channels: {
144+
discord: {
145+
pluralkit: {
146+
token: { source: "env", provider: "default", id: "DISCORD_DEFAULT_PK_TOKEN" },
147+
},
148+
accounts: {
149+
second: {
150+
pluralkit: {
151+
token: { source: "env", provider: "default", id: "DISCORD_SECOND_PK_TOKEN" },
152+
},
153+
},
154+
},
155+
},
156+
},
157+
}),
158+
env: {
159+
DISCORD_BOT_TOKEN: "env-default-token",
160+
DISCORD_DEFAULT_PK_TOKEN: "default-pk-token",
161+
DISCORD_SECOND_PK_TOKEN: "second-pk-token",
162+
},
163+
agentDirs: ["/tmp/openclaw-agent-main"],
164+
loadAuthStore: () => loadAuthStoreWithProfiles({}),
165+
});
166+
167+
expect(snapshot.config.channels?.discord?.pluralkit?.token).toBe("default-pk-token");
168+
expect(snapshot.config.channels?.discord?.accounts?.second?.pluralkit?.token).toBe(
169+
"second-pk-token",
170+
);
171+
expect(snapshot.warnings).toStrictEqual([]);
172+
});
173+
74174
it("fails when non-default Discord account inherits an unresolved top-level token ref", async () => {
75175
await expect(
76176
prepareSecretsRuntimeSnapshot({

0 commit comments

Comments
 (0)