Skip to content

Commit 67aa7a5

Browse files
authored
Merge pull request #81 from odefun/feat/discord-thread-actions-17709849
Add Discord workspace support and thread-first action parity
2 parents 0538690 + bbbde9e commit 67aa7a5

33 files changed

+2233
-199
lines changed

bun.lock

Lines changed: 41 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ode",
3-
"version": "0.0.50",
3+
"version": "0.0.51",
44
"description": "Coding anywhere with your coding agents connected",
55
"module": "packages/core/index.ts",
66
"type": "module",
@@ -34,6 +34,7 @@
3434
"@slack/socket-mode": "^2.0.5",
3535
"@slack/web-api": "^7.13.0",
3636
"@supabase/supabase-js": "^2.90.1",
37+
"discord.js": "^14.25.1",
3738
"ioredis": "^5.9.2",
3839
"pino": "^10.1.1",
3940
"pino-pretty": "^13.1.3",

packages/agents/shared.ts

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import type { OpenCodeMessageContext, OpenCodeOptions, PromptPart, SlackContext
66
import { getSlackActionApiUrl } from "@/config";
77

88
export function buildSystemPrompt(slack?: SlackContext): string {
9+
const platform = slack?.platform === "discord" ? "discord" : "slack";
10+
const platformLabel = platform === "discord" ? "Discord" : "Slack";
911
const lines = [
1012
"COMMUNICATION STYLE:",
1113
"- Be concise and conversational - this is chat, not documentation",
@@ -29,7 +31,7 @@ export function buildSystemPrompt(slack?: SlackContext): string {
2931
];
3032

3133
if (slack) {
32-
lines.push("SLACK CONTEXT:");
34+
lines.push(`${platformLabel.toUpperCase()} CONTEXT:`);
3335
lines.push(`- Channel: ${slack.channelId}`);
3436
lines.push(`- Thread: ${slack.threadId}`);
3537
lines.push(`- User: <@${slack.userId}>`);
@@ -38,26 +40,44 @@ export function buildSystemPrompt(slack?: SlackContext): string {
3840
}
3941

4042
lines.push("");
41-
lines.push("SLACK ACTIONS:");
42-
if (slack.hasCustomSlackTool) {
43+
lines.push(`${platformLabel.toUpperCase()} ACTIONS:`);
44+
const baseUrl = slack.odeSlackApiUrl ?? getSlackActionApiUrl();
45+
if (platform === "slack" && slack.hasCustomSlackTool) {
4346
lines.push("- Use `ode_action` tool for Slack actions (messages, reactions, thread history, questions, uploads).");
4447
} else {
45-
const baseUrl = slack.odeSlackApiUrl ?? getSlackActionApiUrl();
46-
lines.push("- Use bash + curl to call the Ode Slack API.");
48+
lines.push("- Use bash + curl to call the Ode action API.");
4749
lines.push(`- Endpoint: ${baseUrl}/action`);
48-
lines.push("- Payload: {\"action\":\"post_message\",\"channelId\":\"...\",\"threadId\":\"...\",\"messageId\":\"...\",\"text\":\"...\"}");
50+
lines.push(
51+
platform === "discord"
52+
? "- Payload: {\"platform\":\"discord\",\"action\":\"post_message\",\"channelId\":\"...\",\"messageId\":\"...\",\"text\":\"...\"}"
53+
: "- Payload: {\"action\":\"post_message\",\"channelId\":\"...\",\"threadId\":\"...\",\"messageId\":\"...\",\"text\":\"...\"}"
54+
);
55+
}
56+
if (platform === "discord") {
57+
lines.push("- Supported actions: get_guilds, get_channels, post_message, update_message, create_thread_from_message, get_thread_messages, ask_user, add_reaction, get_user_info, upload_file.");
58+
lines.push("- Required fields: channelId for message/reaction/question/upload actions; threadId for get_thread_messages; messageId + emoji for reactions; userId (or \"@me\") for get_user_info; filePath for upload_file.");
59+
lines.push("- add_reaction schema: { platform: \"discord\", action: \"add_reaction\", channelId: string, messageId: string, emoji: \"thumbsup\" | \"eyes\" | \"ok_hand\" }");
60+
} else {
61+
lines.push("- Supported actions: post_message, add_reaction, get_thread_messages, ask_user, get_user_info, upload_file.");
62+
lines.push("- Required fields: channelId; threadId for thread actions; messageId + emoji for reactions; userId for get_user_info.");
63+
lines.push("- add_reaction schema: { action: \"add_reaction\", channelId: string, messageId: string, emoji: \"thumbsup\" | \"eyes\" | \"ok_hand\" }");
4964
}
50-
lines.push("- Supported actions: post_message, add_reaction, get_thread_messages, ask_user, get_user_info, upload_file.");
51-
lines.push("- Required fields: channelId; threadId for thread actions; messageId + emoji for reactions; userId for get_user_info.");
52-
lines.push("- add_reaction schema: { action: \"add_reaction\", channelId: string, messageId: string, emoji: \"thumbsup\" | \"eyes\" | \"ok_hand\" }");
5365
lines.push("- You can use any tool available via bash, curl");
5466
lines.push("");
55-
lines.push("IMPORTANT: Your text output is automatically posted to Slack.");
56-
lines.push("- When asking the user to choose options, you can send an ask_user Slack action, do NOT also output text - the buttons are enough.");
67+
lines.push(`IMPORTANT: Your text output is automatically posted to ${platformLabel}.`);
68+
lines.push(
69+
platform === "discord"
70+
? "- When asking the user to choose options, use ask_user action and do NOT also output text - the posted question is enough."
71+
: "- When asking the user to choose options, you can send an ask_user Slack action, do NOT also output text - the buttons are enough."
72+
);
5773
lines.push("- Only output text OR use a messaging tool, never both.");
5874
lines.push("");
5975
lines.push("FORMATTING:");
60-
lines.push("- Slack uses *bold* and _italic_ (not **bold** or *italic*)");
76+
lines.push(
77+
platform === "discord"
78+
? "- Discord supports markdown like **bold**, _italic_, and code fences."
79+
: "- Slack uses *bold* and _italic_ (not **bold** or *italic*)"
80+
);
6181
lines.push("- Use ` for inline code and ``` for code blocks");
6282
lines.push("- Keep responses readable on mobile screens");
6383
lines.push("");

packages/agents/test/cli-command.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,20 @@ describe("agent cli command formatting", () => {
6161
expect(systemPrompt).toContain("Preferred branch format before PR: `feat/<short-slug>-<threadShortId>`");
6262
});
6363

64+
it("builds Discord action instructions for Discord context", () => {
65+
const systemPrompt = buildSystemPrompt({
66+
platform: "discord",
67+
channelId: "C123",
68+
threadId: "T456",
69+
userId: "U789",
70+
});
71+
72+
expect(systemPrompt).toContain("DISCORD CONTEXT:");
73+
expect(systemPrompt).toContain("DISCORD ACTIONS:");
74+
expect(systemPrompt).toContain('"platform":"discord"');
75+
expect(systemPrompt).toContain("Supported actions: get_guilds, get_channels, post_message");
76+
});
77+
6478
it("builds the OpenCode curl command", () => {
6579
const command = buildOpenCodeCommand("http://127.0.0.1:8080", "session-2", {
6680
directory: "/tmp/project",

packages/agents/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export interface OpenCodeOptions {
1313
}
1414

1515
export interface SlackContext {
16+
platform?: "slack" | "discord";
1617
channelId: string;
1718
threadId: string;
1819
userId: string;

packages/config/dashboard-config.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export type DashboardConfig = {
3737
};
3838
workspaces: {
3939
id: string;
40+
type: "slack" | "discord";
4041
name: string;
4142
domain: string;
4243
status: "active" | "paused";
@@ -45,6 +46,7 @@ export type DashboardConfig = {
4546
lastSync: string;
4647
slackAppToken?: string;
4748
slackBotToken?: string;
49+
discordBotToken?: string;
4850
channelDetails: {
4951
id: string;
5052
name: string;
@@ -59,6 +61,7 @@ export type DashboardConfig = {
5961

6062
const defaultWorkspace: DashboardConfig["workspaces"][number] = {
6163
id: "workspace-1",
64+
type: "slack",
6265
name: "Workspace 1",
6366
domain: "",
6467
status: "active",
@@ -177,9 +180,12 @@ const sanitizeWorkspace = (
177180
: [];
178181
const slackAppToken = asString(workspace.slackAppToken, "");
179182
const slackBotToken = asString(workspace.slackBotToken, "");
183+
const discordBotToken = asString(workspace.discordBotToken, "");
184+
const type = workspace.type === "discord" ? "discord" : "slack";
180185

181186
return {
182187
id: asString(workspace.id) || fallbackId,
188+
type,
183189
name: asString(workspace.name) || fallbackName,
184190
domain: asString(workspace.domain),
185191
status: asStatus(workspace.status),
@@ -188,6 +194,7 @@ const sanitizeWorkspace = (
188194
lastSync: asString(workspace.lastSync),
189195
slackAppToken: slackAppToken || undefined,
190196
slackBotToken: slackBotToken || undefined,
197+
discordBotToken: discordBotToken || undefined,
191198
channelDetails,
192199
};
193200
};

packages/config/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ export {
2222
getSlackAppTokens,
2323
getSlackBotTokens,
2424
getSlackTargetChannels,
25+
getDiscordBotTokens,
26+
getDiscordTargetChannels,
2527
getDefaultCwd,
2628
getGitHubInfoForUser,
2729
getUserGeneralSettings,

0 commit comments

Comments
 (0)