Skip to content

Commit 636a2d2

Browse files
author
Joshua Chittick
committed
refactor: extract Slack and Lark settings launchers
1 parent 6e7c56c commit 636a2d2

File tree

4 files changed

+189
-165
lines changed

4 files changed

+189
-165
lines changed

packages/ims/lark/client.ts

Lines changed: 11 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ import {
66
getGitHubInfoForUser,
77
getLarkAppCredentials,
88
getLarkTargetChannels,
9-
getWebHost,
10-
getWebPort,
119
getWorkspaces,
1210
} from "@/config";
1311
import { isThreadActive, markThreadActive } from "@/config/local/settings";
@@ -24,6 +22,7 @@ import { executeIncomingFlow } from "@/ims/shared/incoming-executor";
2422
import { buildIncomingContext } from "@/ims/shared/incoming-normalizer";
2523
import { parseIncomingCommand } from "@/ims/shared/command-router";
2624
import { createRuntimeController } from "@/ims/shared/runtime-controller";
25+
import { sendLarkSettingsCard } from "./settings";
2726

2827
let larkRuntimeStarted = false;
2928

@@ -223,10 +222,6 @@ function parseLarkText(content: string | undefined): string {
223222
}
224223
}
225224

226-
function getLocalSettingsUrl(): string {
227-
return `http://${getWebHost()}:${getWebPort()}/`;
228-
}
229-
230225
async function buildLarkContext(
231226
channelId: string,
232227
threadId: string,
@@ -263,71 +258,19 @@ async function sendMessage(
263258
}
264259

265260
async function sendSettingsCard(channelId: string, threadId: string): Promise<string | undefined> {
266-
const settingsUrl = getLocalSettingsUrl();
267-
logLarkEvent("Lark settings UI launcher triggered", {
261+
return sendLarkSettingsCard({
268262
channelId,
269263
threadId,
270-
settingsUrl,
264+
sendInteractive: (card) =>
265+
sendLarkMessage({
266+
channelId,
267+
threadId,
268+
msgType: "interactive",
269+
content: card,
270+
}),
271+
sendText: (text) => sendMessage(channelId, threadId, text, true),
272+
logEvent: logLarkEvent,
271273
});
272-
const card = {
273-
config: {
274-
wide_screen_mode: true,
275-
},
276-
header: {
277-
template: "blue",
278-
title: {
279-
tag: "plain_text",
280-
content: "Ode Settings",
281-
},
282-
},
283-
elements: [
284-
{
285-
tag: "markdown",
286-
content: `Configure this chat in the local settings UI.\\n\\nChannel: \`${channelId}\``,
287-
},
288-
{
289-
tag: "action",
290-
actions: [
291-
{
292-
tag: "button",
293-
text: {
294-
tag: "plain_text",
295-
content: "Open Local Setting",
296-
},
297-
type: "primary",
298-
url: settingsUrl,
299-
},
300-
],
301-
},
302-
],
303-
};
304-
305-
try {
306-
const messageId = await sendLarkMessage({
307-
channelId,
308-
threadId,
309-
msgType: "interactive",
310-
content: card as unknown as Record<string, unknown>,
311-
});
312-
logLarkEvent("Lark settings card sent", {
313-
channelId,
314-
threadId,
315-
messageId: messageId ?? "",
316-
});
317-
return messageId;
318-
} catch {
319-
logLarkEvent("Lark settings card failed, sending fallback text", {
320-
channelId,
321-
threadId,
322-
});
323-
const fallbackText = [
324-
"Ode settings",
325-
`Open: ${settingsUrl}`,
326-
`Channel: ${channelId}`,
327-
"Use this channel in Local Setting to configure provider/model/directory.",
328-
].join("\n");
329-
return sendMessage(channelId, threadId, fallbackText, true);
330-
}
331274
}
332275

333276
async function updateMessage(

packages/ims/lark/settings.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { getWebHost, getWebPort } from "@/config";
2+
3+
function getLocalSettingsUrl(): string {
4+
return `http://${getWebHost()}:${getWebPort()}/`;
5+
}
6+
7+
export async function sendLarkSettingsCard(params: {
8+
channelId: string;
9+
threadId: string;
10+
sendInteractive: (card: Record<string, unknown>) => Promise<string | undefined>;
11+
sendText: (text: string) => Promise<string | undefined>;
12+
logEvent: (message: string, payload: Record<string, unknown>) => void;
13+
}): Promise<string | undefined> {
14+
const { channelId, threadId, sendInteractive, sendText, logEvent } = params;
15+
const settingsUrl = getLocalSettingsUrl();
16+
logEvent("Lark settings UI launcher triggered", {
17+
channelId,
18+
threadId,
19+
settingsUrl,
20+
});
21+
22+
const card = {
23+
config: {
24+
wide_screen_mode: true,
25+
},
26+
header: {
27+
template: "blue",
28+
title: {
29+
tag: "plain_text",
30+
content: "Ode Settings",
31+
},
32+
},
33+
elements: [
34+
{
35+
tag: "markdown",
36+
content: `Configure this chat in the local settings UI.\n\nChannel: \`${channelId}\``,
37+
},
38+
{
39+
tag: "action",
40+
actions: [
41+
{
42+
tag: "button",
43+
text: {
44+
tag: "plain_text",
45+
content: "Open Local Setting",
46+
},
47+
type: "primary",
48+
url: settingsUrl,
49+
},
50+
],
51+
},
52+
],
53+
};
54+
55+
try {
56+
const messageId = await sendInteractive(card as unknown as Record<string, unknown>);
57+
logEvent("Lark settings card sent", {
58+
channelId,
59+
threadId,
60+
messageId: messageId ?? "",
61+
});
62+
return messageId;
63+
} catch {
64+
logEvent("Lark settings card failed, sending fallback text", {
65+
channelId,
66+
threadId,
67+
});
68+
const fallbackText = [
69+
"Ode settings",
70+
`Open: ${settingsUrl}`,
71+
`Channel: ${channelId}`,
72+
"Use this channel in Local Setting to configure provider/model/directory.",
73+
].join("\n");
74+
return sendText(fallbackText);
75+
}
76+
}

packages/ims/slack/client.ts

Lines changed: 3 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,13 @@
11
import { App } from "@slack/bolt";
22
import { WebClient } from "@slack/web-api";
3-
import { existsSync } from "fs";
43
import { join } from "path";
54
import {
65
getSlackTargetChannels,
76
getSlackBotTokens,
87
invalidateOdeConfigCache,
98
getChannelAgentProvider,
10-
getChannelModel,
11-
getOpenCodeModels,
12-
getKiloModels,
13-
isAgentEnabled,
149
getGitHubInfoForUser,
1510
getChannelSystemMessage,
16-
resolveChannelCwd,
1711
} from "@/config";
1812
import { markdownToSlack, splitForSlack, truncateForSlack } from "./formatter";
1913
import {
@@ -31,6 +25,7 @@ import { getSlackActionApiUrl } from "./config";
3125
import { fetchThreadHistoryByClient } from "./message-history";
3226
import { registerSlackMessageRouter } from "./message-router";
3327
import { syncSlackWorkspace } from "@/core/web/local-settings";
28+
import { describeSlackSettingsIssues, postSlackGeneralSettingsLauncher } from "./settings";
3429

3530
export interface MessageContext {
3631
channelId: string;
@@ -200,95 +195,6 @@ function truncateToken(token: string): string {
200195
return `${token.slice(0, 6)}...${token.slice(-4)}`;
201196
}
202197

203-
function describeSettingsIssues(channelId: string): string[] {
204-
const issues: string[] = [];
205-
const provider = getChannelAgentProvider(channelId);
206-
const model = getChannelModel(channelId);
207-
const { workingDirectory } = resolveChannelCwd(channelId);
208-
const normalizeModel = (value: string) => value.trim().toLowerCase();
209-
210-
if (!isAgentEnabled(provider)) {
211-
issues.push(`Agent not enabled: ${provider}`);
212-
}
213-
214-
if (provider === "opencode") {
215-
const models = getOpenCodeModels();
216-
const modelSet = new Set(models.map(normalizeModel));
217-
if (!model) {
218-
issues.push("Model not configured.");
219-
} else if (!modelSet.has(normalizeModel(model))) {
220-
issues.push("Model not available in configured OpenCode models.");
221-
}
222-
} else if (provider === "kilo") {
223-
const models = getKiloModels();
224-
const modelSet = new Set(models.map(normalizeModel));
225-
if (!model) {
226-
issues.push("Model not configured.");
227-
} else if (!modelSet.has(normalizeModel(model))) {
228-
issues.push("Model not available in configured Kilo models.");
229-
}
230-
}
231-
232-
if (!workingDirectory) {
233-
issues.push("Working directory not configured.");
234-
} else if (!existsSync(workingDirectory)) {
235-
issues.push(`Working directory not found: ${workingDirectory}`);
236-
}
237-
238-
return issues;
239-
}
240-
241-
type SettingsLauncherButton = {
242-
actionId: string;
243-
label: string;
244-
};
245-
246-
function buildSettingsLauncherBlocks(
247-
channelId: string,
248-
description: string,
249-
buttons: SettingsLauncherButton[]
250-
): any[] {
251-
return [
252-
{
253-
type: "section",
254-
text: {
255-
type: "mrkdwn",
256-
text: description,
257-
},
258-
},
259-
{
260-
type: "actions",
261-
elements: buttons.map((button) => ({
262-
type: "button",
263-
action_id: button.actionId,
264-
text: { type: "plain_text", text: button.label },
265-
value: channelId,
266-
})),
267-
},
268-
];
269-
}
270-
271-
async function postGeneralSettingsLauncher(
272-
channelId: string,
273-
userId: string,
274-
client: WebClient
275-
): Promise<void> {
276-
await client.chat.postEphemeral({
277-
channel: channelId,
278-
user: userId,
279-
text: "Open settings",
280-
blocks: buildSettingsLauncherBlocks(
281-
channelId,
282-
"Choose which settings page to open.",
283-
[
284-
{ actionId: "open_general_settings_modal", label: "general setting" },
285-
{ actionId: "open_settings_modal", label: "channel setting" },
286-
{ actionId: "open_github_token_modal", label: "github info" },
287-
]
288-
),
289-
});
290-
}
291-
292198
async function syncWorkspaceAfterMention(
293199
channelId: string,
294200
workspace: { workspaceId?: string; workspaceName?: string } | undefined
@@ -577,8 +483,8 @@ export function setupMessageHandlers(): void {
577483
},
578484
isThreadActive,
579485
markThreadActive,
580-
postGeneralSettingsLauncher,
581-
describeSettingsIssues,
486+
postGeneralSettingsLauncher: postSlackGeneralSettingsLauncher,
487+
describeSettingsIssues: describeSlackSettingsIssues,
582488
getChannelAgentProvider,
583489
handleStopCommand: (channelId, threadId) => coreRuntime.handleStopCommand(channelId, threadId),
584490
handleIncomingMessage: (context, text) => coreRuntime.handleIncomingMessage(context, text),

0 commit comments

Comments
 (0)