Merge f04ddea9a1 into 4583f88626
This commit is contained in:
commit
d32942a1ec
@ -263,7 +263,11 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
|
|||||||
const to = typeof args.to === "string" ? args.to : undefined;
|
const to = typeof args.to === "string" ? args.to : undefined;
|
||||||
if (!to) return null;
|
if (!to) return null;
|
||||||
const accountId = typeof args.accountId === "string" ? args.accountId.trim() : undefined;
|
const accountId = typeof args.accountId === "string" ? args.accountId.trim() : undefined;
|
||||||
return { to, accountId };
|
const threadIdRaw = typeof args.threadId === "string" ? args.threadId.trim() : "";
|
||||||
|
const replyToRaw = typeof args.replyTo === "string" ? args.replyTo.trim() : "";
|
||||||
|
const threadTsRaw = typeof args.threadTs === "string" ? args.threadTs.trim() : "";
|
||||||
|
const threadId = threadIdRaw || replyToRaw || threadTsRaw || undefined;
|
||||||
|
return { to, accountId, threadId };
|
||||||
},
|
},
|
||||||
handleAction: async ({ action, params, cfg, accountId, toolContext }) => {
|
handleAction: async ({ action, params, cfg, accountId, toolContext }) => {
|
||||||
const resolveChannelId = () =>
|
const resolveChannelId = () =>
|
||||||
|
|||||||
@ -16,7 +16,7 @@ Use `slack` to react, manage pins, send/edit/delete messages, and fetch member i
|
|||||||
- For reactions, an `emoji` (Unicode or `:name:`).
|
- For reactions, an `emoji` (Unicode or `:name:`).
|
||||||
- For message sends, a `to` target (`channel:<id>` or `user:<id>`) and `content`.
|
- For message sends, a `to` target (`channel:<id>` or `user:<id>`) and `content`.
|
||||||
|
|
||||||
Message context lines include `slack message id` and `channel` fields you can reuse directly.
|
Message text no longer includes `slack message id`. Use metadata fields like `MessageSid` or `ReplyToId`.
|
||||||
|
|
||||||
## Actions
|
## Actions
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,7 @@ export type MessagingToolSend = {
|
|||||||
provider: string;
|
provider: string;
|
||||||
accountId?: string;
|
accountId?: string;
|
||||||
to?: string;
|
to?: string;
|
||||||
|
threadId?: string | number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CORE_MESSAGING_TOOLS = new Set(["sessions_send", "message"]);
|
const CORE_MESSAGING_TOOLS = new Set(["sessions_send", "message"]);
|
||||||
|
|||||||
@ -118,6 +118,14 @@ export function extractMessagingToolSend(
|
|||||||
toolName: string,
|
toolName: string,
|
||||||
args: Record<string, unknown>,
|
args: Record<string, unknown>,
|
||||||
): MessagingToolSend | undefined {
|
): MessagingToolSend | undefined {
|
||||||
|
const readThreadId = (value: unknown): string | number | undefined => {
|
||||||
|
if (typeof value === "string") {
|
||||||
|
const trimmed = value.trim();
|
||||||
|
return trimmed ? trimmed : undefined;
|
||||||
|
}
|
||||||
|
if (typeof value === "number" && Number.isFinite(value)) return value;
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
// Provider docking: new provider tools must implement plugin.actions.extractToolSend.
|
// Provider docking: new provider tools must implement plugin.actions.extractToolSend.
|
||||||
const action = typeof args.action === "string" ? args.action.trim() : "";
|
const action = typeof args.action === "string" ? args.action.trim() : "";
|
||||||
const accountIdRaw = typeof args.accountId === "string" ? args.accountId.trim() : undefined;
|
const accountIdRaw = typeof args.accountId === "string" ? args.accountId.trim() : undefined;
|
||||||
@ -132,7 +140,11 @@ export function extractMessagingToolSend(
|
|||||||
const providerId = providerHint ? normalizeChannelId(providerHint) : null;
|
const providerId = providerHint ? normalizeChannelId(providerHint) : null;
|
||||||
const provider = providerId ?? (providerHint ? providerHint.toLowerCase() : "message");
|
const provider = providerId ?? (providerHint ? providerHint.toLowerCase() : "message");
|
||||||
const to = normalizeTargetForProvider(provider, toRaw);
|
const to = normalizeTargetForProvider(provider, toRaw);
|
||||||
return to ? { tool: toolName, provider, accountId, to } : undefined;
|
let threadId = readThreadId(args.threadId);
|
||||||
|
if (!threadId && provider === "slack") {
|
||||||
|
threadId = readThreadId(args.replyTo ?? args.threadTs);
|
||||||
|
}
|
||||||
|
return to ? { tool: toolName, provider, accountId, to, threadId } : undefined;
|
||||||
}
|
}
|
||||||
const providerId = normalizeChannelId(toolName);
|
const providerId = normalizeChannelId(toolName);
|
||||||
if (!providerId) return undefined;
|
if (!providerId) return undefined;
|
||||||
@ -146,6 +158,7 @@ export function extractMessagingToolSend(
|
|||||||
provider: providerId,
|
provider: providerId,
|
||||||
accountId: extracted.accountId ?? accountId,
|
accountId: extracted.accountId ?? accountId,
|
||||||
to,
|
to,
|
||||||
|
threadId: extracted.threadId,
|
||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,6 +31,7 @@ export function buildReplyPayloads(params: {
|
|||||||
typeof shouldSuppressMessagingToolReplies
|
typeof shouldSuppressMessagingToolReplies
|
||||||
>[0]["messagingToolSentTargets"];
|
>[0]["messagingToolSentTargets"];
|
||||||
originatingTo?: string;
|
originatingTo?: string;
|
||||||
|
originatingThreadId?: string | number;
|
||||||
accountId?: string;
|
accountId?: string;
|
||||||
}): { replyPayloads: ReplyPayload[]; didLogHeartbeatStrip: boolean } {
|
}): { replyPayloads: ReplyPayload[]; didLogHeartbeatStrip: boolean } {
|
||||||
let didLogHeartbeatStrip = params.didLogHeartbeatStrip;
|
let didLogHeartbeatStrip = params.didLogHeartbeatStrip;
|
||||||
@ -94,7 +95,9 @@ export function buildReplyPayloads(params: {
|
|||||||
messageProvider: params.messageProvider,
|
messageProvider: params.messageProvider,
|
||||||
messagingToolSentTargets,
|
messagingToolSentTargets,
|
||||||
originatingTo: params.originatingTo,
|
originatingTo: params.originatingTo,
|
||||||
|
originatingThreadId: params.originatingThreadId,
|
||||||
accountId: params.accountId,
|
accountId: params.accountId,
|
||||||
|
replyToMode: params.replyToMode,
|
||||||
});
|
});
|
||||||
const dedupedPayloads = filterMessagingToolDuplicates({
|
const dedupedPayloads = filterMessagingToolDuplicates({
|
||||||
payloads: replyTaggedPayloads,
|
payloads: replyTaggedPayloads,
|
||||||
|
|||||||
@ -408,6 +408,7 @@ export async function runReplyAgent(params: {
|
|||||||
messagingToolSentTexts: runResult.messagingToolSentTexts,
|
messagingToolSentTexts: runResult.messagingToolSentTexts,
|
||||||
messagingToolSentTargets: runResult.messagingToolSentTargets,
|
messagingToolSentTargets: runResult.messagingToolSentTargets,
|
||||||
originatingTo: sessionCtx.OriginatingTo ?? sessionCtx.To,
|
originatingTo: sessionCtx.OriginatingTo ?? sessionCtx.To,
|
||||||
|
originatingThreadId: sessionCtx.MessageThreadId,
|
||||||
accountId: sessionCtx.AccountId,
|
accountId: sessionCtx.AccountId,
|
||||||
});
|
});
|
||||||
const { replyPayloads } = payloadResult;
|
const { replyPayloads } = payloadResult;
|
||||||
|
|||||||
@ -245,7 +245,9 @@ export function createFollowupRunner(params: {
|
|||||||
messageProvider: queued.run.messageProvider,
|
messageProvider: queued.run.messageProvider,
|
||||||
messagingToolSentTargets: runResult.messagingToolSentTargets,
|
messagingToolSentTargets: runResult.messagingToolSentTargets,
|
||||||
originatingTo: queued.originatingTo,
|
originatingTo: queued.originatingTo,
|
||||||
|
originatingThreadId: queued.originatingThreadId,
|
||||||
accountId: queued.run.agentAccountId,
|
accountId: queued.run.agentAccountId,
|
||||||
|
replyToMode,
|
||||||
});
|
});
|
||||||
const finalPayloads = suppressMessagingToolReplies ? [] : dedupedPayloads;
|
const finalPayloads = suppressMessagingToolReplies ? [] : dedupedPayloads;
|
||||||
|
|
||||||
|
|||||||
@ -82,24 +82,46 @@ export function shouldSuppressMessagingToolReplies(params: {
|
|||||||
messageProvider?: string;
|
messageProvider?: string;
|
||||||
messagingToolSentTargets?: MessagingToolSend[];
|
messagingToolSentTargets?: MessagingToolSend[];
|
||||||
originatingTo?: string;
|
originatingTo?: string;
|
||||||
|
originatingThreadId?: string | number;
|
||||||
accountId?: string;
|
accountId?: string;
|
||||||
|
replyToMode?: ReplyToMode;
|
||||||
}): boolean {
|
}): boolean {
|
||||||
const provider = params.messageProvider?.trim().toLowerCase();
|
const provider = params.messageProvider?.trim().toLowerCase();
|
||||||
if (!provider) return false;
|
if (!provider) return false;
|
||||||
const originTarget = normalizeTargetForProvider(provider, params.originatingTo);
|
const originTarget = normalizeTargetForProvider(provider, params.originatingTo);
|
||||||
if (!originTarget) return false;
|
if (!originTarget) return false;
|
||||||
|
const normalizeThreadId = (value?: string | number) => {
|
||||||
|
if (value === undefined || value === null) return undefined;
|
||||||
|
if (typeof value === "string") {
|
||||||
|
const trimmed = value.trim();
|
||||||
|
return trimmed ? trimmed : undefined;
|
||||||
|
}
|
||||||
|
return Number.isFinite(value) ? String(value) : undefined;
|
||||||
|
};
|
||||||
|
const originThreadId = normalizeThreadId(params.originatingThreadId);
|
||||||
const originAccount = normalizeAccountId(params.accountId);
|
const originAccount = normalizeAccountId(params.accountId);
|
||||||
|
const shouldAssumeAutoThread =
|
||||||
|
provider === "slack" &&
|
||||||
|
(params.replyToMode === "all" || params.replyToMode === "first") &&
|
||||||
|
Boolean(originThreadId);
|
||||||
const sentTargets = params.messagingToolSentTargets ?? [];
|
const sentTargets = params.messagingToolSentTargets ?? [];
|
||||||
if (sentTargets.length === 0) return false;
|
if (sentTargets.length === 0) return false;
|
||||||
return sentTargets.some((target) => {
|
return sentTargets.some((target) => {
|
||||||
if (!target?.provider) return false;
|
if (!target?.provider) return false;
|
||||||
if (target.provider.trim().toLowerCase() !== provider) return false;
|
if (target.provider.trim().toLowerCase() !== provider) return false;
|
||||||
const targetKey = normalizeTargetForProvider(provider, target.to);
|
const targetKey = normalizeTargetForProvider(provider, target.to);
|
||||||
if (!targetKey) return false;
|
if (!targetKey || targetKey !== originTarget) return false;
|
||||||
const targetAccount = normalizeAccountId(target.accountId);
|
const targetAccount = normalizeAccountId(target.accountId);
|
||||||
if (originAccount && targetAccount && originAccount !== targetAccount) {
|
if (originAccount && targetAccount && originAccount !== targetAccount) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return targetKey === originTarget;
|
let targetThreadId = normalizeThreadId(target.threadId);
|
||||||
|
if (!targetThreadId && shouldAssumeAutoThread) {
|
||||||
|
targetThreadId = originThreadId;
|
||||||
|
}
|
||||||
|
if (originThreadId || targetThreadId) {
|
||||||
|
return originThreadId === targetThreadId;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -69,7 +69,9 @@ export const telegramMessageActions: ChannelMessageActionAdapter = {
|
|||||||
const to = typeof args.to === "string" ? args.to : undefined;
|
const to = typeof args.to === "string" ? args.to : undefined;
|
||||||
if (!to) return null;
|
if (!to) return null;
|
||||||
const accountId = typeof args.accountId === "string" ? args.accountId.trim() : undefined;
|
const accountId = typeof args.accountId === "string" ? args.accountId.trim() : undefined;
|
||||||
return { to, accountId };
|
const threadIdRaw = typeof args.threadId === "string" ? args.threadId.trim() : "";
|
||||||
|
const threadId = threadIdRaw || undefined;
|
||||||
|
return { to, accountId, threadId };
|
||||||
},
|
},
|
||||||
handleAction: async ({ action, params, cfg, accountId }) => {
|
handleAction: async ({ action, params, cfg, accountId }) => {
|
||||||
if (action === "send") {
|
if (action === "send") {
|
||||||
|
|||||||
@ -54,7 +54,11 @@ export function createSlackActions(providerId: string): ChannelMessageActionAdap
|
|||||||
const to = typeof args.to === "string" ? args.to : undefined;
|
const to = typeof args.to === "string" ? args.to : undefined;
|
||||||
if (!to) return null;
|
if (!to) return null;
|
||||||
const accountId = typeof args.accountId === "string" ? args.accountId.trim() : undefined;
|
const accountId = typeof args.accountId === "string" ? args.accountId.trim() : undefined;
|
||||||
return { to, accountId };
|
const threadIdRaw = typeof args.threadId === "string" ? args.threadId.trim() : "";
|
||||||
|
const replyToRaw = typeof args.replyTo === "string" ? args.replyTo.trim() : "";
|
||||||
|
const threadTsRaw = typeof args.threadTs === "string" ? args.threadTs.trim() : "";
|
||||||
|
const threadId = threadIdRaw || replyToRaw || threadTsRaw || undefined;
|
||||||
|
return { to, accountId, threadId };
|
||||||
},
|
},
|
||||||
handleAction: async (ctx: ChannelMessageActionContext) => {
|
handleAction: async (ctx: ChannelMessageActionContext) => {
|
||||||
const { action, params, cfg } = ctx;
|
const { action, params, cfg } = ctx;
|
||||||
|
|||||||
@ -304,6 +304,7 @@ export type ChannelMessageActionContext = {
|
|||||||
export type ChannelToolSend = {
|
export type ChannelToolSend = {
|
||||||
to: string;
|
to: string;
|
||||||
accountId?: string | null;
|
accountId?: string | null;
|
||||||
|
threadId?: string | number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ChannelMessageActionAdapter = {
|
export type ChannelMessageActionAdapter = {
|
||||||
|
|||||||
@ -32,7 +32,9 @@ export function createSlackMessageHandler(params: {
|
|||||||
const senderId = entry.message.user ?? entry.message.bot_id;
|
const senderId = entry.message.user ?? entry.message.bot_id;
|
||||||
if (!senderId) return null;
|
if (!senderId) return null;
|
||||||
const messageTs = entry.message.ts ?? entry.message.event_ts;
|
const messageTs = entry.message.ts ?? entry.message.event_ts;
|
||||||
// If Slack flags a thread reply but omits thread_ts, isolate it from root debouncing.
|
// If we get a Slack event that looks like a thread reply (has parent_user_id)
|
||||||
|
// but is missing thread_ts, avoid debouncing it into the channel root bucket.
|
||||||
|
// We'll attempt to resolve the missing thread_ts later in prepareSlackMessage.
|
||||||
const threadKey = entry.message.thread_ts
|
const threadKey = entry.message.thread_ts
|
||||||
? `${entry.message.channel}:${entry.message.thread_ts}`
|
? `${entry.message.channel}:${entry.message.thread_ts}`
|
||||||
: entry.message.parent_user_id && messageTs
|
: entry.message.parent_user_id && messageTs
|
||||||
|
|||||||
@ -44,8 +44,15 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag
|
|||||||
|
|
||||||
const messageTs = message.ts ?? message.event_ts;
|
const messageTs = message.ts ?? message.event_ts;
|
||||||
const incomingThreadTs = message.thread_ts;
|
const incomingThreadTs = message.thread_ts;
|
||||||
|
const toolThreadTs = incomingThreadTs ?? (ctx.replyToMode === "all" ? messageTs : undefined);
|
||||||
let didSetStatus = false;
|
let didSetStatus = false;
|
||||||
|
|
||||||
|
if (shouldLogVerbose()) {
|
||||||
|
logVerbose(
|
||||||
|
`slack threading: inbound channel=${message.channel} ts=${messageTs ?? "unknown"} thread_ts=${incomingThreadTs ?? "none"} replyToMode=${ctx.replyToMode}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Shared mutable ref for "replyToMode=first". Both tool + auto-reply flows
|
// Shared mutable ref for "replyToMode=first". Both tool + auto-reply flows
|
||||||
// mark this to ensure only the first reply is threaded.
|
// mark this to ensure only the first reply is threaded.
|
||||||
const hasRepliedRef = { value: false };
|
const hasRepliedRef = { value: false };
|
||||||
@ -101,8 +108,13 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag
|
|||||||
responsePrefix: prefixContext.responsePrefix,
|
responsePrefix: prefixContext.responsePrefix,
|
||||||
responsePrefixContextProvider: prefixContext.responsePrefixContextProvider,
|
responsePrefixContextProvider: prefixContext.responsePrefixContextProvider,
|
||||||
humanDelay: resolveHumanDelayConfig(cfg, route.agentId),
|
humanDelay: resolveHumanDelayConfig(cfg, route.agentId),
|
||||||
deliver: async (payload) => {
|
deliver: async (payload, info) => {
|
||||||
const replyThreadTs = replyPlan.nextThreadTs();
|
const replyThreadTs = info.kind === "tool" ? toolThreadTs : replyPlan.nextThreadTs();
|
||||||
|
if (shouldLogVerbose()) {
|
||||||
|
logVerbose(
|
||||||
|
`slack threading: deliver kind=${info.kind} target=${prepared.replyTarget} thread_ts=${replyThreadTs ?? "none"} payload.replyToId=${payload.replyToId ?? "none"}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
await deliverReplies({
|
await deliverReplies({
|
||||||
replies: [payload],
|
replies: [payload],
|
||||||
target: prepared.replyTarget,
|
target: prepared.replyTarget,
|
||||||
@ -112,7 +124,9 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag
|
|||||||
textLimit: ctx.textLimit,
|
textLimit: ctx.textLimit,
|
||||||
replyThreadTs,
|
replyThreadTs,
|
||||||
});
|
});
|
||||||
replyPlan.markSent();
|
if (info.kind !== "tool") {
|
||||||
|
replyPlan.markSent();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onError: (err, info) => {
|
onError: (err, info) => {
|
||||||
runtime.error?.(danger(`slack ${info.kind} reply failed: ${String(err)}`));
|
runtime.error?.(danger(`slack ${info.kind} reply failed: ${String(err)}`));
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import type { WebClient as SlackWebClient } from "@slack/web-api";
|
||||||
|
|
||||||
import { resolveAckReaction } from "../../../agents/identity.js";
|
import { resolveAckReaction } from "../../../agents/identity.js";
|
||||||
import { hasControlCommand } from "../../../auto-reply/command-detection.js";
|
import { hasControlCommand } from "../../../auto-reply/command-detection.js";
|
||||||
import { shouldHandleTextCommands } from "../../../auto-reply/commands-registry.js";
|
import { shouldHandleTextCommands } from "../../../auto-reply/commands-registry.js";
|
||||||
@ -48,13 +50,41 @@ import { resolveSlackMedia, resolveSlackThreadStarter } from "../media.js";
|
|||||||
|
|
||||||
import type { PreparedSlackMessage } from "./types.js";
|
import type { PreparedSlackMessage } from "./types.js";
|
||||||
|
|
||||||
|
async function resolveSlackThreadTsFromHistory(params: {
|
||||||
|
client: SlackWebClient;
|
||||||
|
channelId: string;
|
||||||
|
messageTs: string;
|
||||||
|
}): Promise<string | undefined> {
|
||||||
|
try {
|
||||||
|
const response = (await params.client.conversations.history({
|
||||||
|
channel: params.channelId,
|
||||||
|
latest: params.messageTs,
|
||||||
|
oldest: params.messageTs,
|
||||||
|
inclusive: true,
|
||||||
|
limit: 1,
|
||||||
|
})) as { messages?: Array<{ ts?: string; thread_ts?: string }> };
|
||||||
|
const message =
|
||||||
|
response.messages?.find((entry) => entry.ts === params.messageTs) ?? response.messages?.[0];
|
||||||
|
const threadTs = message?.thread_ts?.trim();
|
||||||
|
return threadTs || undefined;
|
||||||
|
} catch (err) {
|
||||||
|
if (shouldLogVerbose()) {
|
||||||
|
logVerbose(
|
||||||
|
`slack inbound: failed to resolve thread_ts via conversations.history for channel=${params.channelId} ts=${params.messageTs}: ${String(err)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function prepareSlackMessage(params: {
|
export async function prepareSlackMessage(params: {
|
||||||
ctx: SlackMonitorContext;
|
ctx: SlackMonitorContext;
|
||||||
account: ResolvedSlackAccount;
|
account: ResolvedSlackAccount;
|
||||||
message: SlackMessageEvent;
|
message: SlackMessageEvent;
|
||||||
opts: { source: "message" | "app_mention"; wasMentioned?: boolean };
|
opts: { source: "message" | "app_mention"; wasMentioned?: boolean };
|
||||||
}): Promise<PreparedSlackMessage | null> {
|
}): Promise<PreparedSlackMessage | null> {
|
||||||
const { ctx, account, message, opts } = params;
|
const { ctx, account, opts } = params;
|
||||||
|
let message = params.message;
|
||||||
const cfg = ctx.cfg;
|
const cfg = ctx.cfg;
|
||||||
|
|
||||||
let channelInfo: {
|
let channelInfo: {
|
||||||
@ -193,18 +223,48 @@ export async function prepareSlackMessage(params: {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Slack occasionally delivers events that include parent_user_id (so they're thread replies)
|
||||||
|
// but omit thread_ts. If we don't recover the thread_ts, replies can end up in the channel root.
|
||||||
|
if (!message.thread_ts && message.parent_user_id && message.ts) {
|
||||||
|
if (shouldLogVerbose()) {
|
||||||
|
logVerbose(
|
||||||
|
`slack inbound: missing thread_ts for thread reply channel=${message.channel} ts=${message.ts} source=${opts.source}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const resolved = await resolveSlackThreadTsFromHistory({
|
||||||
|
client: ctx.app.client,
|
||||||
|
channelId: message.channel,
|
||||||
|
messageTs: message.ts,
|
||||||
|
});
|
||||||
|
if (resolved) {
|
||||||
|
message = { ...message, thread_ts: resolved };
|
||||||
|
if (shouldLogVerbose()) {
|
||||||
|
logVerbose(
|
||||||
|
`slack inbound: resolved missing thread_ts channel=${message.channel} ts=${message.ts} -> thread_ts=${resolved}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (shouldLogVerbose()) {
|
||||||
|
logVerbose(
|
||||||
|
`slack inbound: could not resolve missing thread_ts channel=${message.channel} ts=${message.ts}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const baseSessionKey = route.sessionKey;
|
const baseSessionKey = route.sessionKey;
|
||||||
const threadContext = resolveSlackThreadContext({ message, replyToMode: ctx.replyToMode });
|
const threadContext = resolveSlackThreadContext({ message, replyToMode: ctx.replyToMode });
|
||||||
const threadTs = threadContext.incomingThreadTs;
|
const threadTs = threadContext.incomingThreadTs;
|
||||||
const isThreadReply = threadContext.isThreadReply;
|
const isThreadReply = threadContext.isThreadReply;
|
||||||
|
const autoThreadId =
|
||||||
|
!isThreadReply && ctx.replyToMode === "all" ? threadContext.messageThreadId : undefined;
|
||||||
|
const threadSessionId = isThreadReply ? threadTs : autoThreadId;
|
||||||
const threadKeys = resolveThreadSessionKeys({
|
const threadKeys = resolveThreadSessionKeys({
|
||||||
baseSessionKey,
|
baseSessionKey,
|
||||||
threadId: isThreadReply ? threadTs : undefined,
|
threadId: threadSessionId,
|
||||||
parentSessionKey: isThreadReply && ctx.threadInheritParent ? baseSessionKey : undefined,
|
parentSessionKey: threadSessionId && ctx.threadInheritParent ? baseSessionKey : undefined,
|
||||||
});
|
});
|
||||||
const sessionKey = threadKeys.sessionKey;
|
const sessionKey = threadKeys.sessionKey;
|
||||||
const historyKey =
|
const historyKey =
|
||||||
isThreadReply && ctx.threadHistoryScope === "thread" ? sessionKey : message.channel;
|
threadSessionId && ctx.threadHistoryScope === "thread" ? sessionKey : message.channel;
|
||||||
|
|
||||||
const mentionRegexes = buildMentionRegexes(cfg, route.agentId);
|
const mentionRegexes = buildMentionRegexes(cfg, route.agentId);
|
||||||
const hasAnyMention = /<@[^>]+>/.test(message.text ?? "");
|
const hasAnyMention = /<@[^>]+>/.test(message.text ?? "");
|
||||||
@ -395,20 +455,19 @@ export async function prepareSlackMessage(params: {
|
|||||||
GroupSubject: isRoomish ? roomLabel : undefined,
|
GroupSubject: isRoomish ? roomLabel : undefined,
|
||||||
From: slackFrom,
|
From: slackFrom,
|
||||||
}) ?? (isDirectMessage ? senderName : roomLabel);
|
}) ?? (isDirectMessage ? senderName : roomLabel);
|
||||||
const textWithId = `${rawBody}\n[slack message id: ${message.ts} channel: ${message.channel}]`;
|
|
||||||
const storePath = resolveStorePath(ctx.cfg.session?.store, {
|
const storePath = resolveStorePath(ctx.cfg.session?.store, {
|
||||||
agentId: route.agentId,
|
agentId: route.agentId,
|
||||||
});
|
});
|
||||||
const envelopeOptions = resolveEnvelopeFormatOptions(ctx.cfg);
|
const envelopeOptions = resolveEnvelopeFormatOptions(ctx.cfg);
|
||||||
const previousTimestamp = readSessionUpdatedAt({
|
const previousTimestamp = readSessionUpdatedAt({
|
||||||
storePath,
|
storePath,
|
||||||
sessionKey: route.sessionKey,
|
sessionKey,
|
||||||
});
|
});
|
||||||
const body = formatInboundEnvelope({
|
const body = formatInboundEnvelope({
|
||||||
channel: "Slack",
|
channel: "Slack",
|
||||||
from: envelopeFrom,
|
from: envelopeFrom,
|
||||||
timestamp: message.ts ? Math.round(Number(message.ts) * 1000) : undefined,
|
timestamp: message.ts ? Math.round(Number(message.ts) * 1000) : undefined,
|
||||||
body: textWithId,
|
body: rawBody,
|
||||||
chatType: isDirectMessage ? "direct" : "channel",
|
chatType: isDirectMessage ? "direct" : "channel",
|
||||||
sender: { name: senderName, id: senderId },
|
sender: { name: senderName, id: senderId },
|
||||||
previousTimestamp,
|
previousTimestamp,
|
||||||
@ -427,9 +486,7 @@ export async function prepareSlackMessage(params: {
|
|||||||
channel: "Slack",
|
channel: "Slack",
|
||||||
from: roomLabel,
|
from: roomLabel,
|
||||||
timestamp: entry.timestamp,
|
timestamp: entry.timestamp,
|
||||||
body: `${entry.body}${
|
body: entry.body,
|
||||||
entry.messageId ? ` [id:${entry.messageId} channel:${message.channel}]` : ""
|
|
||||||
}`,
|
|
||||||
chatType: "channel",
|
chatType: "channel",
|
||||||
senderLabel: entry.sender,
|
senderLabel: entry.sender,
|
||||||
envelope: envelopeOptions,
|
envelope: envelopeOptions,
|
||||||
@ -463,12 +520,11 @@ export async function prepareSlackMessage(params: {
|
|||||||
if (starter?.text) {
|
if (starter?.text) {
|
||||||
const starterUser = starter.userId ? await ctx.resolveUserName(starter.userId) : null;
|
const starterUser = starter.userId ? await ctx.resolveUserName(starter.userId) : 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}]`;
|
|
||||||
threadStarterBody = formatThreadStarterEnvelope({
|
threadStarterBody = formatThreadStarterEnvelope({
|
||||||
channel: "Slack",
|
channel: "Slack",
|
||||||
author: starterName,
|
author: starterName,
|
||||||
timestamp: starter.ts ? Math.round(Number(starter.ts) * 1000) : undefined,
|
timestamp: starter.ts ? Math.round(Number(starter.ts) * 1000) : undefined,
|
||||||
body: starterWithId,
|
body: starter.text,
|
||||||
envelope: envelopeOptions,
|
envelope: envelopeOptions,
|
||||||
});
|
});
|
||||||
const snippet = starter.text.replace(/\s+/g, " ").slice(0, 80);
|
const snippet = starter.text.replace(/\s+/g, " ").slice(0, 80);
|
||||||
|
|||||||
@ -44,7 +44,7 @@ export async function deliverReplies(params: {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
params.runtime.log?.(`delivered reply to ${params.target}`);
|
params.runtime.log?.(`delivered reply to ${params.target} threadTs=${threadTs ?? "none"}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user