refactor: centralize thread helpers

This commit is contained in:
Peter Steinberger
2026-01-07 20:01:19 +01:00
parent 42b637bbc8
commit cb9f8146c4
4 changed files with 60 additions and 15 deletions

View File

@@ -34,3 +34,17 @@ export function formatAgentEnvelope(params: AgentEnvelopeParams): string {
const header = `[${parts.join(" ")}]`; const header = `[${parts.join(" ")}]`;
return `${header} ${params.body}`; return `${header} ${params.body}`;
} }
export function formatThreadStarterEnvelope(params: {
provider: string;
author?: string;
timestamp?: number | Date;
body: string;
}): string {
return formatAgentEnvelope({
provider: params.provider,
from: params.author,
timestamp: params.timestamp,
body: params.body,
});
}

View File

@@ -27,7 +27,10 @@ import {
listNativeCommandSpecs, listNativeCommandSpecs,
shouldHandleTextCommands, shouldHandleTextCommands,
} from "../auto-reply/commands-registry.js"; } from "../auto-reply/commands-registry.js";
import { formatAgentEnvelope } from "../auto-reply/envelope.js"; import {
formatAgentEnvelope,
formatThreadStarterEnvelope,
} from "../auto-reply/envelope.js";
import { dispatchReplyFromConfig } from "../auto-reply/reply/dispatch-from-config.js"; import { dispatchReplyFromConfig } from "../auto-reply/reply/dispatch-from-config.js";
import { import {
buildMentionRegexes, buildMentionRegexes,
@@ -55,6 +58,7 @@ import {
buildAgentSessionKey, buildAgentSessionKey,
resolveAgentRoute, resolveAgentRoute,
} from "../routing/resolve-route.js"; } from "../routing/resolve-route.js";
import { resolveThreadSessionKeys } from "../routing/session-key.js";
import type { RuntimeEnv } from "../runtime.js"; import type { RuntimeEnv } from "../runtime.js";
import { loadWebMedia } from "../web/media.js"; import { loadWebMedia } from "../web/media.js";
import { fetchDiscordApplicationId } from "./probe.js"; import { fetchDiscordApplicationId } from "./probe.js";
@@ -863,9 +867,9 @@ export function createDiscordMessageHandler(params: {
parentId: threadParentId, parentId: threadParentId,
}); });
if (starter?.text) { if (starter?.text) {
const starterEnvelope = formatAgentEnvelope({ const starterEnvelope = formatThreadStarterEnvelope({
provider: "Discord", provider: "Discord",
from: starter.author, author: starter.author,
timestamp: starter.timestamp, timestamp: starter.timestamp,
body: starter.text, body: starter.text,
}); });
@@ -885,13 +889,19 @@ export function createDiscordMessageHandler(params: {
} }
const mediaPayload = buildDiscordMediaPayload(mediaList); const mediaPayload = buildDiscordMediaPayload(mediaList);
const discordTo = `channel:${message.channelId}`; const discordTo = `channel:${message.channelId}`;
const threadKeys = resolveThreadSessionKeys({
baseSessionKey,
threadId: threadChannel ? message.channelId : undefined,
parentSessionKey,
useSuffix: false,
});
const ctxPayload = { const ctxPayload = {
Body: combinedBody, Body: combinedBody,
From: isDirectMessage From: isDirectMessage
? `discord:${author.id}` ? `discord:${author.id}`
: `group:${message.channelId}`, : `group:${message.channelId}`,
To: discordTo, To: discordTo,
SessionKey: baseSessionKey, SessionKey: threadKeys.sessionKey,
AccountId: route.accountId, AccountId: route.accountId,
ChatType: isDirectMessage ? "direct" : "group", ChatType: isDirectMessage ? "direct" : "group",
SenderName: SenderName:
@@ -909,7 +919,7 @@ export function createDiscordMessageHandler(params: {
Surface: "discord" as const, Surface: "discord" as const,
WasMentioned: wasMentioned, WasMentioned: wasMentioned,
MessageSid: message.id, MessageSid: message.id,
ParentSessionKey: parentSessionKey, ParentSessionKey: threadKeys.parentSessionKey,
ThreadStarterBody: threadStarterBody, ThreadStarterBody: threadStarterBody,
ThreadLabel: threadLabel, ThreadLabel: threadLabel,
Timestamp: resolveTimestampMs(message.timestamp), Timestamp: resolveTimestampMs(message.timestamp),

View File

@@ -89,3 +89,20 @@ export function buildAgentPeerSessionKey(params: {
const peerId = (params.peerId ?? "").trim() || "unknown"; const peerId = (params.peerId ?? "").trim() || "unknown";
return `agent:${normalizeAgentId(params.agentId)}:${provider}:${peerKind}:${peerId}`; return `agent:${normalizeAgentId(params.agentId)}:${provider}:${peerKind}:${peerId}`;
} }
export function resolveThreadSessionKeys(params: {
baseSessionKey: string;
threadId?: string | null;
parentSessionKey?: string;
useSuffix?: boolean;
}): { sessionKey: string; parentSessionKey?: string } {
const threadId = (params.threadId ?? "").trim();
if (!threadId) {
return { sessionKey: params.baseSessionKey, parentSessionKey: undefined };
}
const useSuffix = params.useSuffix ?? true;
const sessionKey = useSuffix
? `${params.baseSessionKey}:thread:${threadId}`
: params.baseSessionKey;
return { sessionKey, parentSessionKey: params.parentSessionKey };
}

View File

@@ -14,7 +14,10 @@ import {
listNativeCommandSpecs, listNativeCommandSpecs,
shouldHandleTextCommands, shouldHandleTextCommands,
} from "../auto-reply/commands-registry.js"; } from "../auto-reply/commands-registry.js";
import { formatAgentEnvelope } from "../auto-reply/envelope.js"; import {
formatAgentEnvelope,
formatThreadStarterEnvelope,
} from "../auto-reply/envelope.js";
import { dispatchReplyFromConfig } from "../auto-reply/reply/dispatch-from-config.js"; import { dispatchReplyFromConfig } from "../auto-reply/reply/dispatch-from-config.js";
import { import {
buildMentionRegexes, buildMentionRegexes,
@@ -34,6 +37,7 @@ import {
resolveStorePath, resolveStorePath,
updateLastRoute, updateLastRoute,
} from "../config/sessions.js"; } from "../config/sessions.js";
import { resolveThreadSessionKeys } from "../routing/session-key.js";
import { danger, logVerbose, shouldLogVerbose } from "../globals.js"; import { danger, logVerbose, shouldLogVerbose } from "../globals.js";
import { enqueueSystemEvent } from "../infra/system-events.js"; import { enqueueSystemEvent } from "../infra/system-events.js";
import { getChildLogger } from "../logging.js"; import { getChildLogger } from "../logging.js";
@@ -930,12 +934,12 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
const isThreadReply = const isThreadReply =
hasThreadTs && hasThreadTs &&
(threadTs !== message.ts || Boolean(message.parent_user_id)); (threadTs !== message.ts || Boolean(message.parent_user_id));
const threadSessionKey = const threadKeys = resolveThreadSessionKeys({
isThreadReply && threadTs baseSessionKey,
? `${baseSessionKey}:thread:${threadTs}` threadId: isThreadReply ? threadTs : undefined,
: undefined; parentSessionKey: isThreadReply ? baseSessionKey : undefined,
const parentSessionKey = isThreadReply ? baseSessionKey : undefined; });
const sessionKey = threadSessionKey ?? baseSessionKey; const sessionKey = threadKeys.sessionKey;
enqueueSystemEvent(`${inboundLabel}: ${preview}`, { enqueueSystemEvent(`${inboundLabel}: ${preview}`, {
sessionKey, sessionKey,
contextKey: `slack:message:${message.channel}:${message.ts ?? "unknown"}`, contextKey: `slack:message:${message.channel}:${message.ts ?? "unknown"}`,
@@ -978,9 +982,9 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
: null; : null;
const starterName = starterUser?.name ?? starter.userId ?? "Unknown"; const starterName = starterUser?.name ?? starter.userId ?? "Unknown";
const starterWithId = `${starter.text}\n[slack message id: ${starter.ts ?? threadTs} channel: ${message.channel}]`; const starterWithId = `${starter.text}\n[slack message id: ${starter.ts ?? threadTs} channel: ${message.channel}]`;
threadStarterBody = formatAgentEnvelope({ threadStarterBody = formatThreadStarterEnvelope({
provider: "Slack", provider: "Slack",
from: starterName, author: starterName,
timestamp: starter.ts timestamp: starter.ts
? Math.round(Number(starter.ts) * 1000) ? Math.round(Number(starter.ts) * 1000)
: undefined, : undefined,
@@ -1007,7 +1011,7 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
Surface: "slack" as const, Surface: "slack" as const,
MessageSid: message.ts, MessageSid: message.ts,
ReplyToId: message.thread_ts ?? message.ts, ReplyToId: message.thread_ts ?? message.ts,
ParentSessionKey: parentSessionKey, ParentSessionKey: threadKeys.parentSessionKey,
ThreadStarterBody: threadStarterBody, ThreadStarterBody: threadStarterBody,
ThreadLabel: threadLabel, ThreadLabel: threadLabel,
Timestamp: message.ts ? Math.round(Number(message.ts) * 1000) : undefined, Timestamp: message.ts ? Math.round(Number(message.ts) * 1000) : undefined,