Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ode",
"version": "0.1.16",
"version": "0.1.17",
"description": "Coding anywhere with your coding agents connected",
"module": "packages/core/index.ts",
"type": "module",
Expand Down
8 changes: 4 additions & 4 deletions packages/core/kernel/runtime-facade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ export class KernelRuntimeFacade {
evaluate: (event) => defaultInboundPolicy({
selfMessage: event.selfMessage,
threadOwnerMessage: event.threadOwnerMessage,
threadParticipantBotCount: event.threadParticipantBotCount,
isTopLevel: event.isTopLevel,
hasAnyMention: event.hasAnyMention ?? event.mentionedBot,
mentionedBot: event.mentionedBot,
activeThread: event.activeThread,
normalizedText: event.normalizedText,
Expand All @@ -104,8 +104,8 @@ export class KernelRuntimeFacade {
const decision = defaultInboundPolicy({
selfMessage: event.selfMessage,
threadOwnerMessage: event.threadOwnerMessage,
threadParticipantBotCount: event.threadParticipantBotCount,
isTopLevel: event.isTopLevel,
hasAnyMention: event.hasAnyMention ?? event.mentionedBot,
mentionedBot: event.mentionedBot,
activeThread: event.activeThread,
normalizedText: event.normalizedText,
Expand Down Expand Up @@ -163,8 +163,8 @@ export class KernelRuntimeFacade {
userId,
selfMessage: false,
threadOwnerMessage: true,
threadParticipantBotCount: 1,
isTopLevel: false,
hasAnyMention: false,
mentionedBot: true,
activeThread: true,
rawText: selection,
Expand Down Expand Up @@ -267,8 +267,8 @@ export class KernelRuntimeFacade {
userId: context.userId,
selfMessage: false,
threadOwnerMessage: true,
threadParticipantBotCount: 1,
isTopLevel: false,
hasAnyMention: false,
mentionedBot: true,
activeThread: true,
rawText: text,
Expand Down
2 changes: 1 addition & 1 deletion packages/core/model/raw-inbound-event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ export type RawInboundEvent = Readonly<{
userId: string;
selfMessage: boolean;
threadOwnerMessage: boolean;
threadParticipantBotCount: number;
isTopLevel: boolean;
hasAnyMention?: boolean;
mentionedBot: boolean;
activeThread: boolean;
rawText: string;
Expand Down
4 changes: 0 additions & 4 deletions packages/core/test/runtime-e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ function toInboundEvent(params: {
userId: params.userId,
selfMessage: false,
threadOwnerMessage: true,
threadParticipantBotCount: 1,
isTopLevel: params.isTopLevel ?? false,
mentionedBot: params.mentionedBot ?? true,
activeThread: params.activeThread ?? true,
Expand Down Expand Up @@ -319,7 +318,6 @@ describe("core runtime e2e", () => {
userId: "UE2E-dis",
selfMessage: false,
threadOwnerMessage: true,
threadParticipantBotCount: 1,
isTopLevel: false,
mentionedBot: true,
activeThread: true,
Expand Down Expand Up @@ -358,7 +356,6 @@ describe("core runtime e2e", () => {
userId: "UE2E-lark",
selfMessage: false,
threadOwnerMessage: true,
threadParticipantBotCount: 1,
isTopLevel: true,
mentionedBot: false,
activeThread: false,
Expand All @@ -378,7 +375,6 @@ describe("core runtime e2e", () => {
userId: "UE2E-lark",
selfMessage: false,
threadOwnerMessage: true,
threadParticipantBotCount: 1,
isTopLevel: false,
mentionedBot: true,
activeThread: true,
Expand Down
1 change: 0 additions & 1 deletion packages/core/test/runtime-resilience-e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,6 @@ function toInboundEvent(params: {
userId: params.userId,
selfMessage: false,
threadOwnerMessage: true,
threadParticipantBotCount: 1,
isTopLevel: false,
mentionedBot: true,
activeThread: true,
Expand Down
22 changes: 11 additions & 11 deletions packages/ims/discord/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
} from "@/config";
import { findReplyThreadIdByStatusMessageTs } from "@/config/local/sessions";
import {
getThreadParticipantBotIds,
isThreadActive,
loadSession,
markThreadActive,
Expand Down Expand Up @@ -464,6 +463,8 @@ async function startDiscordRuntimeInternal(reason: string): Promise<boolean> {

const threadId = message.channel.id;
const text = message.content.trim();
const mentioned = isBotMentioned(message, client.user.id);
const hasAnyMention = (message?.mentions?.users?.size ?? 0) > 0;
if (await maybeHandleLauncherCommand({
text,
message,
Expand All @@ -472,7 +473,6 @@ async function startDiscordRuntimeInternal(reason: string): Promise<boolean> {
})) {
return;
}
const mentioned = isBotMentioned(message, client.user.id);
const active = isThreadActive(parentId, threadId, processorId);
const normalizedText = mentioned ? cleanBotMention(text, client.user.id) : text;
const threadSession = loadSession(parentId, threadId);
Expand All @@ -487,8 +487,8 @@ async function startDiscordRuntimeInternal(reason: string): Promise<boolean> {
userId: message.author.id,
selfMessage: false,
threadOwnerMessage: threadSession?.threadOwnerUserId === message.author.id,
threadParticipantBotCount: getThreadParticipantBotIds(parentId, threadId).length,
isTopLevel: false,
hasAnyMention,
mentionedBot: mentioned,
activeThread: active,
rawText: text,
Expand Down Expand Up @@ -554,14 +554,14 @@ async function startDiscordRuntimeInternal(reason: string): Promise<boolean> {
rawChannelId: parentId,
threadId: thread.id,
replyThreadId: thread.id,
messageId: message.id,
userId: message.author.id,
selfMessage: false,
threadOwnerMessage: true,
threadParticipantBotCount: getThreadParticipantBotIds(parentId, thread.id).length,
isTopLevel: false,
mentionedBot: true,
activeThread: false,
messageId: message.id,
userId: message.author.id,
selfMessage: false,
threadOwnerMessage: true,
isTopLevel: false,
hasAnyMention: true,
mentionedBot: true,
activeThread: false,
rawText: message.content,
normalizedText: topLevelText,
receivedAtMs: Date.now(),
Expand Down
4 changes: 2 additions & 2 deletions packages/ims/lark/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
} from "@/config";
import { findReplyThreadIdByStatusMessageTs } from "@/config/local/sessions";
import {
getThreadParticipantBotIds,
isThreadActive,
loadSession,
markThreadActive,
Expand Down Expand Up @@ -1086,6 +1085,7 @@ async function processLarkIncomingEvent(event: LarkIncomingEvent, processorAppId
const isMentioned = botOpenId
? (mentions.includes(botOpenId) || isBotMentionedInText(rawText, botOpenId))
: false;
const hasAnyMention = mentions.length > 0;
const active = isThreadActive(channelId, threadId, processorId);
const threadSession = loadSession(channelId, threadId);
const text = stripLarkMentionMarkup(rawText);
Expand All @@ -1100,8 +1100,8 @@ async function processLarkIncomingEvent(event: LarkIncomingEvent, processorAppId
userId: senderOpenId,
selfMessage: isSelfMessage,
threadOwnerMessage: threadSession?.threadOwnerUserId === senderOpenId,
threadParticipantBotCount: getThreadParticipantBotIds(channelId, threadId).length,
isTopLevel: topLevelMessage,
hasAnyMention,
mentionedBot: isMentioned,
activeThread: active,
rawText,
Expand Down
32 changes: 32 additions & 0 deletions packages/ims/shared/inbound-policy.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { describe, expect, it } from "bun:test";
import { defaultInboundPolicy } from "./inbound-policy";

describe("defaultInboundPolicy", () => {
it("drops thread messages that mention another target", () => {
const decision = defaultInboundPolicy({
selfMessage: false,
threadOwnerMessage: true,
isTopLevel: false,
hasAnyMention: true,
mentionedBot: false,
activeThread: true,
normalizedText: "<@other> handle this",
});

expect(decision).toEqual({ kind: "ignore", reason: "not_mentioned_and_inactive" });
});

it("keeps active-thread owner follow-ups without mentions", () => {
const decision = defaultInboundPolicy({
selfMessage: false,
threadOwnerMessage: true,
isTopLevel: false,
hasAnyMention: false,
mentionedBot: false,
activeThread: true,
normalizedText: "continue",
});

expect(decision).toEqual({ kind: "message", text: "continue" });
});
});
6 changes: 5 additions & 1 deletion packages/ims/shared/inbound-policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import type { InboundDecision } from "@/core/model/inbound-decision";
export function defaultInboundPolicy(params: {
selfMessage: boolean;
threadOwnerMessage: boolean;
threadParticipantBotCount: number;
isTopLevel: boolean;
hasAnyMention: boolean;
mentionedBot: boolean;
activeThread: boolean;
normalizedText: string;
Expand All @@ -14,6 +14,10 @@ export function defaultInboundPolicy(params: {
return { kind: "ignore", reason: "self_message" };
}

if (!params.isTopLevel && params.hasAnyMention && !params.mentionedBot) {
return { kind: "ignore", reason: "not_mentioned_and_inactive" };
}

if (!params.isTopLevel && !params.mentionedBot) {
if (!params.activeThread) {
return { kind: "ignore", reason: "not_mentioned_and_inactive" };
Expand Down
2 changes: 0 additions & 2 deletions packages/ims/slack/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
} from "@/config";
import { markdownToSlack, splitForSlack, truncateForSlack } from "./formatter";
import {
getThreadParticipantBotIds,
isThreadActive,
loadSession,
} from "@/config/local/sessions";
Expand Down Expand Up @@ -482,7 +481,6 @@ export function setupMessageHandlers(): void {
const session = loadSession(channelId, threadId);
return session?.threadOwnerUserId === userId;
},
getThreadParticipantBotCount: (channelId, threadId) => getThreadParticipantBotIds(channelId, threadId).length,
isThreadActive,
postGeneralSettingsLauncher: postSlackGeneralSettingsLauncher,
describeSettingsIssues: describeSlackSettingsIssues,
Expand Down
2 changes: 0 additions & 2 deletions packages/ims/slack/message-router.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ function createDeps(overrides: Partial<Parameters<typeof registerSlackMessageRou
setChannelWorkspaceName: () => {},
setChannelWorkspaceAuth: () => {},
isThreadOwner: () => false,
getThreadParticipantBotCount: () => 1,
isThreadActive: () => false,
postGeneralSettingsLauncher: async () => {},
describeSettingsIssues: () => [],
Expand Down Expand Up @@ -243,7 +242,6 @@ describe("registerSlackMessageRouter", () => {
},
isThreadActive: () => true,
isThreadOwner: () => true,
getThreadParticipantBotCount: () => 2,
handleInboundEvent,
});

Expand Down
29 changes: 2 additions & 27 deletions packages/ims/slack/message-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ type RouterDeps = {
auth: { workspaceId?: string; workspaceName?: string; botToken?: string; [key: string]: unknown } | undefined
) => void;
isThreadOwner: (channelId: string, threadId: string, userId: string) => boolean;
getThreadParticipantBotCount: (channelId: string, threadId: string) => number;
isThreadActive: (channelId: string, threadId: string, botId: string) => boolean;
postGeneralSettingsLauncher: (channelId: string, userId: string, client: any) => Promise<void>;
describeSettingsIssues: (channelId: string) => string[];
Expand Down Expand Up @@ -118,15 +117,6 @@ function extractIncomingMessageData(message: any): IncomingMessageData | null {
};
}

function shouldDropForOtherMentions(text: string, isMention: boolean): boolean {
return extractMentionedUserIds(text).length > 0 && !isMention;
}

function tokenLast6(token?: string): string | undefined {
if (!token) return undefined;
return token.slice(-6);
}

async function maybeRefreshWorkspaceForMention(params: {
deps: RouterDeps;
channelId: string;
Expand Down Expand Up @@ -275,6 +265,7 @@ export function registerSlackMessageRouter(deps: RouterDeps): void {
|| (Boolean(identity.botId) && (messageBotId === identity.botId || messageBotProfileId === identity.botId));

const mentionedUserIds = extractMentionedUserIds(text);
const hasAnyMention = mentionedUserIds.length > 0;
const isMention = currentBotUserId ? mentionedUserIds.includes(currentBotUserId) : false;
const cleanText = stripBotMention(text, currentBotUserId);
logSlackTrace("Slack mention parse", {
Expand All @@ -298,7 +289,6 @@ export function registerSlackMessageRouter(deps: RouterDeps): void {
const runtimeBotId = contextBotToken ?? workspaceAuth?.botToken ?? "default";
const isTopLevel = threadId === messageId;
const threadOwnerMessage = deps.isThreadOwner(channelId, threadId, userId);
const threadParticipantBotCount = deps.getThreadParticipantBotCount(channelId, threadId);
const threadActive = deps.isThreadActive(channelId, threadId, runtimeBotId);
const inboundEvent: RawInboundEvent = {
platform: "slack",
Expand All @@ -311,8 +301,8 @@ export function registerSlackMessageRouter(deps: RouterDeps): void {
userId,
selfMessage,
threadOwnerMessage,
threadParticipantBotCount,
isTopLevel,
hasAnyMention,
mentionedBot: isMention,
activeThread: threadActive,
rawText: text,
Expand All @@ -328,7 +318,6 @@ export function registerSlackMessageRouter(deps: RouterDeps): void {
mentionedUserIds,
isMention,
threadOwnerMessage,
threadParticipantBotCount,
threadActive,
flowType: flowResult.type,
flowReason: flowResult.type === "ignore" ? flowResult.reason : undefined,
Expand All @@ -347,20 +336,6 @@ export function registerSlackMessageRouter(deps: RouterDeps): void {
});
}

if (shouldDropForOtherMentions(text, isMention)) {
log.info("[DROP] Mentions other user", {
channelId,
threadId,
messageId,
imName: workspaceAuth?.workspaceName ?? deps.getChannelWorkspaceName(channelId) ?? "unknown",
botTokenLast6: tokenLast6(workspaceAuth?.botToken),
botUserId: currentBotUserId || "unknown",
mentionedUserIds,
isMention,
});
return;
}

if (await maybeHandleLauncherCommand({
deps,
cleanText,
Expand Down
2 changes: 1 addition & 1 deletion packages/ims/slack/slack-inbound-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ export class SlackInboundAdapter implements InboundAdapter {
return defaultInboundPolicy({
selfMessage: event.selfMessage,
threadOwnerMessage: event.threadOwnerMessage,
threadParticipantBotCount: event.threadParticipantBotCount,
isTopLevel: event.isTopLevel,
hasAnyMention: event.hasAnyMention ?? event.mentionedBot,
mentionedBot: event.mentionedBot,
activeThread: event.activeThread,
normalizedText: event.normalizedText,
Expand Down