refactor: normalize inbound context
This commit is contained in:
@@ -308,6 +308,7 @@ export function createSessionStatusTool(opts?: {
|
||||
|
||||
const isGroup =
|
||||
resolved.entry.chatType === "group" ||
|
||||
resolved.entry.chatType === "channel" ||
|
||||
resolved.entry.chatType === "room" ||
|
||||
resolved.key.startsWith("group:") ||
|
||||
resolved.key.includes(":group:") ||
|
||||
|
||||
@@ -120,12 +120,18 @@ export async function resolveReplyDirectives(params: {
|
||||
// Prefer CommandBody/RawBody (clean message without structural context) for directive parsing.
|
||||
// Keep `Body`/`BodyStripped` as the best-available prompt text (may include context).
|
||||
const commandSource =
|
||||
sessionCtx.BodyForCommands ??
|
||||
sessionCtx.CommandBody ??
|
||||
sessionCtx.RawBody ??
|
||||
sessionCtx.Transcript ??
|
||||
sessionCtx.BodyStripped ??
|
||||
sessionCtx.Body ??
|
||||
ctx.BodyForCommands ??
|
||||
ctx.CommandBody ??
|
||||
ctx.RawBody ??
|
||||
"";
|
||||
const promptSource = sessionCtx.BodyForAgent ?? sessionCtx.BodyStripped ?? sessionCtx.Body ?? "";
|
||||
const commandText = commandSource || promptSource;
|
||||
const command = buildCommandContext({
|
||||
ctx,
|
||||
cfg,
|
||||
@@ -162,7 +168,7 @@ export async function resolveReplyDirectives(params: {
|
||||
.filter((alias): alias is string => Boolean(alias))
|
||||
.filter((alias) => !reservedCommands.has(alias.toLowerCase()));
|
||||
const allowStatusDirective = allowTextCommands && command.isAuthorizedSender;
|
||||
let parsedDirectives = parseInlineDirectives(commandSource, {
|
||||
let parsedDirectives = parseInlineDirectives(commandText, {
|
||||
modelAliases: configuredAliases,
|
||||
allowStatusDirective,
|
||||
});
|
||||
@@ -253,6 +259,7 @@ export async function resolveReplyDirectives(params: {
|
||||
cleanedBody = stripInlineStatus(cleanedBody).cleaned;
|
||||
}
|
||||
|
||||
sessionCtx.BodyForAgent = cleanedBody;
|
||||
sessionCtx.Body = cleanedBody;
|
||||
sessionCtx.BodyStripped = cleanedBody;
|
||||
|
||||
@@ -402,7 +409,7 @@ export async function resolveReplyDirectives(params: {
|
||||
return {
|
||||
kind: "continue",
|
||||
result: {
|
||||
commandSource,
|
||||
commandSource: commandText,
|
||||
command,
|
||||
allowTextCommands,
|
||||
skillCommands,
|
||||
|
||||
@@ -135,7 +135,9 @@ export async function handleInlineActions(params: {
|
||||
].filter((entry): entry is string => Boolean(entry));
|
||||
const rewrittenBody = promptParts.join("\n\n");
|
||||
ctx.Body = rewrittenBody;
|
||||
ctx.BodyForAgent = rewrittenBody;
|
||||
sessionCtx.Body = rewrittenBody;
|
||||
sessionCtx.BodyForAgent = rewrittenBody;
|
||||
sessionCtx.BodyStripped = rewrittenBody;
|
||||
cleanedBody = rewrittenBody;
|
||||
}
|
||||
@@ -153,6 +155,7 @@ export async function handleInlineActions(params: {
|
||||
if (inlineCommand) {
|
||||
cleanedBody = inlineCommand.cleaned;
|
||||
sessionCtx.Body = cleanedBody;
|
||||
sessionCtx.BodyForAgent = cleanedBody;
|
||||
sessionCtx.BodyStripped = cleanedBody;
|
||||
}
|
||||
|
||||
|
||||
@@ -295,7 +295,10 @@ export async function runPreparedReply(
|
||||
abortKey: command.abortKey,
|
||||
messageId: sessionCtx.MessageSid,
|
||||
});
|
||||
const isGroupSession = sessionEntry?.chatType === "group" || sessionEntry?.chatType === "room";
|
||||
const isGroupSession =
|
||||
sessionEntry?.chatType === "group" ||
|
||||
sessionEntry?.chatType === "channel" ||
|
||||
sessionEntry?.chatType === "room";
|
||||
const isMainSession = !isGroupSession && sessionKey === normalizeMainKey(sessionCfg?.mainKey);
|
||||
prefixedBodyBase = await prependSystemEvents({
|
||||
cfg,
|
||||
|
||||
@@ -28,10 +28,10 @@ describe("formatInboundBodyWithSenderMeta", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves escaped newline style when body uses literal \\\\n", () => {
|
||||
it("appends with a real newline even if the body contains literal \\\\n", () => {
|
||||
const ctx: MsgContext = { ChatType: "group", SenderName: "Bob", SenderId: "+222" };
|
||||
expect(formatInboundBodyWithSenderMeta({ ctx, body: "[X] one\\n[X] two" })).toBe(
|
||||
"[X] one\\n[X] two\\n[from: Bob (+222)]",
|
||||
"[X] one\\n[X] two\n[from: Bob (+222)]",
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,28 +1,21 @@
|
||||
import type { MsgContext } from "../templating.js";
|
||||
import { normalizeChatType } from "../../channels/chat-type.js";
|
||||
|
||||
export function formatInboundBodyWithSenderMeta(params: { body: string; ctx: MsgContext }): string {
|
||||
const body = params.body;
|
||||
if (!body.trim()) return body;
|
||||
const chatType = params.ctx.ChatType?.trim().toLowerCase();
|
||||
const chatType = normalizeChatType(params.ctx.ChatType);
|
||||
if (!chatType || chatType === "direct") return body;
|
||||
if (hasSenderMetaLine(body)) return body;
|
||||
|
||||
const senderLabel = formatSenderLabel(params.ctx);
|
||||
if (!senderLabel) return body;
|
||||
|
||||
const lineBreak = resolveBodyLineBreak(body);
|
||||
return `${body}${lineBreak}[from: ${senderLabel}]`;
|
||||
}
|
||||
|
||||
function resolveBodyLineBreak(body: string): string {
|
||||
const hasEscaped = body.includes("\\n");
|
||||
const hasNewline = body.includes("\n");
|
||||
if (hasEscaped && !hasNewline) return "\\n";
|
||||
return "\n";
|
||||
return `${body}\n[from: ${senderLabel}]`;
|
||||
}
|
||||
|
||||
function hasSenderMetaLine(body: string): boolean {
|
||||
return /(^|\n|\\n)\[from:/i.test(body);
|
||||
return /(^|\n)\[from:/i.test(body);
|
||||
}
|
||||
|
||||
function formatSenderLabel(ctx: MsgContext): string | null {
|
||||
|
||||
@@ -25,8 +25,10 @@ import {
|
||||
import { normalizeMainKey } from "../../routing/session-key.js";
|
||||
import { resolveCommandAuthorization } from "../command-auth.js";
|
||||
import type { MsgContext, TemplateContext } from "../templating.js";
|
||||
import { normalizeChatType } from "../../channels/chat-type.js";
|
||||
import { stripMentions, stripStructuralPrefixes } from "./mentions.js";
|
||||
import { formatInboundBodyWithSenderMeta } from "./inbound-sender-meta.js";
|
||||
import { normalizeInboundTextNewlines } from "./inbound-text.js";
|
||||
|
||||
export type SessionInitResult = {
|
||||
sessionCtx: TemplateContext;
|
||||
@@ -126,10 +128,11 @@ export async function initSessionState(params: {
|
||||
let persistedProviderOverride: string | undefined;
|
||||
|
||||
const groupResolution = resolveGroupSessionKey(sessionCtxForState) ?? undefined;
|
||||
const isGroup = ctx.ChatType?.trim().toLowerCase() === "group" || Boolean(groupResolution);
|
||||
const normalizedChatType = normalizeChatType(ctx.ChatType);
|
||||
const isGroup = normalizedChatType != null && normalizedChatType !== "direct" ? true : Boolean(groupResolution);
|
||||
// Prefer CommandBody/RawBody (clean message) for command detection; fall back
|
||||
// to Body which may contain structural context (history, sender labels).
|
||||
const commandSource = ctx.CommandBody ?? ctx.RawBody ?? ctx.Body ?? "";
|
||||
const commandSource = ctx.BodyForCommands ?? ctx.CommandBody ?? ctx.RawBody ?? ctx.Body ?? "";
|
||||
const triggerBodyNormalized = stripStructuralPrefixes(commandSource).trim().toLowerCase();
|
||||
|
||||
// Use CommandBody/RawBody for reset trigger matching (clean message without structural context).
|
||||
@@ -308,7 +311,15 @@ export async function initSessionState(params: {
|
||||
// RawBody is reserved for command/directive parsing and may omit context.
|
||||
BodyStripped: formatInboundBodyWithSenderMeta({
|
||||
ctx,
|
||||
body: bodyStripped ?? ctx.Body ?? ctx.CommandBody ?? ctx.RawBody ?? "",
|
||||
body: normalizeInboundTextNewlines(
|
||||
bodyStripped ??
|
||||
ctx.BodyForAgent ??
|
||||
ctx.Body ??
|
||||
ctx.CommandBody ??
|
||||
ctx.RawBody ??
|
||||
ctx.BodyForCommands ??
|
||||
"",
|
||||
),
|
||||
}),
|
||||
SessionId: sessionId,
|
||||
IsNewSession: isNewSession ? "true" : "false",
|
||||
|
||||
@@ -253,6 +253,7 @@ export function buildStatusMessage(args: StatusArgs): string {
|
||||
|
||||
const isGroupSession =
|
||||
entry?.chatType === "group" ||
|
||||
entry?.chatType === "channel" ||
|
||||
entry?.chatType === "room" ||
|
||||
Boolean(args.sessionKey?.includes(":group:")) ||
|
||||
Boolean(args.sessionKey?.includes(":channel:")) ||
|
||||
|
||||
@@ -8,6 +8,11 @@ export type OriginatingChannelType = ChannelId | InternalMessageChannel;
|
||||
|
||||
export type MsgContext = {
|
||||
Body?: string;
|
||||
/**
|
||||
* Agent prompt body (may include envelope/history/context). Prefer this for prompt shaping.
|
||||
* Should use real newlines (`\n`), not escaped `\\n`.
|
||||
*/
|
||||
BodyForAgent?: string;
|
||||
/**
|
||||
* Raw message body without structural context (history, sender labels).
|
||||
* Legacy alias for CommandBody. Falls back to Body if not set.
|
||||
@@ -17,6 +22,11 @@ export type MsgContext = {
|
||||
* Prefer for command detection; RawBody is treated as legacy alias.
|
||||
*/
|
||||
CommandBody?: string;
|
||||
/**
|
||||
* Command parsing body. Prefer this over CommandBody/RawBody when set.
|
||||
* Should be the "clean" text (no history/sender context).
|
||||
*/
|
||||
BodyForCommands?: string;
|
||||
CommandArgs?: CommandArgs;
|
||||
From?: string;
|
||||
To?: string;
|
||||
@@ -46,6 +56,8 @@ export type MsgContext = {
|
||||
Prompt?: string;
|
||||
MaxChars?: number;
|
||||
ChatType?: string;
|
||||
/** Human label for envelope headers (conversation label, not sender). */
|
||||
ConversationLabel?: string;
|
||||
GroupSubject?: string;
|
||||
GroupRoom?: string;
|
||||
GroupSpace?: string;
|
||||
|
||||
@@ -110,7 +110,9 @@ const formatAge = (ms: number | null | undefined) => {
|
||||
function classifyKey(key: string, entry?: SessionEntry): SessionRow["kind"] {
|
||||
if (key === "global") return "global";
|
||||
if (key === "unknown") return "unknown";
|
||||
if (entry?.chatType === "group" || entry?.chatType === "room") return "group";
|
||||
if (entry?.chatType === "group" || entry?.chatType === "channel" || entry?.chatType === "room") {
|
||||
return "group";
|
||||
}
|
||||
if (key.startsWith("group:") || key.includes(":group:") || key.includes(":channel:")) {
|
||||
return "group";
|
||||
}
|
||||
|
||||
@@ -19,7 +19,9 @@ import type { HeartbeatStatus, SessionStatus, StatusSummary } from "./status.typ
|
||||
const classifyKey = (key: string, entry?: SessionEntry): SessionStatus["kind"] => {
|
||||
if (key === "global") return "global";
|
||||
if (key === "unknown") return "unknown";
|
||||
if (entry?.chatType === "group" || entry?.chatType === "room") return "group";
|
||||
if (entry?.chatType === "group" || entry?.chatType === "channel" || entry?.chatType === "room") {
|
||||
return "group";
|
||||
}
|
||||
if (key.startsWith("group:") || key.includes(":group:") || key.includes(":channel:")) {
|
||||
return "group";
|
||||
}
|
||||
|
||||
@@ -136,6 +136,6 @@ export function resolveGroupSessionKey(ctx: MsgContext): GroupKeyResolution | nu
|
||||
legacyKey,
|
||||
channel: resolvedProvider,
|
||||
id: id || raw || from,
|
||||
chatType: resolvedKind === "channel" ? "room" : "group",
|
||||
chatType: resolvedKind === "channel" ? "channel" : "group",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,7 +7,12 @@ export type SessionScope = "per-sender" | "global";
|
||||
|
||||
export type SessionChannelId = ChannelId | "webchat";
|
||||
|
||||
export type SessionChatType = "direct" | "group" | "room";
|
||||
export type SessionChatType =
|
||||
| "direct"
|
||||
| "group"
|
||||
| "channel"
|
||||
// Legacy alias for "channel".
|
||||
| "room";
|
||||
|
||||
export type SessionEntry = {
|
||||
/**
|
||||
|
||||
@@ -39,7 +39,13 @@ export const SessionSchema = z
|
||||
.object({
|
||||
channel: z.string().optional(),
|
||||
chatType: z
|
||||
.union([z.literal("direct"), z.literal("group"), z.literal("room")])
|
||||
.union([
|
||||
z.literal("direct"),
|
||||
z.literal("group"),
|
||||
z.literal("channel"),
|
||||
// Legacy alias for "channel".
|
||||
z.literal("room"),
|
||||
])
|
||||
.optional(),
|
||||
keyPrefix: z.string().optional(),
|
||||
})
|
||||
|
||||
@@ -221,13 +221,16 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
|
||||
|
||||
const ctxPayload = {
|
||||
Body: combinedBody,
|
||||
BodyForAgent: combinedBody,
|
||||
RawBody: baseText,
|
||||
CommandBody: baseText,
|
||||
BodyForCommands: baseText,
|
||||
From: effectiveFrom,
|
||||
To: effectiveTo,
|
||||
SessionKey: autoThreadContext?.SessionKey ?? threadKeys.sessionKey,
|
||||
AccountId: route.accountId,
|
||||
ChatType: isDirectMessage ? "direct" : "group",
|
||||
ChatType: isDirectMessage ? "direct" : "channel",
|
||||
ConversationLabel: fromLabel,
|
||||
SenderName: data.member?.nickname ?? author.globalName ?? author.username,
|
||||
SenderId: author.id,
|
||||
SenderUsername: author.username,
|
||||
|
||||
@@ -569,16 +569,20 @@ async function dispatchDiscordCommandInteraction(params: {
|
||||
id: isDirectMessage ? user.id : channelId,
|
||||
},
|
||||
});
|
||||
const conversationLabel = isDirectMessage ? (user.globalName ?? user.username) : channelId;
|
||||
const ctxPayload = {
|
||||
Body: prompt,
|
||||
BodyForAgent: prompt,
|
||||
CommandBody: prompt,
|
||||
BodyForCommands: prompt,
|
||||
CommandArgs: commandArgs,
|
||||
From: isDirectMessage ? `discord:${user.id}` : `group:${channelId}`,
|
||||
To: `slash:${user.id}`,
|
||||
SessionKey: `agent:${route.agentId}:${sessionPrefix}:${user.id}`,
|
||||
CommandTargetSessionKey: route.sessionKey,
|
||||
AccountId: route.accountId,
|
||||
ChatType: isDirectMessage ? "direct" : "group",
|
||||
ChatType: isDirectMessage ? "direct" : isGroupDm ? "group" : "channel",
|
||||
ConversationLabel: conversationLabel,
|
||||
GroupSubject: isGuild ? interaction.guild?.name : undefined,
|
||||
GroupSystemPrompt: isGuild
|
||||
? (() => {
|
||||
|
||||
@@ -59,7 +59,9 @@ export function loadSessionEntry(sessionKey: string) {
|
||||
export function classifySessionKey(key: string, entry?: SessionEntry): GatewaySessionRow["kind"] {
|
||||
if (key === "global") return "global";
|
||||
if (key === "unknown") return "unknown";
|
||||
if (entry?.chatType === "group" || entry?.chatType === "room") return "group";
|
||||
if (entry?.chatType === "group" || entry?.chatType === "channel" || entry?.chatType === "room") {
|
||||
return "group";
|
||||
}
|
||||
if (key.startsWith("group:") || key.includes(":group:") || key.includes(":channel:")) {
|
||||
return "group";
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export type GatewaySessionRow = {
|
||||
subject?: string;
|
||||
room?: string;
|
||||
space?: string;
|
||||
chatType?: "direct" | "group" | "room";
|
||||
chatType?: "direct" | "group" | "channel" | "room";
|
||||
updatedAt: number | null;
|
||||
sessionId?: string;
|
||||
systemSent?: boolean;
|
||||
|
||||
@@ -389,13 +389,16 @@ export async function monitorIMessageProvider(opts: MonitorIMessageOpts = {}): P
|
||||
const imessageTo = (isGroup ? chatTarget : undefined) || `imessage:${sender}`;
|
||||
const ctxPayload = {
|
||||
Body: combinedBody,
|
||||
BodyForAgent: combinedBody,
|
||||
RawBody: bodyText,
|
||||
CommandBody: bodyText,
|
||||
BodyForCommands: bodyText,
|
||||
From: isGroup ? `group:${chatId}` : `imessage:${sender}`,
|
||||
To: imessageTo,
|
||||
SessionKey: route.sessionKey,
|
||||
AccountId: route.accountId,
|
||||
ChatType: isGroup ? "group" : "direct",
|
||||
ConversationLabel: fromLabel,
|
||||
GroupSubject: isGroup ? (message.chat_name ?? undefined) : undefined,
|
||||
GroupMembers: isGroup ? (message.participants ?? []).filter(Boolean).join(", ") : undefined,
|
||||
SenderName: senderNormalized,
|
||||
|
||||
@@ -104,8 +104,10 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
const signalTo = entry.isGroup ? `group:${entry.groupId}` : `signal:${entry.senderRecipient}`;
|
||||
const ctxPayload = {
|
||||
Body: combinedBody,
|
||||
BodyForAgent: combinedBody,
|
||||
RawBody: entry.bodyText,
|
||||
CommandBody: entry.bodyText,
|
||||
BodyForCommands: entry.bodyText,
|
||||
From: entry.isGroup
|
||||
? `group:${entry.groupId ?? "unknown"}`
|
||||
: `signal:${entry.senderRecipient}`,
|
||||
@@ -113,6 +115,7 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
SessionKey: route.sessionKey,
|
||||
AccountId: route.accountId,
|
||||
ChatType: entry.isGroup ? "group" : "direct",
|
||||
ConversationLabel: fromLabel,
|
||||
GroupSubject: entry.isGroup ? (entry.groupName ?? undefined) : undefined,
|
||||
SenderName: entry.senderName,
|
||||
SenderId: entry.senderDisplay,
|
||||
|
||||
@@ -14,6 +14,7 @@ import { upsertChannelPairingRequest } from "../../../pairing/pairing-store.js";
|
||||
import { resolveAgentRoute } from "../../../routing/resolve-route.js";
|
||||
import { resolveThreadSessionKeys } from "../../../routing/session-key.js";
|
||||
import { resolveMentionGating } from "../../../channels/mention-gating.js";
|
||||
import { resolveConversationLabel } from "../../../channels/conversation-label.js";
|
||||
|
||||
import type { ResolvedSlackAccount } from "../../accounts.js";
|
||||
import { reactSlackMessage } from "../../actions.js";
|
||||
@@ -330,7 +331,13 @@ export async function prepareSlackMessage(params: {
|
||||
contextKey: `slack:message:${message.channel}:${message.ts ?? "unknown"}`,
|
||||
});
|
||||
|
||||
const envelopeFrom = isDirectMessage ? senderName : roomLabel;
|
||||
const envelopeFrom =
|
||||
resolveConversationLabel({
|
||||
ChatType: isDirectMessage ? "direct" : "channel",
|
||||
SenderName: senderName,
|
||||
GroupSubject: isRoomish ? roomLabel : undefined,
|
||||
From: slackFrom,
|
||||
}) ?? (isDirectMessage ? senderName : roomLabel);
|
||||
const textWithId = `${rawBody}\n[slack message id: ${message.ts} channel: ${message.channel}]`;
|
||||
const body = formatAgentEnvelope({
|
||||
channel: "Slack",
|
||||
@@ -399,13 +406,16 @@ export async function prepareSlackMessage(params: {
|
||||
|
||||
const ctxPayload = {
|
||||
Body: combinedBody,
|
||||
BodyForAgent: combinedBody,
|
||||
RawBody: rawBody,
|
||||
CommandBody: rawBody,
|
||||
BodyForCommands: rawBody,
|
||||
From: slackFrom,
|
||||
To: slackTo,
|
||||
SessionKey: sessionKey,
|
||||
AccountId: route.accountId,
|
||||
ChatType: isDirectMessage ? "direct" : isRoom ? "room" : "group",
|
||||
ChatType: isDirectMessage ? "direct" : "channel",
|
||||
ConversationLabel: envelopeFrom,
|
||||
GroupSubject: isRoomish ? roomLabel : undefined,
|
||||
GroupSystemPrompt: isRoomish ? groupSystemPrompt : undefined,
|
||||
SenderName: senderName,
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
upsertChannelPairingRequest,
|
||||
} from "../../pairing/pairing-store.js";
|
||||
import { resolveAgentRoute } from "../../routing/resolve-route.js";
|
||||
import { resolveConversationLabel } from "../../channels/conversation-label.js";
|
||||
|
||||
import type { ResolvedSlackAccount } from "../accounts.js";
|
||||
|
||||
@@ -337,14 +338,27 @@ export function registerSlackMonitorSlashCommands(params: {
|
||||
|
||||
const ctxPayload = {
|
||||
Body: prompt,
|
||||
BodyForAgent: prompt,
|
||||
CommandArgs: commandArgs,
|
||||
BodyForCommands: prompt,
|
||||
From: isDirectMessage
|
||||
? `slack:${command.user_id}`
|
||||
: isRoom
|
||||
? `slack:channel:${command.channel_id}`
|
||||
: `slack:group:${command.channel_id}`,
|
||||
To: `slash:${command.user_id}`,
|
||||
ChatType: isDirectMessage ? "direct" : isRoom ? "room" : "group",
|
||||
ChatType: isDirectMessage ? "direct" : "channel",
|
||||
ConversationLabel:
|
||||
resolveConversationLabel({
|
||||
ChatType: isDirectMessage ? "direct" : "channel",
|
||||
SenderName: senderName,
|
||||
GroupSubject: isRoomish ? roomLabel : undefined,
|
||||
From: isDirectMessage
|
||||
? `slack:${command.user_id}`
|
||||
: isRoom
|
||||
? `slack:channel:${command.channel_id}`
|
||||
: `slack:group:${command.channel_id}`,
|
||||
}) ?? (isDirectMessage ? senderName : roomLabel),
|
||||
GroupSubject: isRoomish ? roomLabel : undefined,
|
||||
GroupSystemPrompt: isRoomish ? groupSystemPrompt : undefined,
|
||||
SenderName: senderName,
|
||||
|
||||
@@ -324,9 +324,12 @@ export const buildTelegramMessageContext = async ({
|
||||
}]\n${replyTarget.body}\n[/Replying]`
|
||||
: "";
|
||||
const groupLabel = isGroup ? buildGroupLabel(msg, chatId, resolvedThreadId) : undefined;
|
||||
const conversationLabel = isGroup
|
||||
? (groupLabel ?? `group:${chatId}`)
|
||||
: buildSenderLabel(msg, senderId || chatId);
|
||||
const body = formatAgentEnvelope({
|
||||
channel: "Telegram",
|
||||
from: isGroup ? (groupLabel ?? `group:${chatId}`) : buildSenderLabel(msg, senderId || chatId),
|
||||
from: conversationLabel,
|
||||
timestamp: msg.date ? msg.date * 1000 : undefined,
|
||||
body: `${bodyText}${replySuffix}`,
|
||||
});
|
||||
@@ -357,13 +360,16 @@ export const buildTelegramMessageContext = async ({
|
||||
const commandBody = normalizeCommandBody(rawBody, { botUsername });
|
||||
const ctxPayload = {
|
||||
Body: combinedBody,
|
||||
BodyForAgent: combinedBody,
|
||||
RawBody: rawBody,
|
||||
CommandBody: commandBody,
|
||||
BodyForCommands: commandBody,
|
||||
From: isGroup ? buildTelegramGroupFrom(chatId, resolvedThreadId) : `telegram:${chatId}`,
|
||||
To: `telegram:${chatId}`,
|
||||
SessionKey: route.sessionKey,
|
||||
AccountId: route.accountId,
|
||||
ChatType: isGroup ? "group" : "direct",
|
||||
ConversationLabel: conversationLabel,
|
||||
GroupSubject: isGroup ? (msg.chat.title ?? undefined) : undefined,
|
||||
GroupSystemPrompt: isGroup ? groupSystemPrompt : undefined,
|
||||
SenderName: buildSenderName(msg),
|
||||
|
||||
@@ -248,12 +248,18 @@ export const registerTelegramNativeCommands = ({
|
||||
].filter((entry): entry is string => Boolean(entry));
|
||||
const groupSystemPrompt =
|
||||
systemPromptParts.length > 0 ? systemPromptParts.join("\n\n") : undefined;
|
||||
const conversationLabel = isGroup
|
||||
? (msg.chat.title ? `${msg.chat.title} id:${chatId}` : `group:${chatId}`)
|
||||
: (buildSenderName(msg) ?? String(senderId || chatId));
|
||||
const ctxPayload = {
|
||||
Body: prompt,
|
||||
BodyForAgent: prompt,
|
||||
CommandArgs: commandArgs,
|
||||
BodyForCommands: prompt,
|
||||
From: isGroup ? buildTelegramGroupFrom(chatId, resolvedThreadId) : `telegram:${chatId}`,
|
||||
To: `slash:${senderId || chatId}`,
|
||||
ChatType: isGroup ? "group" : "direct",
|
||||
ConversationLabel: conversationLabel,
|
||||
GroupSubject: isGroup ? (msg.chat.title ?? undefined) : undefined,
|
||||
GroupSystemPrompt: isGroup ? groupSystemPrompt : undefined,
|
||||
SenderName: buildSenderName(msg),
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
import { listSkillCommandsForAgents } from "../auto-reply/skill-commands.js";
|
||||
import { resetInboundDedupe } from "../auto-reply/reply/inbound-dedupe.js";
|
||||
import * as replyModule from "../auto-reply/reply.js";
|
||||
import { expectInboundContextContract } from "../../test/helpers/inbound-contract.js";
|
||||
import { createTelegramBot, getTelegramSequentialKey } from "./bot.js";
|
||||
import { resolveTelegramFetch } from "./fetch.js";
|
||||
|
||||
@@ -583,6 +584,7 @@ describe("createTelegramBot", () => {
|
||||
|
||||
expect(replySpy).toHaveBeenCalledTimes(1);
|
||||
const payload = replySpy.mock.calls[0][0];
|
||||
expectInboundContextContract(payload);
|
||||
expect(payload.WasMentioned).toBe(true);
|
||||
expect(payload.Body).toMatch(/^\[Telegram Test Group id:7 2025-01-09T00:00Z\]/);
|
||||
expect(payload.SenderName).toBe("Ada");
|
||||
@@ -625,6 +627,7 @@ describe("createTelegramBot", () => {
|
||||
|
||||
expect(replySpy).toHaveBeenCalledTimes(1);
|
||||
const payload = replySpy.mock.calls[0][0];
|
||||
expectInboundContextContract(payload);
|
||||
expect(payload.Body).toMatch(/^\[Telegram Ops id:42 2025-01-09T00:00Z\]/);
|
||||
expect(payload.SenderName).toBe("Ada Lovelace");
|
||||
expect(payload.SenderId).toBe("99");
|
||||
|
||||
@@ -15,6 +15,7 @@ vi.mock("../agents/pi-embedded.js", () => ({
|
||||
}));
|
||||
|
||||
import { resetInboundDedupe } from "../auto-reply/reply/inbound-dedupe.js";
|
||||
import { expectInboundContextContract } from "../../test/helpers/inbound-contract.js";
|
||||
import { resetLogger, setLoggerOverride } from "../logging.js";
|
||||
import { monitorWebChannel, SILENT_REPLY_TOKEN } from "./auto-reply.js";
|
||||
import { resetBaileysMocks, resetLoadConfigMock, setLoadConfigMock } from "./test-helpers.js";
|
||||
@@ -184,6 +185,7 @@ describe("web auto-reply", () => {
|
||||
expect(payload.Body).not.toContain("Alice (+111): first");
|
||||
expect(payload.Body).not.toContain("[message_id: g-always-1]");
|
||||
expect(payload.Body).toContain("second");
|
||||
expectInboundContextContract(payload);
|
||||
expect(payload.SenderName).toBe("Bob");
|
||||
expect(payload.SenderE164).toBe("+222");
|
||||
expect(reply).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -22,6 +22,7 @@ import { logVerbose, shouldLogVerbose } from "../../../globals.js";
|
||||
import type { getChildLogger } from "../../../logging.js";
|
||||
import type { resolveAgentRoute } from "../../../routing/resolve-route.js";
|
||||
import { jidToE164, normalizeE164 } from "../../../utils.js";
|
||||
import { normalizeChatType } from "../../../channels/chat-type.js";
|
||||
import { newConnectionId } from "../../reconnect.js";
|
||||
import { formatError } from "../../session.js";
|
||||
import { deliverWebReply } from "../deliver-reply.js";
|
||||
@@ -197,8 +198,10 @@ export async function processMessage(params: {
|
||||
const { queuedFinal } = await dispatchReplyWithBufferedBlockDispatcher({
|
||||
ctx: {
|
||||
Body: combinedBody,
|
||||
BodyForAgent: combinedBody,
|
||||
RawBody: params.msg.body,
|
||||
CommandBody: params.msg.body,
|
||||
BodyForCommands: params.msg.body,
|
||||
From: params.msg.from,
|
||||
To: params.msg.to,
|
||||
SessionKey: params.route.sessionKey,
|
||||
@@ -210,7 +213,8 @@ export async function processMessage(params: {
|
||||
MediaPath: params.msg.mediaPath,
|
||||
MediaUrl: params.msg.mediaUrl,
|
||||
MediaType: params.msg.mediaType,
|
||||
ChatType: params.msg.chatType,
|
||||
ChatType: normalizeChatType(params.msg.chatType) ?? params.msg.chatType,
|
||||
ConversationLabel: params.msg.chatType === "group" ? conversationId : params.msg.from,
|
||||
GroupSubject: params.msg.groupSubject,
|
||||
GroupMembers: formatGroupMembers({
|
||||
participants: params.msg.groupParticipants,
|
||||
|
||||
Reference in New Issue
Block a user