mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 09:11:26 +00:00
Slack: implement replyToMode threading for tool path
- Add shared hasRepliedRef state between auto-reply and tool paths - Extract buildSlackThreadingContext helper in agent-runner.ts - Extract resolveThreadTsFromContext helper in slack-actions.ts - Update docs with clear replyToMode table (off/first/all) - Add tests for first mode behavior across multiple messages
This commit is contained in:
committed by
Peter Steinberger
parent
29e6f13b29
commit
b4663ed11c
@@ -34,9 +34,60 @@ const messagingActions = new Set([
|
||||
const reactionsActions = new Set(["react", "reactions"]);
|
||||
const pinActions = new Set(["pinMessage", "unpinMessage", "listPins"]);
|
||||
|
||||
export type SlackActionContext = {
|
||||
/** Current channel ID for auto-threading. */
|
||||
currentChannelId?: string;
|
||||
/** Current thread timestamp for auto-threading. */
|
||||
currentThreadTs?: string;
|
||||
/** Reply-to mode for auto-threading. */
|
||||
replyToMode?: "off" | "first" | "all";
|
||||
/** Mutable ref to track if a reply was sent (for "first" mode). */
|
||||
hasRepliedRef?: { value: boolean };
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolve threadTs for a Slack message based on context and replyToMode.
|
||||
* - "all": always inject threadTs
|
||||
* - "first": inject only for first message (updates hasRepliedRef)
|
||||
* - "off": never auto-inject
|
||||
*/
|
||||
function resolveThreadTsFromContext(
|
||||
explicitThreadTs: string | undefined,
|
||||
targetChannel: string,
|
||||
context: SlackActionContext | undefined,
|
||||
): string | undefined {
|
||||
// Agent explicitly provided threadTs - use it
|
||||
if (explicitThreadTs) return explicitThreadTs;
|
||||
// No context or missing required fields
|
||||
if (!context?.currentThreadTs || !context?.currentChannelId) return undefined;
|
||||
|
||||
// Normalize target (strip "channel:" prefix if present)
|
||||
const normalizedTarget = targetChannel.startsWith("channel:")
|
||||
? targetChannel.slice("channel:".length)
|
||||
: targetChannel;
|
||||
|
||||
// Different channel - don't inject
|
||||
if (normalizedTarget !== context.currentChannelId) return undefined;
|
||||
|
||||
// Check replyToMode
|
||||
if (context.replyToMode === "all") {
|
||||
return context.currentThreadTs;
|
||||
}
|
||||
if (
|
||||
context.replyToMode === "first" &&
|
||||
context.hasRepliedRef &&
|
||||
!context.hasRepliedRef.value
|
||||
) {
|
||||
context.hasRepliedRef.value = true;
|
||||
return context.currentThreadTs;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export async function handleSlackAction(
|
||||
params: Record<string, unknown>,
|
||||
cfg: ClawdbotConfig,
|
||||
context?: SlackActionContext,
|
||||
): Promise<AgentToolResult<unknown>> {
|
||||
const action = readStringParam(params, "action", { required: true });
|
||||
const accountId = readStringParam(params, "accountId");
|
||||
@@ -91,7 +142,11 @@ export async function handleSlackAction(
|
||||
const to = readStringParam(params, "to", { required: true });
|
||||
const content = readStringParam(params, "content", { required: true });
|
||||
const mediaUrl = readStringParam(params, "mediaUrl");
|
||||
const threadTs = readStringParam(params, "threadTs");
|
||||
const threadTs = resolveThreadTsFromContext(
|
||||
readStringParam(params, "threadTs"),
|
||||
to,
|
||||
context,
|
||||
);
|
||||
const result = await sendSlackMessage(to, content, {
|
||||
accountId: accountId ?? undefined,
|
||||
mediaUrl: mediaUrl ?? undefined,
|
||||
|
||||
Reference in New Issue
Block a user