fix: make heartbeat routing orthogonal to conversation mode (#463)
This commit is contained in:
@@ -145,9 +145,10 @@ export function resolveConversationKey(
|
||||
|
||||
/**
|
||||
* Pure function: resolve the conversation key for heartbeat/sendToAgent.
|
||||
* In per-chat mode, uses the full channel:chatId of the last-active target.
|
||||
* In per-channel mode, respects heartbeatConversation setting.
|
||||
* In shared mode with overrides, respects override channels when using last-active.
|
||||
* The heartbeat setting is orthogonal to conversation mode:
|
||||
* - "dedicated" always returns "heartbeat" (isolated conversation)
|
||||
* - "<channel>" always routes to that channel's conversation
|
||||
* - "last-active" routes to wherever the user last messaged from
|
||||
*/
|
||||
export function resolveHeartbeatConversationKey(
|
||||
conversationMode: string | undefined,
|
||||
@@ -159,23 +160,26 @@ export function resolveHeartbeatConversationKey(
|
||||
if (conversationMode === 'disabled') return 'default';
|
||||
const hb = heartbeatConversation || 'last-active';
|
||||
|
||||
// "dedicated" always gets its own conversation, regardless of mode
|
||||
if (hb === 'dedicated') return 'heartbeat';
|
||||
|
||||
// Explicit channel name — route to that channel's conversation
|
||||
if (hb !== 'last-active') return hb;
|
||||
|
||||
// "last-active" handling varies by mode
|
||||
if (conversationMode === 'per-chat') {
|
||||
if (hb === 'dedicated') return 'heartbeat';
|
||||
if (hb === 'last-active' && lastActiveChannel && lastActiveChatId) {
|
||||
if (lastActiveChannel && lastActiveChatId) {
|
||||
return `${lastActiveChannel.toLowerCase()}:${lastActiveChatId}`;
|
||||
}
|
||||
// Fall back to shared if no last-active target
|
||||
return 'shared';
|
||||
}
|
||||
|
||||
if (conversationMode === 'per-channel') {
|
||||
if (hb === 'dedicated') return 'heartbeat';
|
||||
if (hb === 'last-active') return lastActiveChannel ?? 'shared';
|
||||
return hb;
|
||||
return lastActiveChannel ?? 'shared';
|
||||
}
|
||||
|
||||
// shared mode — if last-active and overrides exist, respect the override channel
|
||||
if (hb === 'last-active' && conversationOverrides.size > 0 && lastActiveChannel) {
|
||||
// shared mode — if overrides exist, respect the override channel
|
||||
if (conversationOverrides.size > 0 && lastActiveChannel) {
|
||||
return resolveConversationKey(lastActiveChannel, conversationMode, conversationOverrides);
|
||||
}
|
||||
|
||||
|
||||
@@ -140,10 +140,25 @@ describe('resolveHeartbeatConversationKey', () => {
|
||||
expect(resolveHeartbeatConversationKey('shared', 'last-active', overrides, undefined)).toBe('shared');
|
||||
});
|
||||
|
||||
it('returns "shared" in shared mode even with overrides when heartbeat is not last-active', () => {
|
||||
// Non-last-active heartbeat in shared mode always returns 'shared'
|
||||
// --- dedicated is orthogonal to mode ---
|
||||
|
||||
it('returns \"heartbeat\" in shared mode with dedicated', () => {
|
||||
expect(resolveHeartbeatConversationKey('shared', 'dedicated', new Set())).toBe('heartbeat');
|
||||
});
|
||||
|
||||
it('returns \"heartbeat\" in shared mode with dedicated even when overrides exist', () => {
|
||||
const overrides = new Set(['slack']);
|
||||
expect(resolveHeartbeatConversationKey('shared', 'dedicated', overrides, 'slack')).toBe('shared');
|
||||
expect(resolveHeartbeatConversationKey('shared', 'dedicated', overrides, 'slack')).toBe('heartbeat');
|
||||
});
|
||||
|
||||
// --- explicit channel is orthogonal to mode ---
|
||||
|
||||
it('returns explicit channel name in shared mode', () => {
|
||||
expect(resolveHeartbeatConversationKey('shared', 'discord', new Set(), 'telegram')).toBe('discord');
|
||||
});
|
||||
|
||||
it('returns explicit channel name in per-chat mode', () => {
|
||||
expect(resolveHeartbeatConversationKey('per-chat', 'discord', new Set(), 'telegram', '12345')).toBe('discord');
|
||||
});
|
||||
|
||||
// --- per-chat mode ---
|
||||
@@ -152,7 +167,7 @@ describe('resolveHeartbeatConversationKey', () => {
|
||||
expect(resolveHeartbeatConversationKey('per-chat', 'last-active', new Set(), 'telegram', '12345')).toBe('telegram:12345');
|
||||
});
|
||||
|
||||
it('returns "heartbeat" in per-chat mode with dedicated', () => {
|
||||
it('returns \"heartbeat\" in per-chat mode with dedicated', () => {
|
||||
expect(resolveHeartbeatConversationKey('per-chat', 'dedicated', new Set(), 'telegram', '12345')).toBe('heartbeat');
|
||||
});
|
||||
|
||||
@@ -160,7 +175,7 @@ describe('resolveHeartbeatConversationKey', () => {
|
||||
expect(resolveHeartbeatConversationKey('per-chat', 'last-active', new Set(), 'telegram', undefined)).toBe('shared');
|
||||
});
|
||||
|
||||
it('falls back to "shared" in per-chat mode when no last-active target', () => {
|
||||
it('falls back to \"shared\" in per-chat mode when no last-active target', () => {
|
||||
expect(resolveHeartbeatConversationKey('per-chat', 'last-active', new Set(), undefined, undefined)).toBe('shared');
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user