Skip to content

Commit 8746362

Browse files
authored
refactor(slack): move Slack channel code to extensions/slack/src/ (#45621)
Move all Slack channel implementation files from src/slack/ to extensions/slack/src/ and replace originals with shim re-exports. This follows the extension migration pattern for channel plugins. - Copy all .ts files to extensions/slack/src/ (preserving directory structure: monitor/, http/, monitor/events/, monitor/message-handler/) - Transform import paths: external src/ imports use relative paths back to src/, internal slack imports stay relative within extension - Replace all src/slack/ files with shim re-exports pointing to the extension copies - Update tsconfig.plugin-sdk.dts.json rootDir from "src" to "." so the DTS build can follow shim chains into extensions/ - Update write-plugin-sdk-entry-dts.ts re-export path accordingly - Preserve extensions/slack/index.ts, package.json, openclaw.plugin.json, src/channel.ts, src/runtime.ts, src/channel.test.ts (untouched)
1 parent 1650571 commit 8746362

File tree

252 files changed

+20551
-20287
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

252 files changed

+20551
-20287
lines changed
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
import type { OpenClawConfig } from "../../../src/config/config.js";
2+
import {
3+
hasConfiguredSecretInput,
4+
normalizeSecretInputString,
5+
} from "../../../src/config/types.secrets.js";
6+
import type { SlackAccountConfig } from "../../../src/config/types.slack.js";
7+
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../../src/routing/session-key.js";
8+
import type { SlackAccountSurfaceFields } from "./account-surface-fields.js";
9+
import {
10+
mergeSlackAccountConfig,
11+
resolveDefaultSlackAccountId,
12+
type SlackTokenSource,
13+
} from "./accounts.js";
14+
15+
export type SlackCredentialStatus = "available" | "configured_unavailable" | "missing";
16+
17+
export type InspectedSlackAccount = {
18+
accountId: string;
19+
enabled: boolean;
20+
name?: string;
21+
mode?: SlackAccountConfig["mode"];
22+
botToken?: string;
23+
appToken?: string;
24+
signingSecret?: string;
25+
userToken?: string;
26+
botTokenSource: SlackTokenSource;
27+
appTokenSource: SlackTokenSource;
28+
signingSecretSource?: SlackTokenSource;
29+
userTokenSource: SlackTokenSource;
30+
botTokenStatus: SlackCredentialStatus;
31+
appTokenStatus: SlackCredentialStatus;
32+
signingSecretStatus?: SlackCredentialStatus;
33+
userTokenStatus: SlackCredentialStatus;
34+
configured: boolean;
35+
config: SlackAccountConfig;
36+
} & SlackAccountSurfaceFields;
37+
38+
function inspectSlackToken(value: unknown): {
39+
token?: string;
40+
source: Exclude<SlackTokenSource, "env">;
41+
status: SlackCredentialStatus;
42+
} {
43+
const token = normalizeSecretInputString(value);
44+
if (token) {
45+
return {
46+
token,
47+
source: "config",
48+
status: "available",
49+
};
50+
}
51+
if (hasConfiguredSecretInput(value)) {
52+
return {
53+
source: "config",
54+
status: "configured_unavailable",
55+
};
56+
}
57+
return {
58+
source: "none",
59+
status: "missing",
60+
};
61+
}
62+
63+
export function inspectSlackAccount(params: {
64+
cfg: OpenClawConfig;
65+
accountId?: string | null;
66+
envBotToken?: string | null;
67+
envAppToken?: string | null;
68+
envUserToken?: string | null;
69+
}): InspectedSlackAccount {
70+
const accountId = normalizeAccountId(
71+
params.accountId ?? resolveDefaultSlackAccountId(params.cfg),
72+
);
73+
const merged = mergeSlackAccountConfig(params.cfg, accountId);
74+
const enabled = params.cfg.channels?.slack?.enabled !== false && merged.enabled !== false;
75+
const allowEnv = accountId === DEFAULT_ACCOUNT_ID;
76+
const mode = merged.mode ?? "socket";
77+
const isHttpMode = mode === "http";
78+
79+
const configBot = inspectSlackToken(merged.botToken);
80+
const configApp = inspectSlackToken(merged.appToken);
81+
const configSigningSecret = inspectSlackToken(merged.signingSecret);
82+
const configUser = inspectSlackToken(merged.userToken);
83+
84+
const envBot = allowEnv
85+
? normalizeSecretInputString(params.envBotToken ?? process.env.SLACK_BOT_TOKEN)
86+
: undefined;
87+
const envApp = allowEnv
88+
? normalizeSecretInputString(params.envAppToken ?? process.env.SLACK_APP_TOKEN)
89+
: undefined;
90+
const envUser = allowEnv
91+
? normalizeSecretInputString(params.envUserToken ?? process.env.SLACK_USER_TOKEN)
92+
: undefined;
93+
94+
const botToken = configBot.token ?? envBot;
95+
const appToken = configApp.token ?? envApp;
96+
const signingSecret = configSigningSecret.token;
97+
const userToken = configUser.token ?? envUser;
98+
const botTokenSource: SlackTokenSource = configBot.token
99+
? "config"
100+
: configBot.status === "configured_unavailable"
101+
? "config"
102+
: envBot
103+
? "env"
104+
: "none";
105+
const appTokenSource: SlackTokenSource = configApp.token
106+
? "config"
107+
: configApp.status === "configured_unavailable"
108+
? "config"
109+
: envApp
110+
? "env"
111+
: "none";
112+
const signingSecretSource: SlackTokenSource = configSigningSecret.token
113+
? "config"
114+
: configSigningSecret.status === "configured_unavailable"
115+
? "config"
116+
: "none";
117+
const userTokenSource: SlackTokenSource = configUser.token
118+
? "config"
119+
: configUser.status === "configured_unavailable"
120+
? "config"
121+
: envUser
122+
? "env"
123+
: "none";
124+
125+
return {
126+
accountId,
127+
enabled,
128+
name: merged.name?.trim() || undefined,
129+
mode,
130+
botToken,
131+
appToken,
132+
...(isHttpMode ? { signingSecret } : {}),
133+
userToken,
134+
botTokenSource,
135+
appTokenSource,
136+
...(isHttpMode ? { signingSecretSource } : {}),
137+
userTokenSource,
138+
botTokenStatus: configBot.token
139+
? "available"
140+
: configBot.status === "configured_unavailable"
141+
? "configured_unavailable"
142+
: envBot
143+
? "available"
144+
: "missing",
145+
appTokenStatus: configApp.token
146+
? "available"
147+
: configApp.status === "configured_unavailable"
148+
? "configured_unavailable"
149+
: envApp
150+
? "available"
151+
: "missing",
152+
...(isHttpMode
153+
? {
154+
signingSecretStatus: configSigningSecret.token
155+
? "available"
156+
: configSigningSecret.status === "configured_unavailable"
157+
? "configured_unavailable"
158+
: "missing",
159+
}
160+
: {}),
161+
userTokenStatus: configUser.token
162+
? "available"
163+
: configUser.status === "configured_unavailable"
164+
? "configured_unavailable"
165+
: envUser
166+
? "available"
167+
: "missing",
168+
configured: isHttpMode
169+
? (configBot.status !== "missing" || Boolean(envBot)) &&
170+
configSigningSecret.status !== "missing"
171+
: (configBot.status !== "missing" || Boolean(envBot)) &&
172+
(configApp.status !== "missing" || Boolean(envApp)),
173+
config: merged,
174+
groupPolicy: merged.groupPolicy,
175+
textChunkLimit: merged.textChunkLimit,
176+
mediaMaxMb: merged.mediaMaxMb,
177+
reactionNotifications: merged.reactionNotifications,
178+
reactionAllowlist: merged.reactionAllowlist,
179+
replyToMode: merged.replyToMode,
180+
replyToModeByChatType: merged.replyToModeByChatType,
181+
actions: merged.actions,
182+
slashCommand: merged.slashCommand,
183+
dm: merged.dm,
184+
channels: merged.channels,
185+
};
186+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { SlackAccountConfig } from "../../../src/config/types.js";
2+
3+
export type SlackAccountSurfaceFields = {
4+
groupPolicy?: SlackAccountConfig["groupPolicy"];
5+
textChunkLimit?: SlackAccountConfig["textChunkLimit"];
6+
mediaMaxMb?: SlackAccountConfig["mediaMaxMb"];
7+
reactionNotifications?: SlackAccountConfig["reactionNotifications"];
8+
reactionAllowlist?: SlackAccountConfig["reactionAllowlist"];
9+
replyToMode?: SlackAccountConfig["replyToMode"];
10+
replyToModeByChatType?: SlackAccountConfig["replyToModeByChatType"];
11+
actions?: SlackAccountConfig["actions"];
12+
slashCommand?: SlackAccountConfig["slashCommand"];
13+
dm?: SlackAccountConfig["dm"];
14+
channels?: SlackAccountConfig["channels"];
15+
};
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { describe, expect, it } from "vitest";
2+
import { resolveSlackAccount } from "./accounts.js";
3+
4+
describe("resolveSlackAccount allowFrom precedence", () => {
5+
it("prefers accounts.default.allowFrom over top-level for default account", () => {
6+
const resolved = resolveSlackAccount({
7+
cfg: {
8+
channels: {
9+
slack: {
10+
allowFrom: ["top"],
11+
accounts: {
12+
default: {
13+
botToken: "xoxb-default",
14+
appToken: "xapp-default",
15+
allowFrom: ["default"],
16+
},
17+
},
18+
},
19+
},
20+
},
21+
accountId: "default",
22+
});
23+
24+
expect(resolved.config.allowFrom).toEqual(["default"]);
25+
});
26+
27+
it("falls back to top-level allowFrom for named account without override", () => {
28+
const resolved = resolveSlackAccount({
29+
cfg: {
30+
channels: {
31+
slack: {
32+
allowFrom: ["top"],
33+
accounts: {
34+
work: { botToken: "xoxb-work", appToken: "xapp-work" },
35+
},
36+
},
37+
},
38+
},
39+
accountId: "work",
40+
});
41+
42+
expect(resolved.config.allowFrom).toEqual(["top"]);
43+
});
44+
45+
it("does not inherit default account allowFrom for named account when top-level is absent", () => {
46+
const resolved = resolveSlackAccount({
47+
cfg: {
48+
channels: {
49+
slack: {
50+
accounts: {
51+
default: {
52+
botToken: "xoxb-default",
53+
appToken: "xapp-default",
54+
allowFrom: ["default"],
55+
},
56+
work: { botToken: "xoxb-work", appToken: "xapp-work" },
57+
},
58+
},
59+
},
60+
},
61+
accountId: "work",
62+
});
63+
64+
expect(resolved.config.allowFrom).toBeUndefined();
65+
});
66+
67+
it("falls back to top-level dm.allowFrom when allowFrom alias is unset", () => {
68+
const resolved = resolveSlackAccount({
69+
cfg: {
70+
channels: {
71+
slack: {
72+
dm: { allowFrom: ["U123"] },
73+
accounts: {
74+
work: { botToken: "xoxb-work", appToken: "xapp-work" },
75+
},
76+
},
77+
},
78+
},
79+
accountId: "work",
80+
});
81+
82+
expect(resolved.config.allowFrom).toBeUndefined();
83+
expect(resolved.config.dm?.allowFrom).toEqual(["U123"]);
84+
});
85+
});

0 commit comments

Comments
 (0)