fix(slack): prevent duplicate messages and use Slack date formatting

- Skip text forwarder for Slack when execApprovals buttons are enabled
- Use <!date^timestamp^{time}|fallback> for localized expiry time
This commit is contained in:
Kieran Klukas 2026-01-26 02:41:51 -05:00
parent 07ae264704
commit f6be8845b2
No known key found for this signature in database
2 changed files with 36 additions and 1 deletions

View File

@ -158,6 +158,30 @@ function defaultResolveSessionTarget(params: {
};
}
function isSlackExecApprovalsEnabled(cfg: ClawdbotConfig, accountId?: string | null): boolean {
const slackCfg = cfg.channels?.slack as
| {
execApprovals?: { enabled?: boolean; approvers?: Array<string | number> };
accounts?: Record<
string,
{ execApprovals?: { enabled?: boolean; approvers?: Array<string | number> } }
>;
}
| undefined;
if (!slackCfg) return false;
// Check account-specific config first
if (accountId && slackCfg.accounts?.[accountId]?.execApprovals?.enabled) {
const approvers = slackCfg.accounts[accountId].execApprovals?.approvers;
return Array.isArray(approvers) && approvers.length > 0;
}
// Fall back to top-level config
if (slackCfg.execApprovals?.enabled) {
const approvers = slackCfg.execApprovals?.approvers;
return Array.isArray(approvers) && approvers.length > 0;
}
return false;
}
async function deliverToTargets(params: {
cfg: ClawdbotConfig;
targets: ForwardTarget[];
@ -169,6 +193,10 @@ async function deliverToTargets(params: {
if (params.shouldSend && !params.shouldSend()) return;
const channel = normalizeMessageChannel(target.channel) ?? target.channel;
if (!isDeliverableMessageChannel(channel)) return;
// Skip Slack text messages when Slack exec approval buttons are enabled
if (channel === "slack" && isSlackExecApprovalsEnabled(params.cfg, target.accountId)) {
return;
}
try {
await params.deliver({
cfg: params.cfg,

View File

@ -72,7 +72,9 @@ function formatApprovalBlocks(request: ExecApprovalRequest) {
const commandText = request.request.command;
const commandPreview =
commandText.length > 2000 ? `${commandText.slice(0, 2000)}...` : commandText;
const expiresAtUnix = Math.floor(request.expiresAtMs / 1000);
const expiresIn = Math.max(0, Math.round((request.expiresAtMs - Date.now()) / 1000));
const fallbackTime = new Date(request.expiresAtMs).toISOString();
const contextParts: string[] = [];
if (request.request.cwd) contextParts.push(`*CWD:* ${request.request.cwd}`);
@ -116,7 +118,12 @@ function formatApprovalBlocks(request: ExecApprovalRequest) {
blocks.push({
type: "context",
elements: [{ type: "mrkdwn", text: `Expires in ${expiresIn}s | ID: \`${request.id}\`` }],
elements: [
{
type: "mrkdwn",
text: `Expires <!date^${expiresAtUnix}^{time}|${fallbackTime}> (${expiresIn}s) | ID: \`${request.id}\``,
},
],
});
blocks.push({