Merge b9006ebdf5 into da71eaebd2
This commit is contained in:
commit
064223b07e
@ -227,10 +227,13 @@ export async function runPreparedReply(
|
|||||||
prefixedBodyBase,
|
prefixedBodyBase,
|
||||||
});
|
});
|
||||||
const threadStarterBody = ctx.ThreadStarterBody?.trim();
|
const threadStarterBody = ctx.ThreadStarterBody?.trim();
|
||||||
const threadStarterNote =
|
const threadHistoryBody = ctx.ThreadHistoryBody?.trim();
|
||||||
isNewSession && threadStarterBody
|
const threadContextNote =
|
||||||
? `[Thread starter - for context]\n${threadStarterBody}`
|
isNewSession && threadHistoryBody
|
||||||
: undefined;
|
? `[Thread history - for context]\n${threadHistoryBody}`
|
||||||
|
: isNewSession && threadStarterBody
|
||||||
|
? `[Thread starter - for context]\n${threadStarterBody}`
|
||||||
|
: undefined;
|
||||||
const skillResult = await ensureSkillSnapshot({
|
const skillResult = await ensureSkillSnapshot({
|
||||||
sessionEntry,
|
sessionEntry,
|
||||||
sessionStore,
|
sessionStore,
|
||||||
@ -245,7 +248,7 @@ export async function runPreparedReply(
|
|||||||
sessionEntry = skillResult.sessionEntry ?? sessionEntry;
|
sessionEntry = skillResult.sessionEntry ?? sessionEntry;
|
||||||
currentSystemSent = skillResult.systemSent;
|
currentSystemSent = skillResult.systemSent;
|
||||||
const skillsSnapshot = skillResult.skillsSnapshot;
|
const skillsSnapshot = skillResult.skillsSnapshot;
|
||||||
const prefixedBody = [threadStarterNote, prefixedBodyBase].filter(Boolean).join("\n\n");
|
const prefixedBody = [threadContextNote, prefixedBodyBase].filter(Boolean).join("\n\n");
|
||||||
const mediaNote = buildInboundMediaNote(ctx);
|
const mediaNote = buildInboundMediaNote(ctx);
|
||||||
const mediaReplyHint = mediaNote
|
const mediaReplyHint = mediaNote
|
||||||
? "To send an image back, prefer the message tool (media/path/filePath). If you must inline, use MEDIA:/path or MEDIA:https://example.com/image.jpg (spaces ok, quote if needed). Keep caption in the text body."
|
? "To send an image back, prefer the message tool (media/path/filePath). If you must inline, use MEDIA:/path or MEDIA:https://example.com/image.jpg (spaces ok, quote if needed). Keep caption in the text body."
|
||||||
@ -307,7 +310,7 @@ export async function runPreparedReply(
|
|||||||
}
|
}
|
||||||
const sessionIdFinal = sessionId ?? crypto.randomUUID();
|
const sessionIdFinal = sessionId ?? crypto.randomUUID();
|
||||||
const sessionFile = resolveSessionFilePath(sessionIdFinal, sessionEntry);
|
const sessionFile = resolveSessionFilePath(sessionIdFinal, sessionEntry);
|
||||||
const queueBodyBase = [threadStarterNote, baseBodyFinal].filter(Boolean).join("\n\n");
|
const queueBodyBase = [threadContextNote, baseBodyFinal].filter(Boolean).join("\n\n");
|
||||||
const queueMessageId = sessionCtx.MessageSid?.trim();
|
const queueMessageId = sessionCtx.MessageSid?.trim();
|
||||||
const queueMessageIdHint = queueMessageId ? `[message_id: ${queueMessageId}]` : "";
|
const queueMessageIdHint = queueMessageId ? `[message_id: ${queueMessageId}]` : "";
|
||||||
const queueBodyWithId = queueMessageIdHint
|
const queueBodyWithId = queueMessageIdHint
|
||||||
|
|||||||
@ -29,6 +29,7 @@ export function finalizeInboundContext<T extends Record<string, unknown>>(
|
|||||||
normalized.CommandBody = normalizeTextField(normalized.CommandBody);
|
normalized.CommandBody = normalizeTextField(normalized.CommandBody);
|
||||||
normalized.Transcript = normalizeTextField(normalized.Transcript);
|
normalized.Transcript = normalizeTextField(normalized.Transcript);
|
||||||
normalized.ThreadStarterBody = normalizeTextField(normalized.ThreadStarterBody);
|
normalized.ThreadStarterBody = normalizeTextField(normalized.ThreadStarterBody);
|
||||||
|
normalized.ThreadHistoryBody = normalizeTextField(normalized.ThreadHistoryBody);
|
||||||
|
|
||||||
const chatType = normalizeChatType(normalized.ChatType);
|
const chatType = normalizeChatType(normalized.ChatType);
|
||||||
if (chatType && (opts.forceChatType || normalized.ChatType !== chatType)) {
|
if (chatType && (opts.forceChatType || normalized.ChatType !== chatType)) {
|
||||||
|
|||||||
@ -58,6 +58,8 @@ export type MsgContext = {
|
|||||||
ForwardedFromSignature?: string;
|
ForwardedFromSignature?: string;
|
||||||
ForwardedDate?: number;
|
ForwardedDate?: number;
|
||||||
ThreadStarterBody?: string;
|
ThreadStarterBody?: string;
|
||||||
|
/** Full thread history when starting a new thread session. */
|
||||||
|
ThreadHistoryBody?: string;
|
||||||
ThreadLabel?: string;
|
ThreadLabel?: string;
|
||||||
MediaPath?: string;
|
MediaPath?: string;
|
||||||
MediaUrl?: string;
|
MediaUrl?: string;
|
||||||
|
|||||||
@ -122,3 +122,59 @@ export async function resolveSlackThreadStarter(params: {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SlackThreadMessage = {
|
||||||
|
text: string;
|
||||||
|
userId?: string;
|
||||||
|
ts?: string;
|
||||||
|
botId?: string;
|
||||||
|
files?: SlackFile[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all messages in a Slack thread (excluding the current message).
|
||||||
|
* Used to populate thread context when a new thread session starts.
|
||||||
|
*/
|
||||||
|
export async function resolveSlackThreadHistory(params: {
|
||||||
|
channelId: string;
|
||||||
|
threadTs: string;
|
||||||
|
client: SlackWebClient;
|
||||||
|
currentMessageTs?: string;
|
||||||
|
limit?: number;
|
||||||
|
}): Promise<SlackThreadMessage[]> {
|
||||||
|
const maxMessages = params.limit ?? 20;
|
||||||
|
try {
|
||||||
|
const response = (await params.client.conversations.replies({
|
||||||
|
channel: params.channelId,
|
||||||
|
ts: params.threadTs,
|
||||||
|
limit: maxMessages + 1, // +1 to account for filtering current message
|
||||||
|
inclusive: true,
|
||||||
|
})) as {
|
||||||
|
messages?: Array<{
|
||||||
|
text?: string;
|
||||||
|
user?: string;
|
||||||
|
bot_id?: string;
|
||||||
|
ts?: string;
|
||||||
|
files?: SlackFile[];
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const messages = response?.messages ?? [];
|
||||||
|
return messages
|
||||||
|
.filter((msg) => {
|
||||||
|
if (!msg.text?.trim()) return false;
|
||||||
|
if (params.currentMessageTs && msg.ts === params.currentMessageTs) return false;
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.slice(0, maxMessages)
|
||||||
|
.map((msg) => ({
|
||||||
|
text: msg.text ?? "",
|
||||||
|
userId: msg.user,
|
||||||
|
botId: msg.bot_id,
|
||||||
|
ts: msg.ts,
|
||||||
|
files: msg.files,
|
||||||
|
}));
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -44,7 +44,11 @@ import { resolveSlackAllowListMatch, resolveSlackUserAllowed } from "../allow-li
|
|||||||
import { resolveSlackEffectiveAllowFrom } from "../auth.js";
|
import { resolveSlackEffectiveAllowFrom } from "../auth.js";
|
||||||
import { resolveSlackChannelConfig } from "../channel-config.js";
|
import { resolveSlackChannelConfig } from "../channel-config.js";
|
||||||
import { normalizeSlackChannelType, type SlackMonitorContext } from "../context.js";
|
import { normalizeSlackChannelType, type SlackMonitorContext } from "../context.js";
|
||||||
import { resolveSlackMedia, resolveSlackThreadStarter } from "../media.js";
|
import {
|
||||||
|
resolveSlackMedia,
|
||||||
|
resolveSlackThreadHistory,
|
||||||
|
resolveSlackThreadStarter,
|
||||||
|
} from "../media.js";
|
||||||
|
|
||||||
import type { PreparedSlackMessage } from "./types.js";
|
import type { PreparedSlackMessage } from "./types.js";
|
||||||
|
|
||||||
@ -452,6 +456,7 @@ export async function prepareSlackMessage(params: {
|
|||||||
systemPromptParts.length > 0 ? systemPromptParts.join("\n\n") : undefined;
|
systemPromptParts.length > 0 ? systemPromptParts.join("\n\n") : undefined;
|
||||||
|
|
||||||
let threadStarterBody: string | undefined;
|
let threadStarterBody: string | undefined;
|
||||||
|
let threadHistoryBody: string | undefined;
|
||||||
let threadLabel: string | undefined;
|
let threadLabel: string | undefined;
|
||||||
let threadStarterMedia: Awaited<ReturnType<typeof resolveSlackMedia>> = null;
|
let threadStarterMedia: Awaited<ReturnType<typeof resolveSlackMedia>> = null;
|
||||||
if (isThreadReply && threadTs) {
|
if (isThreadReply && threadTs) {
|
||||||
@ -489,6 +494,44 @@ export async function prepareSlackMessage(params: {
|
|||||||
} else {
|
} else {
|
||||||
threadLabel = `Slack thread ${roomLabel}`;
|
threadLabel = `Slack thread ${roomLabel}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch full thread history for new thread sessions
|
||||||
|
// This provides context of previous messages (including bot replies) in the thread
|
||||||
|
if (!previousTimestamp) {
|
||||||
|
const threadHistory = await resolveSlackThreadHistory({
|
||||||
|
channelId: message.channel,
|
||||||
|
threadTs,
|
||||||
|
client: ctx.app.client,
|
||||||
|
currentMessageTs: message.ts,
|
||||||
|
limit: 20,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (threadHistory.length > 0) {
|
||||||
|
const historyParts: string[] = [];
|
||||||
|
for (const historyMsg of threadHistory) {
|
||||||
|
const msgUser = historyMsg.userId ? await ctx.resolveUserName(historyMsg.userId) : null;
|
||||||
|
const msgSenderName =
|
||||||
|
msgUser?.name ?? (historyMsg.botId ? `Bot (${historyMsg.botId})` : "Unknown");
|
||||||
|
const isBot = Boolean(historyMsg.botId);
|
||||||
|
const role = isBot ? "assistant" : "user";
|
||||||
|
const msgWithId = `${historyMsg.text}\n[slack message id: ${historyMsg.ts ?? "unknown"} channel: ${message.channel}]`;
|
||||||
|
historyParts.push(
|
||||||
|
formatInboundEnvelope({
|
||||||
|
channel: "Slack",
|
||||||
|
from: `${msgSenderName} (${role})`,
|
||||||
|
timestamp: historyMsg.ts ? Math.round(Number(historyMsg.ts) * 1000) : undefined,
|
||||||
|
body: msgWithId,
|
||||||
|
chatType: "channel",
|
||||||
|
envelope: envelopeOptions,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
threadHistoryBody = historyParts.join("\n\n");
|
||||||
|
logVerbose(
|
||||||
|
`slack: populated thread history with ${threadHistory.length} messages for new session`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use thread starter media if current message has none
|
// Use thread starter media if current message has none
|
||||||
@ -516,6 +559,7 @@ export async function prepareSlackMessage(params: {
|
|||||||
MessageThreadId: threadContext.messageThreadId,
|
MessageThreadId: threadContext.messageThreadId,
|
||||||
ParentSessionKey: threadKeys.parentSessionKey,
|
ParentSessionKey: threadKeys.parentSessionKey,
|
||||||
ThreadStarterBody: threadStarterBody,
|
ThreadStarterBody: threadStarterBody,
|
||||||
|
ThreadHistoryBody: threadHistoryBody,
|
||||||
ThreadLabel: threadLabel,
|
ThreadLabel: threadLabel,
|
||||||
Timestamp: message.ts ? Math.round(Number(message.ts) * 1000) : undefined,
|
Timestamp: message.ts ? Math.round(Number(message.ts) * 1000) : undefined,
|
||||||
WasMentioned: isRoomish ? effectiveWasMentioned : undefined,
|
WasMentioned: isRoomish ? effectiveWasMentioned : undefined,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user