From f3403708a3afde367394b6699ecf822059fa2383 Mon Sep 17 00:00:00 2001 From: Cavit Erginsoy Date: Mon, 11 May 2026 22:26:28 +0100 Subject: [PATCH] fix(cron): tighten session mirror classification --- src/cron/isolated-agent/delivery-dispatch.ts | 2 + .../outbound/outbound-session.test-helpers.ts | 2 + src/infra/outbound/outbound-session.test.ts | 14 ++++- src/infra/outbound/outbound-session.ts | 60 +++++++++++++------ 4 files changed, 60 insertions(+), 18 deletions(-) diff --git a/src/cron/isolated-agent/delivery-dispatch.ts b/src/cron/isolated-agent/delivery-dispatch.ts index dcb333ba4728..84938f88a85c 100644 --- a/src/cron/isolated-agent/delivery-dispatch.ts +++ b/src/cron/isolated-agent/delivery-dispatch.ts @@ -1026,6 +1026,8 @@ export async function dispatchCronDelivery( sessionKey: deliverySessionKey, agentId: params.agentId, text: mirrorText, + // Keep cron delivery mirrors text-first: non-audio attachment names + // are folded into mirrorText so media does not replace delivered text. mediaUrls: undefined, storePath: resolveStorePath(params.cfgWithAgentDefaults.session?.store, { agentId: resolveAgentIdFromSessionKey(deliverySessionKey), diff --git a/src/infra/outbound/outbound-session.test-helpers.ts b/src/infra/outbound/outbound-session.test-helpers.ts index bc3ac5ecb7a1..460414cd377c 100644 --- a/src/infra/outbound/outbound-session.test-helpers.ts +++ b/src/infra/outbound/outbound-session.test-helpers.ts @@ -585,6 +585,8 @@ export function setMinimalOutboundSessionPluginRegistryForTests(): void { capabilities: { chatTypes: ["direct", "group", "channel"] }, }), messaging: { + parseExplicitTarget: ({ raw }) => + raw.startsWith("spaces/") ? { to: raw, chatType: "group" } : null, targetPrefixes: ["fallbackchat"], }, }, diff --git a/src/infra/outbound/outbound-session.test.ts b/src/infra/outbound/outbound-session.test.ts index 555e1a34387b..88fdf0aa745b 100644 --- a/src/infra/outbound/outbound-session.test.ts +++ b/src/infra/outbound/outbound-session.test.ts @@ -363,7 +363,7 @@ describe("resolveOutboundSessionRoute", () => { }, }, { - name: "FallbackChat space-style target", + name: "FallbackChat plugin parser classifies space-style target", cfg: baseConfig, channel: "fallbackchat", target: "spaces/AAA", @@ -386,6 +386,18 @@ describe("resolveOutboundSessionRoute", () => { chatType: "direct", }, }, + { + name: "FallbackChat explicit thread prefix", + cfg: baseConfig, + channel: "fallbackchat", + target: "thread:abc", + expected: { + sessionKey: "agent:main:fallbackchat:channel:abc", + from: "fallbackchat:channel:abc", + to: "channel:abc", + chatType: "channel", + }, + }, ] satisfies NamedRouteCase[])("$name", async ({ name: _name, ...params }) => { await expectResolvedRoute(params); }); diff --git a/src/infra/outbound/outbound-session.ts b/src/infra/outbound/outbound-session.ts index f80927a6d54e..32017c737a85 100644 --- a/src/infra/outbound/outbound-session.ts +++ b/src/infra/outbound/outbound-session.ts @@ -50,7 +50,44 @@ function stripProviderPrefix(raw: string, channel: string): string { } function stripKindPrefix(raw: string): string { - return raw.replace(/^(user|channel|group|conversation|room|dm):/i, "").trim(); + return raw.replace(/^(user|channel|group|conversation|room|dm|thread):/i, "").trim(); +} + +const FALLBACK_TARGET_KIND_PREFIXES: Array<{ kind: ChatType; pattern: RegExp }> = [ + { kind: "direct", pattern: /^(user:|dm:)/i }, + { kind: "channel", pattern: /^(channel:|conversation:|thread:)/i }, + { kind: "group", pattern: /^(group:|room:)/i }, +]; + +function normalizeInferredPeerKind(value: ChatType | undefined): ChatType | undefined { + return value === "direct" || value === "group" || value === "channel" ? value : undefined; +} + +function inferPeerKindFromPlugin(params: { + plugin: ReturnType; + targets: readonly string[]; +}): ChatType | undefined { + for (const target of params.targets) { + const inferred = normalizeInferredPeerKind( + params.plugin?.messaging?.parseExplicitTarget?.({ raw: target })?.chatType ?? + params.plugin?.messaging?.inferTargetChatType?.({ to: target }), + ); + if (inferred) { + return inferred; + } + } + return undefined; +} + +function inferPeerKindFromFallbackPrefixes(targets: readonly string[]): ChatType | undefined { + for (const target of targets) { + for (const fallback of FALLBACK_TARGET_KIND_PREFIXES) { + if (fallback.pattern.test(target)) { + return fallback.kind; + } + } + } + return undefined; } function inferPeerKind(params: { @@ -81,22 +118,11 @@ function inferPeerKind(params: { (target, index, values): target is string => Boolean(target) && values.indexOf(target) === index, ); - for (const target of targets) { - const inferred = plugin?.messaging?.inferTargetChatType?.({ to: target }); - if (inferred === "direct" || inferred === "group" || inferred === "channel") { - return inferred; - } - } - if (targets.some((target) => /^(@|<@!?|user:|dm:|c2c:|users\/)/i.test(target))) { - return "direct"; - } - if (targets.some((target) => /^(channel:|conversation:|thread:|channels\/)/i.test(target))) { - return "channel"; - } - if (targets.some((target) => /^(#|group:|room:|spaces\/|groups\/|rooms\/)/i.test(target))) { - return "group"; - } - return "direct"; + return ( + inferPeerKindFromPlugin({ plugin, targets }) ?? + inferPeerKindFromFallbackPrefixes(targets) ?? + "direct" + ); } function resolveFallbackSession(