@openclaw/slack 2026.9.1-beta.1 → 2026.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{account-inspect-CN2hBsim.js → account-inspect-C3sePHWa.js} +3 -3
- package/dist/account-inspect-api.js +1 -1
- package/dist/{action-runtime-BoSeX09X.js → action-runtime-DftQWpI4.js} +84 -66
- package/dist/action-runtime.runtime-rHI3UAh4.js +2 -0
- package/dist/{actions-XdQyjG_p.js → actions-CFkvEv1d.js} +198 -54
- package/dist/api.js +14 -14
- package/dist/{approval-handler.runtime-DxXfGX_Z.js → approval-handler.runtime-BeILKdw4.js} +47 -25
- package/dist/{channel-C_vnTgxo.js → channel-CmOuxbOD.js} +76 -133
- package/dist/channel-config-api.js +1 -1
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel-type-j-2lalcC.js → channel-type-BWt2CRaI.js} +2 -2
- package/dist/{channel.setup-aal8tokM.js → channel.setup-D5z1ReaT.js} +29 -23
- package/dist/{config-schema-CN3XZRX5.js → config-schema-JDx_f9Zb.js} +26 -17
- package/dist/{cursor-pages-eX7p8Wwt.js → cursor-pages-ByFwo-Py.js} +7 -1
- package/dist/{directory-config-DqfQ42OV.js → directory-config-Bmw4ykSy.js} +4 -3
- package/dist/directory-contract-api.js +1 -1
- package/dist/{directory-live-lC6X_Ru-.js → directory-live-BuBPHmju.js} +3 -8
- package/dist/{doctor-contract-BAy7-EUQ.js → doctor-contract-BNjw9bxb.js} +5 -4
- package/dist/doctor-contract-api.js +1 -1
- package/dist/{message-tool-api-CWosHSi5.js → message-tool-api-uNEfQYGY.js} +19 -2
- package/dist/message-tool-api.js +1 -1
- package/dist/{monitor-DrNCGneD.js → monitor-DI07pfJl.js} +3 -3
- package/dist/{outbound-adapter-DkT2vUE9.js → outbound-adapter-B5QyTRpJ.js} +96 -91
- package/dist/{pipeline.runtime-CRRng_Nw.js → pipeline.runtime-BdIwZgm4.js} +596 -920
- package/dist/{policy-BoP6t88v.js → policy-Bh5TOqII.js} +7 -4
- package/dist/{probe-4_aHtVT3.js → probe-Biy--yhL.js} +251 -12
- package/dist/{provider-D4nDgxCh.js → provider-DoaUvAM4.js} +5621 -4944
- package/dist/{relay-source-CEmze11m.js → relay-source-D4uecp68.js} +6 -4
- package/dist/{replies-D9XMM_S0.js → replies-DENuTcPO.js} +21 -16
- package/dist/reply-blocks-PRkhyAN7.js +790 -0
- package/dist/{resolve-channels-DSamDgVs.js → resolve-channels-DQGV2VrE.js} +6 -10
- package/dist/{resolve-users-CIzwbaJR.js → resolve-users-DRGt_Q4I.js} +5 -4
- package/dist/runtime-api.js +10 -10
- package/dist/{scopes-BBrUzpBH.js → scopes-CZzeGdWV.js} +1 -1
- package/dist/{send-DtjEnlLh.js → send-DElUcgIs.js} +32 -24
- package/dist/send-results-eeOu_HYm.js +23 -0
- package/dist/{send.runtime-DQeNKa2J.js → send.runtime-C8D6cKGm.js} +1 -1
- package/dist/{group-policy-CU1tEGpS.js → session-status-BynwcZSO.js} +284 -1013
- package/dist/setup-plugin-api.js +1 -1
- package/dist/{setup-surface-CsBCM4rP.js → setup-surface-B1_gpGBl.js} +5 -5
- package/dist/{shared-D9GeeR9K.js → shared-CNQsWJT7.js} +7 -5
- package/dist/{slash-dispatch.runtime-Chr3YHnT.js → slash-dispatch.runtime-DhsXavSY.js} +2 -2
- package/dist/{target-parsing-BnMD2ZqZ.js → target-parsing-PbxGDbr9.js} +11 -6
- package/dist/{targets-Cx5W_n3W.js → targets-7FexXJ62.js} +1 -1
- package/dist/{thread-ts-DUGhaYKq.js → thread-ts-CukseRVL.js} +5 -1
- package/node_modules/@slack/bolt/node_modules/negotiator/lib/accept.js +54 -0
- package/node_modules/@slack/bolt/node_modules/negotiator/lib/charset.js +10 -34
- package/node_modules/@slack/bolt/node_modules/negotiator/lib/encoding.js +9 -30
- package/node_modules/@slack/bolt/node_modules/negotiator/lib/language.js +15 -36
- package/node_modules/@slack/bolt/node_modules/negotiator/lib/mediaType.js +16 -134
- package/node_modules/@slack/bolt/node_modules/negotiator/node_modules/content-type/LICENSE +22 -0
- package/node_modules/@slack/bolt/node_modules/negotiator/node_modules/content-type/README.md +71 -0
- package/node_modules/@slack/bolt/node_modules/negotiator/node_modules/content-type/dist/index.d.ts +46 -0
- package/node_modules/@slack/bolt/node_modules/negotiator/node_modules/content-type/dist/index.js +176 -0
- package/node_modules/@slack/bolt/node_modules/negotiator/node_modules/content-type/dist/index.js.map +1 -0
- package/node_modules/@slack/bolt/node_modules/negotiator/node_modules/content-type/package.json +52 -0
- package/node_modules/@slack/bolt/node_modules/negotiator/package.json +16 -10
- package/node_modules/typebox/build/format/email.mjs +1 -1
- package/node_modules/typebox/build/format/idna/idn-hostname.mjs +19 -1
- package/node_modules/typebox/build/format/json_pointer.d.mts +2 -2
- package/node_modules/typebox/build/format/json_pointer.mjs +2 -2
- package/node_modules/typebox/build/format/json_pointer_uri_fragment.d.mts +2 -2
- package/node_modules/typebox/build/format/json_pointer_uri_fragment.mjs +2 -2
- package/node_modules/typebox/build/format/relative_json_pointer.d.mts +2 -2
- package/node_modules/typebox/build/format/relative_json_pointer.mjs +2 -2
- package/node_modules/typebox/build/system/memory/assign.mjs +2 -1
- package/node_modules/typebox/build/system/memory/create.mjs +3 -4
- package/node_modules/typebox/build/system/memory/discard.mjs +2 -2
- package/node_modules/typebox/build/system/memory/freeze.d.mts +4 -0
- package/node_modules/typebox/build/system/memory/freeze.mjs +6 -0
- package/node_modules/typebox/build/system/memory/update.mjs +3 -2
- package/node_modules/typebox/package.json +1 -1
- package/node_modules/undici/lib/dispatcher/balanced-pool.js +4 -2
- package/node_modules/undici/lib/dispatcher/client-h1.js +12 -4
- package/node_modules/undici/lib/dispatcher/client-h2.js +70 -14
- package/node_modules/undici/lib/dispatcher/client.js +6 -2
- package/node_modules/undici/lib/handler/cache-handler.js +21 -5
- package/node_modules/undici/lib/handler/retry-handler.js +42 -7
- package/node_modules/undici/lib/interceptor/cache.js +20 -1
- package/node_modules/undici/lib/interceptor/decompress.js +146 -14
- package/node_modules/undici/lib/interceptor/dump.js +10 -23
- package/node_modules/undici/lib/web/eventsource/eventsource-stream.js +245 -150
- package/node_modules/undici/lib/web/websocket/connection.js +1 -1
- package/node_modules/undici/lib/web/websocket/permessage-deflate.js +5 -0
- package/node_modules/undici/lib/web/websocket/stream/websocketstream.js +8 -10
- package/node_modules/undici/package.json +1 -1
- package/node_modules/undici/types/interceptors.d.ts +2 -0
- package/openclaw.plugin.json +17 -9
- package/package.json +6 -6
- package/skills/_vendor/slack-skills-plugin.LICENSE +21 -0
- package/skills/_vendor/slack-skills-plugin.json +31 -0
- package/skills/block-kit/SKILL.md +37 -0
- package/skills/block-kit/references/official-block-kit.md +257 -0
- package/skills/block-kit/references/official-common-patterns.md +385 -0
- package/skills/slack/SKILL.md +1 -1
- package/dist/action-runtime.runtime-Dpa16J87.js +0 -2
- package/dist/conversation-binding-route-DFVLUdYF.js +0 -115
- package/dist/slack-client-kind-hSfjXyUM.js +0 -94
- package/node_modules/@slack/bolt/node_modules/negotiator/HISTORY.md +0 -114
|
@@ -1,225 +1,46 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as formatSlackTarget } from "./target-parsing-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { A as countSlackTextUtf8Bytes, C as buildSlackBlocksFallbackText, E as SLACK_EDIT_TEXT_MAX_BYTES, H as SLACK_SESSION_LINK_ACTION_ID, M as truncateSlackTextByUtf8Bytes, Nt as resolveSlackReplyToMode, Y as normalizeSlackOutboundText, at as escapeSlackMrkdwn, c as qualifySlackRoutePeerId, j as truncateSlackText, k as SLACK_TEXT_LIMIT, v as hasSlackNativeDataBlock } from "./session-status-BynwcZSO.js";
|
|
2
|
+
import { n as formatSlackTarget } from "./target-parsing-PbxGDbr9.js";
|
|
3
|
+
import { C as normalizeAllowListLower, E as resolveSlackAllowListMatch, o as formatSlackError, w as normalizeSlackAllowOwnerEntry } from "./probe-Biy--yhL.js";
|
|
4
|
+
import { o as resolveSlackReplyRenderPlan } from "./reply-blocks-PRkhyAN7.js";
|
|
5
|
+
import { n as resolveSlackReplyThreadTs } from "./thread-ts-CukseRVL.js";
|
|
5
6
|
import { n as resolveSlackStreamingMode, t as resolveSlackNativeStreaming } from "./streaming-compat-B-e0mqM0.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { n as
|
|
11
|
-
import { a as resolveDeliveredSlackReplyThreadTs, c as emitSlackMessageSentHooks, i as readSlackReplyBlocks, n as deliverReplies, o as resolveSlackThreadTs, s as sanitizeSlackMonitorReplyPayload, t as createSlackReplyDeliveryPlan } from "./replies-D9XMM_S0.js";
|
|
7
|
+
import { D as resolveSlackMessageText$1, E as resolveSlackBlocksText, O as buildSlackEditTextPayload, S as formatSlackFileReferenceList, T as isSlackUnfurlAttachment, a as editSlackRenderedMessage, d as reactSlackMessage, i as editSlackMessage, m as removeSlackReaction, n as deleteSlackMessage, v as logVerbose$1, w as hasSlackTableBlock, x as formatSlackFileReference } from "./actions-CFkvEv1d.js";
|
|
8
|
+
import { c as hasSlackThreadFailureNotice, d as recordSlackThreadFailureNotice, f as recordSlackThreadParticipation, l as hasSlackThreadParticipation, m as buildSlackNativeDataDeliveryPlan, o as clearSlackThreadFailureNotice, r as sendMessageSlack, u as hasSlackThreadParticipationWithPersistence } from "./send-DElUcgIs.js";
|
|
9
|
+
import { A as resolveStorePath, C as resolveSlackChatType, D as readSessionUpdatedAt, E as getSessionEntry, N as stripSlackMentionsForCommandDetection, O as resolveChannelContextVisibilityMode, S as normalizeSlackChannelType, T as readSlackAssistantThreadContext, _ as SlackStreamNotDeliveredError, b as startSlackStream, d as resolveSlackRoutingContext, f as registerSlackSessionRun, g as resolveSlackEffectiveAllowFrom, h as resolveSlackCommandIngress, j as updateLastRoute, k as resolveChannelResetConfig, l as authorizeSlackDirectMessage, m as authorizeSlackBotRoomMessage, o as resolveSlackRoomContextHints, p as resolveSlackThreadTargets, r as trackSlackDraftMessage, u as resolveConversationLabel$1, v as appendSlackStream, w as buildSlackAssistantThreadMetadata, x as stopSlackStream, y as markSlackStreamFallbackDelivered } from "./provider-DoaUvAM4.js";
|
|
10
|
+
import { n as resolveSlackChannelConfig } from "./policy-Bh5TOqII.js";
|
|
11
|
+
import { a as resolveSlackThreadTs, i as readSlackReplyBlocks, n as deliverReplies, o as sanitizeSlackMonitorReplyPayload, s as emitSlackMessageSentHooks, t as createSlackReplyDeliveryPlan } from "./replies-DENuTcPO.js";
|
|
12
12
|
import { asOptionalRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
13
|
-
import {
|
|
14
|
-
import { buildChannelProgressDraftLine,
|
|
13
|
+
import { resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
|
|
14
|
+
import { buildChannelProgressDraftLine, createChannelMessageReplyPipeline, createChannelProgressDraftCompositor, createChannelProgressWorkCounter, createDraftStreamLoop, createFinalizableDraftStreamControlsForState, defineFinalizableLivePreviewAdapter, deliverWithFinalizableLivePreviewAdapter, formatChannelProgressDraftText, formatPlanChecklistLines, isChannelProgressDraftWorkToolName, resolveAgentOutboundIdentity, resolveChannelMessageSourceReplyDeliveryMode, resolveChannelProgressDraftConfig, resolveChannelProgressDraftMaxLineChars, resolveChannelStreamingBlockEnabled, resolveChannelStreamingPreviewToolProgress, resolveChannelStreamingSuppressDefaultToolProgressMessages } from "openclaw/plugin-sdk/channel-outbound";
|
|
15
15
|
import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
|
16
16
|
import { danger, logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env";
|
|
17
17
|
import { sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
|
|
18
|
+
import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
|
|
19
|
+
import { buildTtsSupplementMediaPayload, getReplyPayloadTtsSupplement, isReplyPayloadNonTerminalToolErrorWarning, resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
|
|
18
20
|
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
19
21
|
import { createHash } from "node:crypto";
|
|
20
22
|
import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
|
|
21
23
|
import { ensureConfiguredBindingRouteReady } from "openclaw/plugin-sdk/conversation-runtime";
|
|
24
|
+
import { resolveChannelImplicitMentions } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
22
25
|
import fs from "node:fs/promises";
|
|
23
26
|
import { runTasksWithConcurrency } from "openclaw/plugin-sdk/concurrency-runtime";
|
|
24
27
|
import { formatErrorMessage, toErrorObject } from "openclaw/plugin-sdk/error-runtime";
|
|
25
|
-
import { buildTtsSupplementMediaPayload, getReplyPayloadTtsSupplement, isReplyPayloadNonTerminalToolErrorWarning, resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
|
|
26
28
|
import { filterSupplementalContextItems, resolvePinnedMainDmOwnerFromAllowlist, shouldIncludeSupplementalContext } from "openclaw/plugin-sdk/security-runtime";
|
|
27
29
|
import { mimeTypeFromFilePath } from "openclaw/plugin-sdk/media-mime";
|
|
28
30
|
import { createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
|
|
29
31
|
import { mergePairLoopGuardConfig } from "openclaw/plugin-sdk/pair-loop-guard-runtime";
|
|
30
|
-
import { enqueueRoutedSystemEvent } from "openclaw/plugin-sdk/system-event-runtime";
|
|
31
|
-
import { resolveChannelImplicitMentions } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
32
32
|
import { asDateTimestampMs, asFiniteNumberInRange, parseStrictFiniteNumber, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
|
|
33
|
-
import { getGlobalHookRunner } from "openclaw/plugin-sdk/plugin-runtime";
|
|
34
|
-
import { buildChannelInboundEventContext, buildMentionRegexes, classifyChannelInboundEvent, dispatchChannelInboundTurn, formatInboundEnvelope, formatInboundMediaUnavailableText, hasVisibleInboundReplyDispatch, implicitMentionKindWhen, logInboundDrop, matchesMentionWithExplicit, readAgentRunTerminalOutcome, recordDroppedChannelInboundHistory, resolveEnvelopeFormatOptions, resolveInboundMentionDecision, resolveInboundReplyDispatchCounts, resolveInboundSupplementalSenderAllowed, resolveUnmentionedGroupInboundPolicy, toInboundMediaFactsWithMetadata } from "openclaw/plugin-sdk/channel-inbound";
|
|
35
33
|
import { resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
|
|
34
|
+
import { enqueueRoutedSystemEvent } from "openclaw/plugin-sdk/system-event-runtime";
|
|
35
|
+
import { getGlobalHookRunner } from "openclaw/plugin-sdk/plugin-runtime";
|
|
36
|
+
import { buildChannelInboundEventContext, buildMentionRegexes, classifyChannelInboundEvent, dispatchChannelInboundTurn, formatInboundEnvelope, formatInboundMediaUnavailableText, hasVisibleInboundReplyDispatch, implicitMentionKindWhen, matchesMentionWithExplicit, readAgentRunTerminalOutcome, recordDroppedChannelInboundHistory, resolveEnvelopeFormatOptions, resolveInboundMentionDecision, resolveInboundReplyDispatchCounts, resolveInboundSupplementalSenderAllowed, resolveUnmentionedGroupInboundPolicy, toInboundMediaFactsWithMetadata } from "openclaw/plugin-sdk/channel-inbound";
|
|
36
37
|
import { resolveGatewayPublicOrigin } from "openclaw/plugin-sdk/config-contracts";
|
|
37
38
|
import { buildControlUiSessionPath } from "openclaw/plugin-sdk/session-discussion";
|
|
38
|
-
import {
|
|
39
|
+
import { createStatusReactionController, logAckFailure, logTypingFailure, resolveAckReaction, shouldAckReaction } from "openclaw/plugin-sdk/channel-feedback";
|
|
39
40
|
import { hasControlCommand } from "openclaw/plugin-sdk/command-detection";
|
|
40
41
|
import { isAbortRequestText } from "openclaw/plugin-sdk/command-primitives-runtime";
|
|
41
42
|
import { shouldHandleTextCommands } from "openclaw/plugin-sdk/command-surface";
|
|
42
43
|
import { createChannelPreflightAudio, formatAudioTranscriptForAgent } from "openclaw/plugin-sdk/media-understanding-runtime";
|
|
43
|
-
//#region extensions/slack/src/streaming.ts
|
|
44
|
-
/**
|
|
45
|
-
* Thrown when Slack definitively rejects a stream flush/finalize while text
|
|
46
|
-
* remains buffered locally by the Slack SDK. Carries the pending text so the
|
|
47
|
-
* caller can deliver it via the normal Slack reply path.
|
|
48
|
-
*/
|
|
49
|
-
var SlackStreamNotDeliveredError = class extends Error {
|
|
50
|
-
constructor(pendingText, slackCode) {
|
|
51
|
-
super(`slack-stream: finalize failed with ${slackCode} before buffered text reached Slack (${pendingText.length} chars pending)`);
|
|
52
|
-
this.name = "SlackStreamNotDeliveredError";
|
|
53
|
-
this.pendingText = pendingText;
|
|
54
|
-
this.slackCode = slackCode;
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
/**
|
|
58
|
-
* Start a new Slack text stream.
|
|
59
|
-
*
|
|
60
|
-
* Returns a {@link SlackStreamSession} that should be passed to
|
|
61
|
-
* {@link appendSlackStream} and {@link stopSlackStream}.
|
|
62
|
-
*
|
|
63
|
-
* The first chunk of text can optionally be included via `text`.
|
|
64
|
-
*/
|
|
65
|
-
async function startSlackStream(params) {
|
|
66
|
-
const { client, channel, threadTs, text, chunks, taskDisplayMode, teamId, userId, identity } = params;
|
|
67
|
-
const identityPayload = identity?.iconUrl ? {
|
|
68
|
-
...identity.username ? { username: identity.username } : {},
|
|
69
|
-
icon_url: identity.iconUrl
|
|
70
|
-
} : identity?.iconEmoji ? {
|
|
71
|
-
...identity.username ? { username: identity.username } : {},
|
|
72
|
-
icon_emoji: identity.iconEmoji
|
|
73
|
-
} : identity?.username ? { username: identity.username } : {};
|
|
74
|
-
logVerbose(`slack-stream: starting stream in ${channel} thread=${threadTs}${teamId ? ` team=${teamId}` : ""}${userId ? ` user=${userId}` : ""}`);
|
|
75
|
-
const streamer = client.chatStream({
|
|
76
|
-
channel,
|
|
77
|
-
thread_ts: threadTs,
|
|
78
|
-
...taskDisplayMode ? { task_display_mode: taskDisplayMode } : {},
|
|
79
|
-
...teamId ? { recipient_team_id: teamId } : {},
|
|
80
|
-
...userId ? { recipient_user_id: userId } : {},
|
|
81
|
-
...identityPayload
|
|
82
|
-
});
|
|
83
|
-
const session = {
|
|
84
|
-
streamer,
|
|
85
|
-
channel,
|
|
86
|
-
threadTs,
|
|
87
|
-
stopped: false,
|
|
88
|
-
delivered: false,
|
|
89
|
-
pendingText: ""
|
|
90
|
-
};
|
|
91
|
-
if (text || chunks?.length) {
|
|
92
|
-
if (text) session.pendingText += text;
|
|
93
|
-
try {
|
|
94
|
-
const result = await streamer.append({
|
|
95
|
-
...text ? { markdown_text: text } : {},
|
|
96
|
-
...chunks?.length ? { chunks } : {}
|
|
97
|
-
});
|
|
98
|
-
if (result) {
|
|
99
|
-
session.delivered = true;
|
|
100
|
-
session.pendingText = "";
|
|
101
|
-
}
|
|
102
|
-
logVerbose(`slack-stream: appended initial payload (${text?.length ?? 0} chars, ${chunks?.length ?? 0} chunks, ${result ? "flushed" : "buffered"})`);
|
|
103
|
-
} catch (err) {
|
|
104
|
-
if (isBenignSlackFinalizeError(err) && session.pendingText) throw new SlackStreamNotDeliveredError(session.pendingText, extractSlackErrorCode(err) ?? "unknown");
|
|
105
|
-
throw err;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return session;
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* Append markdown text to an active Slack stream.
|
|
112
|
-
*/
|
|
113
|
-
async function appendSlackStream(params) {
|
|
114
|
-
const { session, text, chunks } = params;
|
|
115
|
-
if (session.stopped) {
|
|
116
|
-
logVerbose("slack-stream: attempted to append to a stopped stream, ignoring");
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
if (!text && !chunks?.length) return;
|
|
120
|
-
if (text) session.pendingText += text;
|
|
121
|
-
try {
|
|
122
|
-
const result = await session.streamer.append({
|
|
123
|
-
...text ? { markdown_text: text } : {},
|
|
124
|
-
...chunks?.length ? { chunks } : {}
|
|
125
|
-
});
|
|
126
|
-
if (result) {
|
|
127
|
-
session.delivered = true;
|
|
128
|
-
session.pendingText = "";
|
|
129
|
-
}
|
|
130
|
-
logVerbose(`slack-stream: appended ${text?.length ?? 0} chars, ${chunks?.length ?? 0} chunks (${result ? "flushed" : "buffered"})`);
|
|
131
|
-
} catch (err) {
|
|
132
|
-
if (isBenignSlackFinalizeError(err) && session.pendingText) throw new SlackStreamNotDeliveredError(session.pendingText, extractSlackErrorCode(err) ?? "unknown");
|
|
133
|
-
throw err;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Stop (finalize) a Slack stream.
|
|
138
|
-
*
|
|
139
|
-
* After calling this the stream message becomes a normal Slack message.
|
|
140
|
-
* Optionally include final chunks to append before stopping.
|
|
141
|
-
*
|
|
142
|
-
* If Slack's `chat.stopStream` responds with a definitive recipient/channel
|
|
143
|
-
* rejection while text is still buffered locally, this function throws a
|
|
144
|
-
* {@link SlackStreamNotDeliveredError} carrying that pending text so the caller
|
|
145
|
-
* can deliver it through the normal Slack reply path. Ambiguous failures
|
|
146
|
-
* propagate unchanged because Slack may have committed the request.
|
|
147
|
-
*
|
|
148
|
-
* If Slack responds with a known benign finalize error (see
|
|
149
|
-
* {@link BENIGN_SLACK_FINALIZE_ERROR_CODES}) after prior `append` calls already
|
|
150
|
-
* landed, the error is swallowed and the session is marked stopped - the
|
|
151
|
-
* already-delivered text stays visible.
|
|
152
|
-
*
|
|
153
|
-
* Errors without buffered text propagate unchanged.
|
|
154
|
-
*
|
|
155
|
-
* On success, returns the finalized message's Slack `ts` (when reported) so the
|
|
156
|
-
* caller can emit the `message_sent` hook with a populated `messageId`.
|
|
157
|
-
*/
|
|
158
|
-
async function stopSlackStream(params) {
|
|
159
|
-
const { session, chunks, metadata } = params;
|
|
160
|
-
if (session.stopped) {
|
|
161
|
-
logVerbose("slack-stream: stream already stopped, ignoring duplicate stop");
|
|
162
|
-
return {};
|
|
163
|
-
}
|
|
164
|
-
session.stopped = true;
|
|
165
|
-
logVerbose(`slack-stream: stopping stream in ${session.channel} thread=${session.threadTs}`);
|
|
166
|
-
try {
|
|
167
|
-
const stopResponse = await session.streamer.stop(chunks?.length || metadata ? {
|
|
168
|
-
...chunks?.length ? { chunks } : {},
|
|
169
|
-
...metadata ? { metadata } : {}
|
|
170
|
-
} : void 0);
|
|
171
|
-
session.delivered = true;
|
|
172
|
-
session.pendingText = "";
|
|
173
|
-
logVerbose("slack-stream: stream stopped");
|
|
174
|
-
const messageId = stopResponse?.ts ?? stopResponse?.message?.ts;
|
|
175
|
-
return messageId ? { messageId } : {};
|
|
176
|
-
} catch (err) {
|
|
177
|
-
const code = extractSlackErrorCode(err) ?? "unknown";
|
|
178
|
-
const benignFinalizeError = isBenignSlackFinalizeError(err);
|
|
179
|
-
if (session.pendingText && (benignFinalizeError || code === "missing_scope")) throw new SlackStreamNotDeliveredError(session.pendingText, code);
|
|
180
|
-
if (benignFinalizeError) {
|
|
181
|
-
if (session.delivered) {
|
|
182
|
-
logVerbose(`slack-stream: finalize rejected by Slack (${code}); prior appends delivered, treating stream as stopped`);
|
|
183
|
-
return {};
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
throw err;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Slack API error codes that indicate `chat.stopStream` (or the
|
|
191
|
-
* `chat.startStream` call the SDK issues inside `stop()` when the buffer
|
|
192
|
-
* never flushed) cannot finalize the stream for the current recipient or
|
|
193
|
-
* team. Either the caller falls back to a normal message (see
|
|
194
|
-
* {@link SlackStreamNotDeliveredError}) or, if prior appends already
|
|
195
|
-
* delivered text, the error is logged verbosely and swallowed.
|
|
196
|
-
*/
|
|
197
|
-
const BENIGN_SLACK_FINALIZE_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
198
|
-
"user_not_found",
|
|
199
|
-
"team_not_found",
|
|
200
|
-
"missing_recipient_user_id",
|
|
201
|
-
"method_not_supported_for_channel_type"
|
|
202
|
-
]);
|
|
203
|
-
function isBenignSlackFinalizeError(err) {
|
|
204
|
-
const code = extractSlackErrorCode(err);
|
|
205
|
-
return code !== void 0 && BENIGN_SLACK_FINALIZE_ERROR_CODES.has(code);
|
|
206
|
-
}
|
|
207
|
-
function extractSlackErrorCode(err) {
|
|
208
|
-
if (!err || typeof err !== "object") return;
|
|
209
|
-
const record = err;
|
|
210
|
-
if (record.data && typeof record.data === "object") {
|
|
211
|
-
const inner = record.data.error;
|
|
212
|
-
if (typeof inner === "string") return inner;
|
|
213
|
-
}
|
|
214
|
-
return (typeof record.message === "string" ? record.message : "").match(/An API error occurred:\s*([a-z_][a-z0-9_]*)/i)?.[1];
|
|
215
|
-
}
|
|
216
|
-
function markSlackStreamFallbackDelivered(session) {
|
|
217
|
-
const nativeStreamWasStarted = session.delivered || Boolean(session.streamer.ts);
|
|
218
|
-
session.pendingText = "";
|
|
219
|
-
session.streamer.buffer = "";
|
|
220
|
-
session.stopped = !nativeStreamWasStarted;
|
|
221
|
-
}
|
|
222
|
-
//#endregion
|
|
223
44
|
//#region extensions/slack/src/monitor/message-handler/timestamp.ts
|
|
224
45
|
const SLACK_TIMESTAMP_RE = /^\d+(?:\.\d+)?$/;
|
|
225
46
|
const MAX_SAFE_SLACK_TIMESTAMP_SECONDS = Number.MAX_SAFE_INTEGER / 1e3;
|
|
@@ -269,7 +90,8 @@ function resolveExplicitSlackProgressTitle(entry) {
|
|
|
269
90
|
return trimmed && trimmed.toLowerCase() !== "auto" ? trimmed : void 0;
|
|
270
91
|
}
|
|
271
92
|
function resolveSlackProgressStyle(entry) {
|
|
272
|
-
|
|
93
|
+
const progress = entry?.streaming?.progress;
|
|
94
|
+
return progress?.style ?? (progress?.toolProgress === false ? "compact" : "card");
|
|
273
95
|
}
|
|
274
96
|
function resolveSlackNativeProgressTaskCards(entry) {
|
|
275
97
|
if (resolveSlackProgressStyle(entry) === "compact") return false;
|
|
@@ -479,6 +301,7 @@ function createSlackDraftStream(params) {
|
|
|
479
301
|
};
|
|
480
302
|
const { loop, update, discardPending, seal } = createFinalizableDraftStreamControlsForState({
|
|
481
303
|
throttleMs,
|
|
304
|
+
coalesceInFlight: true,
|
|
482
305
|
state: streamState,
|
|
483
306
|
sendOrEditStreamMessage,
|
|
484
307
|
emptyValue: "",
|
|
@@ -630,6 +453,7 @@ const SLACK_PROGRESS_CHUNK_TEXT_MAX = 256;
|
|
|
630
453
|
const SLACK_PROGRESS_TASK_TITLE_MAX = 120;
|
|
631
454
|
const SLACK_PROGRESS_PLAN_FALLBACK_TITLE = "Thinking";
|
|
632
455
|
const SLACK_PROGRESS_LINE_DELTA_RE = /(?:^|\s)\+(\d+)\s+[−-](\d+)(?=\s|$)/u;
|
|
456
|
+
const SLACK_ATTENTION_TASK_PREFIX = "openclaw-attention-";
|
|
633
457
|
function buildSessionSources(url) {
|
|
634
458
|
return [{
|
|
635
459
|
type: "url_source",
|
|
@@ -661,23 +485,23 @@ function compactTitle(value) {
|
|
|
661
485
|
function compactChunkText(value) {
|
|
662
486
|
return truncateSlackText(value.replace(/\s+/g, " ").trim(), SLACK_PROGRESS_CHUNK_TEXT_MAX);
|
|
663
487
|
}
|
|
488
|
+
function renderProgressCardText(text, enclosingStyle) {
|
|
489
|
+
return normalizeSlackOutboundText(text, {
|
|
490
|
+
mentions: "escape",
|
|
491
|
+
enclosingStyle
|
|
492
|
+
});
|
|
493
|
+
}
|
|
664
494
|
function lineDetailParts(line) {
|
|
665
495
|
return [line.detail, line.status && line.status !== "completed" && !line.detail?.includes(line.status) ? line.status : void 0].map((part) => part?.trim()).filter((part) => Boolean(part));
|
|
666
496
|
}
|
|
667
|
-
function
|
|
668
|
-
return `${line.icon ?? "•"} *${escapeSlackMrkdwn(line.label)}*`;
|
|
669
|
-
}
|
|
670
|
-
function isAuthoredProgressLine(line) {
|
|
671
|
-
return line.id === "reasoning" || line.id?.startsWith("commentary:") === true;
|
|
672
|
-
}
|
|
673
|
-
function legacyLineDetail(line, maxChars) {
|
|
497
|
+
function activityLineDetail(line, maxChars) {
|
|
674
498
|
const detail = lineDetailParts(line).join(" · ");
|
|
675
499
|
if (detail) return escapeSlackMrkdwn(compactDetail(detail, maxChars));
|
|
676
|
-
|
|
677
|
-
return "—";
|
|
500
|
+
const text = line.text.replace(/^(?:🧠|💬)\s+/u, "").trim();
|
|
501
|
+
return line.kind === "item" && !line.toolName && text && text !== line.label ? renderProgressCardText(compactDetail(text, maxChars)) : "—";
|
|
678
502
|
}
|
|
679
503
|
function lineTaskTitle(line) {
|
|
680
|
-
const label =
|
|
504
|
+
const label = line.label.replace(/\s+/g, " ").trim() || line.toolName || line.kind || "Update";
|
|
681
505
|
const fallback = line.text.replace(/\s+/g, " ").trim();
|
|
682
506
|
if (fallback && fallback !== label) return compactTitle(lineDetailParts(line).length > 0 || line.status ? label : fallback);
|
|
683
507
|
return compactTitle(label);
|
|
@@ -696,102 +520,109 @@ function lineTaskStatus(line) {
|
|
|
696
520
|
const normalized = line.status?.replace(/\s+/g, " ").trim().toLowerCase();
|
|
697
521
|
if (!normalized) return "in_progress";
|
|
698
522
|
if (normalized === "complete" || normalized === "completed" || normalized === "done" || normalized === "ok" || normalized === "success" || normalized === "succeeded" || normalized === "successful" || normalized === "exit 0") return "complete";
|
|
699
|
-
if (normalized === "error" || normalized === "failed" || normalized === "failure" || normalized.startsWith("exit ")) return
|
|
523
|
+
if (normalized === "error" || normalized === "failed" || normalized === "failure" || normalized.startsWith("exit ")) return "error";
|
|
700
524
|
return "in_progress";
|
|
701
525
|
}
|
|
702
|
-
function slugTaskIdPart(value) {
|
|
703
|
-
return value?.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "task";
|
|
704
|
-
}
|
|
705
526
|
function stableTaskIdPart(value, slugValue = value) {
|
|
527
|
+
const slug = slugValue.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
706
528
|
const suffix = createHash("sha256").update(value).digest("hex").slice(0, 8);
|
|
707
|
-
return `${
|
|
708
|
-
}
|
|
709
|
-
function resolveLineTaskIdentity(line) {
|
|
710
|
-
if (line.id?.trim()) return
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
}
|
|
724
|
-
function buildPlanTasks(params) {
|
|
725
|
-
if (params.plan) return params.plan.slice(-50).map((entry, index) => ({
|
|
529
|
+
return `${(slug || "task").slice(0, 48)}_${suffix}`;
|
|
530
|
+
}
|
|
531
|
+
function resolveLineTaskIdentity(line, contentIdOccurrences) {
|
|
532
|
+
if (line.id?.trim()) return stableTaskIdPart(line.id);
|
|
533
|
+
const id = stableTaskIdPart([
|
|
534
|
+
line.kind,
|
|
535
|
+
line.toolName,
|
|
536
|
+
line.label,
|
|
537
|
+
line.text
|
|
538
|
+
].join("\0"), line.toolName ?? line.kind ?? line.label);
|
|
539
|
+
const occurrence = (contentIdOccurrences.get(id) ?? 0) + 1;
|
|
540
|
+
contentIdOccurrences.set(id, occurrence);
|
|
541
|
+
return `${id}_${occurrence}`;
|
|
542
|
+
}
|
|
543
|
+
function buildNativeTasks(params) {
|
|
544
|
+
const tasks = (params.plan ?? []).map((entry, index) => ({
|
|
726
545
|
id: `plan_step_${index + 1}`,
|
|
727
546
|
title: compactTitle(entry.step),
|
|
728
547
|
status: entry.status === "completed" ? "complete" : entry.status
|
|
729
548
|
}));
|
|
730
549
|
const maxLineChars = resolveMaxLineChars(params.maxLineChars, DEFAULT_SLACK_PROGRESS_TASK_DETAIL_MAX_CHARS);
|
|
731
|
-
const lines = params.lines.slice(-50);
|
|
732
|
-
const identities = lines.map(resolveLineTaskIdentity);
|
|
733
550
|
const contentIdOccurrences = /* @__PURE__ */ new Map();
|
|
734
|
-
|
|
735
|
-
const identity = identities[index];
|
|
736
|
-
let id = identity.id;
|
|
737
|
-
if (identity.contentDerived) {
|
|
738
|
-
const occurrence = (contentIdOccurrences.get(id) ?? 0) + 1;
|
|
739
|
-
contentIdOccurrences.set(id, occurrence);
|
|
740
|
-
id = `${id}_${occurrence}`;
|
|
741
|
-
}
|
|
742
|
-
const details = lineTaskDetails(line, maxLineChars);
|
|
743
|
-
const output = lineTaskOutput(line);
|
|
551
|
+
for (const line of params.lines) {
|
|
744
552
|
const task = {
|
|
745
|
-
id,
|
|
553
|
+
id: resolveLineTaskIdentity(line, contentIdOccurrences),
|
|
746
554
|
title: lineTaskTitle(line),
|
|
747
555
|
status: lineTaskStatus(line)
|
|
748
556
|
};
|
|
557
|
+
const details = lineTaskDetails(line, maxLineChars);
|
|
558
|
+
const output = lineTaskOutput(line);
|
|
749
559
|
if (details) task.details = details;
|
|
750
560
|
if (output) task.output = output;
|
|
751
|
-
|
|
561
|
+
tasks.push(task);
|
|
562
|
+
}
|
|
563
|
+
return tasks;
|
|
564
|
+
}
|
|
565
|
+
function buildProgressAttentionTasks(lines, finalStatus) {
|
|
566
|
+
const contentIdOccurrences = /* @__PURE__ */ new Map();
|
|
567
|
+
return lines.flatMap((line) => {
|
|
568
|
+
const approval = line.kind === "approval";
|
|
569
|
+
if (approval ? line.status !== "requested" || finalStatus !== void 0 : lineTaskStatus(line) !== "error") return [];
|
|
570
|
+
const title = approval ? `Approval required: ${line.detail || line.label}` : [...new Set([
|
|
571
|
+
line.label,
|
|
572
|
+
line.detail,
|
|
573
|
+
line.status
|
|
574
|
+
].filter(Boolean))].join(" — ");
|
|
575
|
+
const recovered = !approval && finalStatus === "complete";
|
|
576
|
+
return [{
|
|
577
|
+
id: `${SLACK_ATTENTION_TASK_PREFIX}${resolveLineTaskIdentity(line, contentIdOccurrences)}`,
|
|
578
|
+
title: compactTitle(recovered ? `Recovered: ${title}` : title),
|
|
579
|
+
status: approval ? "pending" : finalStatus ?? "error"
|
|
580
|
+
}];
|
|
752
581
|
});
|
|
753
582
|
}
|
|
754
|
-
function
|
|
755
|
-
|
|
583
|
+
function formatDiffStat(diffStat) {
|
|
584
|
+
if (!diffStat || diffStat.files === 0 && diffStat.added === 0 && diffStat.removed === 0) return;
|
|
585
|
+
return [
|
|
586
|
+
`📝 ${diffStat.files} files`,
|
|
587
|
+
...diffStat.added > 0 ? [`+${diffStat.added}`] : [],
|
|
588
|
+
...diffStat.removed > 0 ? [`−${diffStat.removed}`] : []
|
|
589
|
+
].join(" ");
|
|
590
|
+
}
|
|
591
|
+
function formatTaskDiffOutput(diffStat) {
|
|
592
|
+
return diffStat && (diffStat.added > 0 || diffStat.removed > 0) ? `+${diffStat.added} −${diffStat.removed}` : void 0;
|
|
756
593
|
}
|
|
757
594
|
function buildSlackProgressStreamChunks(params) {
|
|
758
|
-
const
|
|
759
|
-
|
|
595
|
+
const approvals = params.lines.filter((line) => line.kind === "approval");
|
|
596
|
+
const tasks = buildNativeTasks({
|
|
597
|
+
lines: params.summaryRow ? [] : params.lines.filter((line) => line.kind !== "approval"),
|
|
760
598
|
plan: params.plan,
|
|
761
599
|
maxLineChars: params.maxLineChars
|
|
762
600
|
});
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
...formatTaskDiffOutput(params.diffStat) ? { output: formatTaskDiffOutput(params.diffStat) } : {},
|
|
779
|
-
...params.sessionUrl ? { sources: buildSessionSources(params.sessionUrl) } : {}
|
|
780
|
-
}];
|
|
781
|
-
}
|
|
782
|
-
const title = resolvePlanTitle({
|
|
783
|
-
label: params.label,
|
|
784
|
-
title: params.title,
|
|
785
|
-
tasks
|
|
601
|
+
const attention = buildProgressAttentionTasks(params.summaryRow ? params.lines : approvals, params.finalInProgressStatus);
|
|
602
|
+
const headline = params.title?.trim() || params.label?.trim();
|
|
603
|
+
const newest = tasks.at(-1);
|
|
604
|
+
const title = compactChunkText(headline || (newest?.details ? `${newest.title} — ${newest.details}` : newest?.title) || (params.summaryRow ? "Working" : attention.at(-1)?.title) || SLACK_PROGRESS_PLAN_FALLBACK_TITLE);
|
|
605
|
+
const diffOutput = formatTaskDiffOutput(params.diffStat);
|
|
606
|
+
if (tasks.length === 0 && (params.summaryRow || params.sessionUrl || diffOutput)) tasks.push({
|
|
607
|
+
id: "openclaw_summary",
|
|
608
|
+
title: params.summaryRow ? compactTitle(title) : "Completed",
|
|
609
|
+
status: params.finalInProgressStatus ?? (params.summaryRow ? "in_progress" : "complete")
|
|
610
|
+
});
|
|
611
|
+
tasks.push(...attention);
|
|
612
|
+
if (params.finalInProgressStatus === "error" && !tasks.some((task) => task.status === "in_progress" || task.status === "error")) tasks.push({
|
|
613
|
+
id: "openclaw_attention",
|
|
614
|
+
title: "Failed",
|
|
615
|
+
status: "error"
|
|
786
616
|
});
|
|
617
|
+
if (!headline && tasks.length === 0) return;
|
|
787
618
|
const finalTaskIndex = tasks.length - 1;
|
|
788
|
-
const diffOutput = formatTaskDiffOutput(params.diffStat);
|
|
789
619
|
const taskChunks = tasks.map((task, index) => {
|
|
620
|
+
const recovered = params.finalInProgressStatus === "complete" && task.status === "error";
|
|
790
621
|
const chunk = {
|
|
791
622
|
type: "task_update",
|
|
792
623
|
id: task.id,
|
|
793
|
-
title: task.title,
|
|
794
|
-
status: task.status === "in_progress" ? params.finalInProgressStatus ??
|
|
624
|
+
title: recovered ? compactTitle(`Recovered: ${task.title}`) : task.title,
|
|
625
|
+
status: recovered ? "complete" : task.status === "in_progress" ? params.finalInProgressStatus ?? task.status : task.status
|
|
795
626
|
};
|
|
796
627
|
if (task.details) chunk.details = task.details;
|
|
797
628
|
if (task.output) chunk.output = task.output;
|
|
@@ -804,22 +635,10 @@ function buildSlackProgressStreamChunks(params) {
|
|
|
804
635
|
title
|
|
805
636
|
}, ...taskChunks];
|
|
806
637
|
}
|
|
807
|
-
function
|
|
808
|
-
if (!diffStat || diffStat.files === 0 && diffStat.added === 0 && diffStat.removed === 0) return;
|
|
809
|
-
return [
|
|
810
|
-
`📝 ${diffStat.files} files`,
|
|
811
|
-
...diffStat.added > 0 ? [`+${diffStat.added}`] : [],
|
|
812
|
-
...diffStat.removed > 0 ? [`−${diffStat.removed}`] : []
|
|
813
|
-
].join(" ");
|
|
814
|
-
}
|
|
815
|
-
function formatTaskDiffOutput(diffStat) {
|
|
816
|
-
return diffStat && (diffStat.added > 0 || diffStat.removed > 0) ? `+${diffStat.added} −${diffStat.removed}` : void 0;
|
|
817
|
-
}
|
|
818
|
-
function buildActivityText(lines, maxLineChars) {
|
|
638
|
+
function joinRecentProgressRows(rows) {
|
|
819
639
|
const rendered = [];
|
|
820
640
|
let length = 0;
|
|
821
|
-
for (const
|
|
822
|
-
const row = `${legacyLineTitle(line)} — ${legacyLineDetail(line, maxLineChars)}`;
|
|
641
|
+
for (const row of rows.toReversed()) {
|
|
823
642
|
const nextLength = length + row.length + (rendered.length > 0 ? 1 : 0);
|
|
824
643
|
if (nextLength > SLACK_PROGRESS_FIELD_MAX) break;
|
|
825
644
|
rendered.push(row);
|
|
@@ -827,6 +646,11 @@ function buildActivityText(lines, maxLineChars) {
|
|
|
827
646
|
}
|
|
828
647
|
return rendered.toReversed().join("\n");
|
|
829
648
|
}
|
|
649
|
+
function buildActivityText(lines, maxLineChars) {
|
|
650
|
+
return joinRecentProgressRows(lines.slice(-50).map((line) => {
|
|
651
|
+
return `${`${line.icon ?? "•"} *${renderProgressCardText(line.label, "bold")}*`} — ${activityLineDetail(line, maxLineChars)}`;
|
|
652
|
+
}));
|
|
653
|
+
}
|
|
830
654
|
function buildSlackProgressCardBlocks(params) {
|
|
831
655
|
const maxLineChars = resolveMaxLineChars(params.maxLineChars, DEFAULT_SLACK_PROGRESS_DETAIL_MAX_CHARS);
|
|
832
656
|
const planLines = formatPlanChecklistLines(params.plan ?? [], {
|
|
@@ -834,7 +658,6 @@ function buildSlackProgressCardBlocks(params) {
|
|
|
834
658
|
maxLineChars
|
|
835
659
|
});
|
|
836
660
|
const narration = params.narration?.replace(/\s+/g, " ").trim();
|
|
837
|
-
const activityText = buildActivityText(params.lines, maxLineChars);
|
|
838
661
|
const diffStat = formatDiffStat(params.diffStat);
|
|
839
662
|
const workingFooter = [
|
|
840
663
|
...params.toolCalls && params.toolCalls > 0 ? [`🛠️ ${params.toolCalls} tools`] : [],
|
|
@@ -842,40 +665,36 @@ function buildSlackProgressCardBlocks(params) {
|
|
|
842
665
|
...params.elapsedSeconds && params.elapsedSeconds > 0 ? [`⏱ ${params.elapsedSeconds}s`] : []
|
|
843
666
|
].join(" · ");
|
|
844
667
|
const footer = params.state === "working" ? workingFooter : diffStat;
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
},
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
url: params.sessionUrl
|
|
876
|
-
}]
|
|
877
|
-
}] : []
|
|
878
|
-
].slice(0, 50);
|
|
668
|
+
const icon = params.state === "working" ? "🔄" : params.state === "success" ? "✅" : "❌";
|
|
669
|
+
const finalStatus = params.state === "working" ? void 0 : params.state === "success" ? "complete" : "error";
|
|
670
|
+
const attention = buildProgressAttentionTasks(params.lines, finalStatus).map((task) => escapeSlackMrkdwn(task.title));
|
|
671
|
+
const blocks = [
|
|
672
|
+
`${icon} *${renderProgressCardText(params.title.trim() || "Working", "bold")}*`,
|
|
673
|
+
narration ? `_${renderProgressCardText(narration, "italic")}_` : "",
|
|
674
|
+
planLines.map((line) => renderProgressCardText(line)).join("\n"),
|
|
675
|
+
buildActivityText(params.lines.filter((line) => line.kind !== "approval" && lineTaskStatus(line) !== "error"), maxLineChars),
|
|
676
|
+
joinRecentProgressRows(attention)
|
|
677
|
+
].filter(Boolean).map((text) => ({
|
|
678
|
+
type: "section",
|
|
679
|
+
text: field(text)
|
|
680
|
+
}));
|
|
681
|
+
if (footer) blocks.push({
|
|
682
|
+
type: "context",
|
|
683
|
+
elements: [field(footer)]
|
|
684
|
+
});
|
|
685
|
+
if (params.state !== "working" && params.sessionUrl) blocks.push({
|
|
686
|
+
type: "actions",
|
|
687
|
+
elements: [{
|
|
688
|
+
type: "button",
|
|
689
|
+
action_id: SLACK_SESSION_LINK_ACTION_ID,
|
|
690
|
+
text: {
|
|
691
|
+
type: "plain_text",
|
|
692
|
+
text: "Open in OpenClaw"
|
|
693
|
+
},
|
|
694
|
+
url: params.sessionUrl
|
|
695
|
+
}]
|
|
696
|
+
});
|
|
697
|
+
return blocks.slice(0, 50);
|
|
879
698
|
}
|
|
880
699
|
const EMPTY_SLACK_NATIVE_STREAM_SNAPSHOT = { tasks: /* @__PURE__ */ new Map() };
|
|
881
700
|
const SLACK_TASK_FIELD_SEPARATOR = " · ";
|
|
@@ -930,24 +749,23 @@ function reconcileSlackNativeTaskChunks(params) {
|
|
|
930
749
|
continue;
|
|
931
750
|
}
|
|
932
751
|
const previousRow = params.previous.tasks.get(chunk.id);
|
|
933
|
-
const status = chunk.status;
|
|
934
752
|
const details = resolveTaskFieldDelta(previousRow?.details, chunk.details);
|
|
935
753
|
const output = resolveTaskFieldDelta(previousRow?.output, chunk.output);
|
|
936
754
|
const sourcesChanged = Boolean(chunk.sources) && !previousRow?.sourcesSent;
|
|
937
755
|
const row = {
|
|
938
756
|
title: chunk.title,
|
|
939
|
-
status
|
|
757
|
+
status: chunk.status
|
|
940
758
|
};
|
|
941
759
|
if (details.field) row.details = details.field;
|
|
942
760
|
if (output.field) row.output = output.field;
|
|
943
761
|
if (sourcesChanged || previousRow?.sourcesSent) row.sourcesSent = true;
|
|
944
762
|
nextTasks.set(chunk.id, row);
|
|
945
|
-
if (!(!previousRow || previousRow.title !== chunk.title || previousRow.status !== status || Boolean(details.delta) || Boolean(output.delta) || sourcesChanged)) continue;
|
|
763
|
+
if (!(!previousRow || previousRow.title !== chunk.title || previousRow.status !== chunk.status || Boolean(details.delta) || Boolean(output.delta) || sourcesChanged)) continue;
|
|
946
764
|
const update = {
|
|
947
765
|
type: "task_update",
|
|
948
766
|
id: chunk.id,
|
|
949
767
|
title: chunk.title,
|
|
950
|
-
status
|
|
768
|
+
status: chunk.status
|
|
951
769
|
};
|
|
952
770
|
if (details.delta) update.details = details.delta;
|
|
953
771
|
if (output.delta) update.output = output.delta;
|
|
@@ -956,18 +774,21 @@ function reconcileSlackNativeTaskChunks(params) {
|
|
|
956
774
|
}
|
|
957
775
|
for (const [id, row] of params.previous.tasks) {
|
|
958
776
|
if (nextTasks.has(id)) continue;
|
|
959
|
-
|
|
777
|
+
const recovered = row.status === "error" && (id.startsWith(SLACK_ATTENTION_TASK_PREFIX) || params.finalStatus === "complete");
|
|
778
|
+
if (row.status === "complete" || row.status === "error" && !recovered) {
|
|
960
779
|
nextTasks.set(id, row);
|
|
961
780
|
continue;
|
|
962
781
|
}
|
|
782
|
+
const title = recovered ? compactTitle(`Recovered: ${row.title}`) : row.title;
|
|
963
783
|
nextTasks.set(id, {
|
|
964
784
|
...row,
|
|
785
|
+
title,
|
|
965
786
|
status: "complete"
|
|
966
787
|
});
|
|
967
788
|
emitted.push({
|
|
968
789
|
type: "task_update",
|
|
969
790
|
id,
|
|
970
|
-
title
|
|
791
|
+
title,
|
|
971
792
|
status: "complete"
|
|
972
793
|
});
|
|
973
794
|
}
|
|
@@ -979,12 +800,6 @@ function reconcileSlackNativeTaskChunks(params) {
|
|
|
979
800
|
}
|
|
980
801
|
};
|
|
981
802
|
}
|
|
982
|
-
function buildSlackProgressStreamCompletionChunks(params) {
|
|
983
|
-
return buildSlackProgressStreamChunks({
|
|
984
|
-
...params,
|
|
985
|
-
completeInProgress: true
|
|
986
|
-
});
|
|
987
|
-
}
|
|
988
803
|
//#endregion
|
|
989
804
|
//#region extensions/slack/src/monitor/message-handler/dispatch-progress-render.ts
|
|
990
805
|
function resolveStructuredProgressLines(lines) {
|
|
@@ -1001,9 +816,6 @@ function resolveStructuredProgressLines(lines) {
|
|
|
1001
816
|
};
|
|
1002
817
|
});
|
|
1003
818
|
}
|
|
1004
|
-
function resolveNativeProgressPlan(snapshot) {
|
|
1005
|
-
return snapshot.plan?.length ? snapshot.plan : void 0;
|
|
1006
|
-
}
|
|
1007
819
|
function resolveNativeProgressLines(snapshot) {
|
|
1008
820
|
const lines = resolveStructuredProgressLines(snapshot.lines).filter((line) => line.id !== "reasoning" && line.id?.startsWith("commentary:") !== true);
|
|
1009
821
|
if (snapshot.plan?.length || !snapshot.planExplanation) return lines;
|
|
@@ -1021,14 +833,6 @@ function resolveNativeProgressNarration(snapshot) {
|
|
|
1021
833
|
function combineProgressHeadlineAndExplanation(headline, explanation) {
|
|
1022
834
|
return headline && explanation && headline !== explanation ? `${headline} — ${explanation}` : headline ?? explanation;
|
|
1023
835
|
}
|
|
1024
|
-
function buildNativeProgressChunks(params) {
|
|
1025
|
-
return buildSlackProgressStreamChunks({
|
|
1026
|
-
title: params.title,
|
|
1027
|
-
lines: resolveNativeProgressLines(params.snapshot),
|
|
1028
|
-
plan: resolveNativeProgressPlan(params.snapshot),
|
|
1029
|
-
maxLineChars: params.maxLineChars
|
|
1030
|
-
});
|
|
1031
|
-
}
|
|
1032
836
|
//#endregion
|
|
1033
837
|
//#region extensions/slack/src/monitor/message-handler/preview-finalize.ts
|
|
1034
838
|
function buildExpectedSlackEditText(params) {
|
|
@@ -1066,7 +870,8 @@ function blocksMatch(expected, actual) {
|
|
|
1066
870
|
const fallbackBlocks = fallbackPlan.fallbackMessages.flatMap((message) => message.blocks ?? []);
|
|
1067
871
|
if (JSON.stringify(fallbackBlocks) === JSON.stringify(actual)) return true;
|
|
1068
872
|
const fallbackText = fallbackPlan.fallbackMessages.map((message) => message.text).filter(Boolean).join("\n\n");
|
|
1069
|
-
|
|
873
|
+
const actualText = buildSlackBlocksFallbackText(actual);
|
|
874
|
+
return normalizeSlackOutboundText(actualText) === normalizeSlackOutboundText(fallbackText);
|
|
1070
875
|
} catch {
|
|
1071
876
|
return false;
|
|
1072
877
|
}
|
|
@@ -1166,18 +971,20 @@ function createSlackDraftProgressCardRuntime(params) {
|
|
|
1166
971
|
});
|
|
1167
972
|
const resolvePresentation = (snapshot, state) => {
|
|
1168
973
|
const title = params.explicitTitle ?? snapshot.statusHeadline ?? "Working";
|
|
974
|
+
const narration = params.explicitTitle ? combineProgressHeadlineAndExplanation(snapshot.statusHeadline, snapshot.planExplanation) : snapshot.planExplanation && snapshot.planExplanation !== title ? snapshot.planExplanation : void 0;
|
|
975
|
+
const workCounter = state === "working" ? params.progressWorkCounter : void 0;
|
|
976
|
+
const sessionUrl = state === "working" ? void 0 : resolveSessionUrl();
|
|
1169
977
|
return buildSlackProgressCardBlocks({
|
|
1170
978
|
state,
|
|
1171
979
|
title,
|
|
1172
|
-
narration
|
|
980
|
+
narration,
|
|
1173
981
|
plan: snapshot.plan,
|
|
1174
982
|
lines: resolveStructuredProgressLines(snapshot.lines),
|
|
1175
983
|
maxLineChars: params.maxLineChars,
|
|
1176
984
|
diffStat: snapshot.diffStat,
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
} : { sessionUrl: resolveSessionUrl() }
|
|
985
|
+
toolCalls: workCounter?.toolCalls,
|
|
986
|
+
elapsedSeconds: workCounter?.elapsedSeconds,
|
|
987
|
+
sessionUrl
|
|
1181
988
|
});
|
|
1182
989
|
};
|
|
1183
990
|
const finalize = async (status, snapshot = params.getSnapshot(), fallbackText = resolveText(snapshot)) => {
|
|
@@ -1231,10 +1038,10 @@ function formatSlackProgressDraftLine(line) {
|
|
|
1231
1038
|
if (/^(?:🧠|💬)\s/u.test(line)) return line;
|
|
1232
1039
|
const italicCommentary = /^_(.*)_$/su.exec(line);
|
|
1233
1040
|
if (!italicCommentary) return escapeSlackMrkdwn(line);
|
|
1234
|
-
return `_${italicCommentary[1]
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
})
|
|
1041
|
+
return `_${normalizeSlackOutboundText(italicCommentary[1], {
|
|
1042
|
+
mentions: "escape",
|
|
1043
|
+
enclosingStyle: "italic"
|
|
1044
|
+
})}_`;
|
|
1238
1045
|
}
|
|
1239
1046
|
//#endregion
|
|
1240
1047
|
//#region extensions/slack/src/monitor/message-handler/dispatch-progress-native.ts
|
|
@@ -1271,6 +1078,7 @@ function createSlackNativeProgressTransport(params) {
|
|
|
1271
1078
|
const startPromise = (async () => {
|
|
1272
1079
|
const session = await startSlackStream({
|
|
1273
1080
|
client: slackClient,
|
|
1081
|
+
clientOptions: params.setup.slackClientOptions,
|
|
1274
1082
|
channel: message.channel,
|
|
1275
1083
|
threadTs: streamThreadTs,
|
|
1276
1084
|
...update.text ? { text: update.text } : {},
|
|
@@ -1341,7 +1149,7 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1341
1149
|
const isProgressMode = slackStreaming.mode === "progress";
|
|
1342
1150
|
const useNativeProgressStreaming = useStreaming && slackStreaming.mode === "progress";
|
|
1343
1151
|
const progressDraftActive = Boolean(draftStream) || useNativeProgressStreaming;
|
|
1344
|
-
const previewToolProgressEnabled = progressDraftActive && resolveChannelStreamingPreviewToolProgress(account.config,
|
|
1152
|
+
const previewToolProgressEnabled = progressDraftActive && resolveChannelStreamingPreviewToolProgress(account.config, slackStreaming.mode !== "progress", slackStreaming.mode);
|
|
1345
1153
|
let shouldYieldDraftProgress = () => false;
|
|
1346
1154
|
const suppressDefaultToolProgressMessages = resolveChannelStreamingSuppressDefaultToolProgressMessages(account.config, {
|
|
1347
1155
|
draftStreamActive: Boolean(draftStream) || useNativeProgressStreaming,
|
|
@@ -1379,7 +1187,7 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1379
1187
|
},
|
|
1380
1188
|
draftStream,
|
|
1381
1189
|
enabled: useDraftProgressCard,
|
|
1382
|
-
progressWorkCounter,
|
|
1190
|
+
progressWorkCounter: previewToolProgressEnabled ? progressWorkCounter : void 0,
|
|
1383
1191
|
progressSeed,
|
|
1384
1192
|
explicitTitle: explicitProgressTitle,
|
|
1385
1193
|
maxLineChars: progressDraftMaxLineChars,
|
|
@@ -1399,12 +1207,17 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1399
1207
|
if (isError) nativeProgressTerminalStatus = "error";
|
|
1400
1208
|
if (!session || nativeProgressCompletionSent) return;
|
|
1401
1209
|
const chunks = buildNativeProgressCompletionChunks(isError ? "error" : "complete");
|
|
1402
|
-
|
|
1210
|
+
const narrationUpdate = resolveNarrationUpdate(resolveNativeProgressNarration(progressDraft.getSnapshot()));
|
|
1211
|
+
if (!chunks?.length && !narrationUpdate.delta) return;
|
|
1403
1212
|
try {
|
|
1404
1213
|
await appendSlackStream({
|
|
1405
1214
|
session,
|
|
1406
1215
|
chunks
|
|
1407
1216
|
});
|
|
1217
|
+
if (narrationUpdate.next.changed) {
|
|
1218
|
+
nativeNarrationRenderedText = narrationUpdate.next.rendered;
|
|
1219
|
+
nativeNarrationSourceText = narrationUpdate.next.source;
|
|
1220
|
+
}
|
|
1408
1221
|
nativeProgressCompletionSent = true;
|
|
1409
1222
|
delivery.observedReplyDelivery ||= session.delivered;
|
|
1410
1223
|
} catch (err) {
|
|
@@ -1413,11 +1226,6 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1413
1226
|
}
|
|
1414
1227
|
};
|
|
1415
1228
|
const resolveNativeProgressTitle = (snapshot) => combineProgressHeadlineAndExplanation(explicitProgressTitle ?? snapshot.statusHeadline, snapshot.planExplanation);
|
|
1416
|
-
const buildNativeProgressChunks$1 = (snapshot) => buildNativeProgressChunks({
|
|
1417
|
-
snapshot,
|
|
1418
|
-
title: resolveNativeProgressTitle(snapshot),
|
|
1419
|
-
maxLineChars: progressDraftMaxLineChars
|
|
1420
|
-
});
|
|
1421
1229
|
const normalizeProgressText = (text) => text?.replace(/\s+/gu, " ").trim() ?? "";
|
|
1422
1230
|
const isRenderedAsProgressTitle = (text) => {
|
|
1423
1231
|
const candidate = normalizeProgressText(text);
|
|
@@ -1436,17 +1244,20 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1436
1244
|
delta: next.changed ? next.rendered.slice(nativeNarrationRenderedText.length) : ""
|
|
1437
1245
|
};
|
|
1438
1246
|
};
|
|
1439
|
-
const updateNativeProgressStream = () => withNativeStreamOrder(updateNativeProgressStreamNow);
|
|
1440
1247
|
const updateNativeProgressStreamNow = async () => {
|
|
1441
1248
|
const snapshot = progressDraft.getSnapshot();
|
|
1442
|
-
const progressLines = resolveNativeProgressLines(snapshot);
|
|
1443
1249
|
const narrationUpdate = resolveNarrationUpdate(resolveNativeProgressNarration(snapshot));
|
|
1444
|
-
|
|
1445
|
-
if (!useNativeProgressStreaming || delivery.streamFailed || progressLines.length === 0 && !snapshot.plan?.length && !snapshot.statusHeadline && !explicitProgressTitle && !hasRetirableNativeTasks && !narrationUpdate.delta) return false;
|
|
1250
|
+
if (!useNativeProgressStreaming || delivery.streamFailed || nativeUpdatesStopped) return false;
|
|
1446
1251
|
if (!await nativeTransport.waitForStart()) return false;
|
|
1447
1252
|
const reconciled = reconcileSlackNativeTaskChunks({
|
|
1448
1253
|
previous: nativeStreamSnapshot,
|
|
1449
|
-
chunks:
|
|
1254
|
+
chunks: buildSlackProgressStreamChunks({
|
|
1255
|
+
title: resolveNativeProgressTitle(snapshot),
|
|
1256
|
+
lines: resolveNativeProgressLines(snapshot),
|
|
1257
|
+
plan: snapshot.plan,
|
|
1258
|
+
maxLineChars: progressDraftMaxLineChars,
|
|
1259
|
+
summaryRow: !previewToolProgressEnabled
|
|
1260
|
+
})
|
|
1450
1261
|
});
|
|
1451
1262
|
const chunks = reconciled.chunks;
|
|
1452
1263
|
if (!chunks?.length && !narrationUpdate.delta) return false;
|
|
@@ -1470,6 +1281,21 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1470
1281
|
return false;
|
|
1471
1282
|
}
|
|
1472
1283
|
};
|
|
1284
|
+
let nativeUpdatesStopped = false;
|
|
1285
|
+
const nativeUpdates = createDraftStreamLoop({
|
|
1286
|
+
throttleMs: 1e3,
|
|
1287
|
+
coalesceInFlight: true,
|
|
1288
|
+
emptyValue: false,
|
|
1289
|
+
isEmpty: (pending) => !pending,
|
|
1290
|
+
isStopped: () => nativeUpdatesStopped,
|
|
1291
|
+
sendOrEditStreamMessage: () => withNativeStreamOrder(updateNativeProgressStreamNow),
|
|
1292
|
+
onBackgroundFlushError: (err) => runtime.error?.(danger(`slack-stream: progress update failed: ${formatSlackError(err)}`))
|
|
1293
|
+
});
|
|
1294
|
+
const cancelNativeUpdates = async () => {
|
|
1295
|
+
nativeUpdatesStopped = true;
|
|
1296
|
+
nativeUpdates.stop();
|
|
1297
|
+
await nativeUpdates.waitForInFlight();
|
|
1298
|
+
};
|
|
1473
1299
|
const appendNativeNarration = (payload, kind) => withNativeStreamOrder(() => appendNativeNarrationNow(payload, kind));
|
|
1474
1300
|
const appendNativeNarrationNow = async (payload, kind) => {
|
|
1475
1301
|
if (isRenderedAsProgressTitle(payload.text)) return false;
|
|
@@ -1500,13 +1326,16 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1500
1326
|
seed: progressSeed,
|
|
1501
1327
|
formatLine: formatSlackProgressDraftLine,
|
|
1502
1328
|
reasoningLinePrefix: "🧠 ",
|
|
1503
|
-
commentaryLinePrefix: "",
|
|
1504
|
-
reasoningGate: previewToolProgressEnabled,
|
|
1505
|
-
commentaryItalics: true,
|
|
1506
|
-
buildProgressEventLine: (input, options) => input.event === "tool" || input.event === "item" || input.event === "command-output" ? buildChannelProgressDraftLineForEntry(account.config, input, options) : buildChannelProgressDraftLine(input, options),
|
|
1507
1329
|
updateOnLineChange: useNativeProgressStreaming || useDraftProgressCard,
|
|
1508
1330
|
update: async (previewText, options) => {
|
|
1509
|
-
if (useNativeProgressStreaming)
|
|
1331
|
+
if (useNativeProgressStreaming) {
|
|
1332
|
+
const priorSnapshot = nativeStreamSnapshot;
|
|
1333
|
+
const priorNarration = nativeNarrationRenderedText;
|
|
1334
|
+
nativeUpdates.update(true);
|
|
1335
|
+
if (options?.flush) await nativeUpdates.flush();
|
|
1336
|
+
else await nativeUpdates.waitForInFlight();
|
|
1337
|
+
return priorSnapshot !== nativeStreamSnapshot || priorNarration !== nativeNarrationRenderedText;
|
|
1338
|
+
}
|
|
1510
1339
|
if (!draftStream) return false;
|
|
1511
1340
|
const snapshot = progressDraft.getSnapshot();
|
|
1512
1341
|
progressCard.setFallbackText(previewText);
|
|
@@ -1519,7 +1348,11 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1519
1348
|
}
|
|
1520
1349
|
});
|
|
1521
1350
|
const commentaryProgressEnabled = progressDraft.commentaryProgressEnabled;
|
|
1522
|
-
const deliverNativeFinal = (payload, kind) =>
|
|
1351
|
+
const deliverNativeFinal = async (payload, kind) => {
|
|
1352
|
+
progressDraft.markFinalReplyStarted();
|
|
1353
|
+
await cancelNativeUpdates();
|
|
1354
|
+
await withNativeStreamOrder(() => deliverNativeFinalNow(payload, kind));
|
|
1355
|
+
};
|
|
1523
1356
|
const deliverNativeFinalNow = async (payload, kind) => {
|
|
1524
1357
|
progressDraft.markFinalReplyStarted();
|
|
1525
1358
|
const streamReady = await nativeTransport.waitForStart();
|
|
@@ -1544,38 +1377,32 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1544
1377
|
const snapshot = progressDraft.getSnapshot();
|
|
1545
1378
|
const lines = resolveNativeProgressLines(snapshot);
|
|
1546
1379
|
const sessionUrl = progressCard.resolveSessionUrl();
|
|
1380
|
+
const narrationUpdate = resolveNarrationUpdate(resolveNativeProgressNarration(snapshot));
|
|
1547
1381
|
const hasRetirableNativeTasks = [...nativeStreamSnapshot.tasks.values()].some((task) => task.status !== "complete" && task.status !== "error");
|
|
1548
|
-
if (lines.length === 0 && !snapshot.plan?.length && !hasRetirableNativeTasks && !snapshot.diffStat && !sessionUrl) return;
|
|
1549
|
-
|
|
1382
|
+
if (lines.length === 0 && !snapshot.plan?.length && !hasRetirableNativeTasks && !snapshot.diffStat && !narrationUpdate.delta && !sessionUrl) return;
|
|
1383
|
+
const completion = reconcileSlackNativeTaskChunks({
|
|
1550
1384
|
previous: nativeStreamSnapshot,
|
|
1551
|
-
|
|
1385
|
+
finalStatus: finalInProgressStatus,
|
|
1386
|
+
chunks: buildSlackProgressStreamChunks({
|
|
1552
1387
|
title: resolveNativeProgressTitle(snapshot) ?? (lines.length === 0 && !snapshot.plan?.length ? "Working" : void 0),
|
|
1553
1388
|
lines,
|
|
1554
|
-
plan:
|
|
1389
|
+
plan: snapshot.plan,
|
|
1555
1390
|
maxLineChars: progressDraftMaxLineChars,
|
|
1391
|
+
summaryRow: !previewToolProgressEnabled,
|
|
1556
1392
|
finalInProgressStatus,
|
|
1557
1393
|
diffStat: snapshot.diffStat,
|
|
1558
1394
|
sessionUrl
|
|
1559
1395
|
})
|
|
1560
1396
|
}).chunks;
|
|
1397
|
+
return narrationUpdate.delta ? [{
|
|
1398
|
+
type: "markdown_text",
|
|
1399
|
+
text: narrationUpdate.delta
|
|
1400
|
+
}, ...completion ?? []] : completion;
|
|
1561
1401
|
};
|
|
1562
1402
|
const finishNativeProgressTurn = async (completionChunks) => {
|
|
1563
1403
|
if (delivery.nativeProgressStreamStartPromise) await delivery.nativeProgressStreamStartPromise.catch(() => null);
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
if (completionChunks?.length) nativeProgressCompletionSent = true;
|
|
1567
|
-
const stopResult = await stopSlackStream({
|
|
1568
|
-
session,
|
|
1569
|
-
...completionChunks?.length ? { chunks: completionChunks } : {},
|
|
1570
|
-
...slackMessageMetadata ? { metadata: slackMessageMetadata } : {}
|
|
1571
|
-
});
|
|
1572
|
-
delivery.acknowledgeStoppedStreamedDeliveries(session, stopResult?.messageId);
|
|
1573
|
-
} catch (err) {
|
|
1574
|
-
const error = formatSlackError(err);
|
|
1575
|
-
delivery.emitAcknowledgedStreamedDeliveries();
|
|
1576
|
-
delivery.emitFailedPendingStreamedDeliveries(error);
|
|
1577
|
-
logVerbose(`slack-stream: failed to rotate native progress stream (${error})`);
|
|
1578
|
-
}
|
|
1404
|
+
if (completionChunks?.length) nativeProgressCompletionSent = true;
|
|
1405
|
+
await delivery.finishStream(completionChunks);
|
|
1579
1406
|
delivery.streamSession = null;
|
|
1580
1407
|
delivery.nativeProgressStreamStartPromise = null;
|
|
1581
1408
|
delivery.nativeProgressStreamThreadTs = void 0;
|
|
@@ -1660,6 +1487,10 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1660
1487
|
progressDraft.reset();
|
|
1661
1488
|
};
|
|
1662
1489
|
const beginNewProgressTurn = async (options) => {
|
|
1490
|
+
if (useNativeProgressStreaming) {
|
|
1491
|
+
if (!nativeUpdatesStopped && options?.force !== true) return false;
|
|
1492
|
+
await cancelNativeUpdates();
|
|
1493
|
+
}
|
|
1663
1494
|
const priorSnapshot = progressDraft.getSnapshot();
|
|
1664
1495
|
const priorFallbackText = progressCard.resolveText(priorSnapshot);
|
|
1665
1496
|
const completionChunks = useNativeProgressStreaming && !nativeProgressCompletionSent ? buildNativeProgressCompletionChunks(nativeProgressTerminalStatus) : void 0;
|
|
@@ -1674,6 +1505,8 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1674
1505
|
nativeStreamSnapshot = EMPTY_SLACK_NATIVE_STREAM_SNAPSHOT;
|
|
1675
1506
|
nativeProgressCompletionSent = false;
|
|
1676
1507
|
nativeProgressTerminalStatus = "complete";
|
|
1508
|
+
nativeUpdatesStopped = false;
|
|
1509
|
+
nativeUpdates.resetThrottleWindow();
|
|
1677
1510
|
progressCard.reset();
|
|
1678
1511
|
resetPreviewDeliveryState();
|
|
1679
1512
|
delivery.resetDeliveryTracker();
|
|
@@ -1697,7 +1530,14 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1697
1530
|
resetDraftDeliveryState();
|
|
1698
1531
|
resetDraftProgressState();
|
|
1699
1532
|
};
|
|
1700
|
-
const onQueuedFollowupSettled = !useDraftProgressCard ? void 0 : async () => {
|
|
1533
|
+
const onQueuedFollowupSettled = !useDraftProgressCard && !useNativeProgressStreaming ? void 0 : async () => {
|
|
1534
|
+
if (useNativeProgressStreaming) {
|
|
1535
|
+
progressDraft.markFinalReplyStarted();
|
|
1536
|
+
await cancelNativeUpdates();
|
|
1537
|
+
await finishNativeProgressTurn(nativeProgressCompletionSent ? void 0 : buildNativeProgressCompletionChunks(nativeProgressTerminalStatus));
|
|
1538
|
+
progressDraft.markFinalReplyDelivered();
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1701
1541
|
if (!progressCard.hasTerminalized) await draftStream?.clear();
|
|
1702
1542
|
await dropDetachedProgressCards();
|
|
1703
1543
|
};
|
|
@@ -1710,8 +1550,12 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1710
1550
|
previewToolProgressEnabled,
|
|
1711
1551
|
suppressDefaultToolProgressMessages,
|
|
1712
1552
|
progressDraft,
|
|
1713
|
-
commentaryProgressEnabled,
|
|
1714
1553
|
progressWorkCounter,
|
|
1554
|
+
commentaryProgressEnabled,
|
|
1555
|
+
async cancel() {
|
|
1556
|
+
progressDraft.cancel();
|
|
1557
|
+
await cancelNativeUpdates();
|
|
1558
|
+
},
|
|
1715
1559
|
get nativeProgressCompletionSent() {
|
|
1716
1560
|
return nativeProgressCompletionSent;
|
|
1717
1561
|
},
|
|
@@ -1743,6 +1587,10 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1743
1587
|
async function createSlackDispatchSetup(prepared) {
|
|
1744
1588
|
const { ctx, account, message, route } = prepared;
|
|
1745
1589
|
const slackClient = prepared.eventScope?.client ?? ctx.app.client;
|
|
1590
|
+
const slackClientOptions = {
|
|
1591
|
+
...ctx.app.webClientOptions,
|
|
1592
|
+
...prepared.eventScope ? { teamId: prepared.eventScope.teamId } : {}
|
|
1593
|
+
};
|
|
1746
1594
|
const slackStreamFallbackTeamId = prepared.eventScope?.teamId ?? ctx.teamId;
|
|
1747
1595
|
const cfg = ctx.cfg;
|
|
1748
1596
|
const runtime = ctx.runtime;
|
|
@@ -1816,24 +1664,17 @@ async function createSlackDispatchSetup(prepared) {
|
|
|
1816
1664
|
await reactSlackMessage(message.channel, reactionMessageTs ?? "", emoji, {
|
|
1817
1665
|
token: ctx.botToken,
|
|
1818
1666
|
client: slackClient
|
|
1819
|
-
}).catch((err) => {
|
|
1820
|
-
if (formatErrorMessage(err).includes("already_reacted")) return;
|
|
1821
|
-
throw err;
|
|
1822
1667
|
});
|
|
1823
1668
|
},
|
|
1824
1669
|
removeReaction: async (emoji) => {
|
|
1825
1670
|
await removeSlackReaction(message.channel, reactionMessageTs ?? "", emoji, {
|
|
1826
1671
|
token: ctx.botToken,
|
|
1827
1672
|
client: slackClient
|
|
1828
|
-
}).catch((err) => {
|
|
1829
|
-
if (formatErrorMessage(err).includes("no_reaction")) return;
|
|
1830
|
-
throw err;
|
|
1831
1673
|
});
|
|
1832
1674
|
}
|
|
1833
1675
|
},
|
|
1834
1676
|
initialEmoji: prepared.ackReactionValue || "eyes",
|
|
1835
|
-
|
|
1836
|
-
timing: DEFAULT_TIMING,
|
|
1677
|
+
presentation: "acknowledgement",
|
|
1837
1678
|
onError: (err) => {
|
|
1838
1679
|
logAckFailure({
|
|
1839
1680
|
log: logVerbose,
|
|
@@ -1863,12 +1704,13 @@ async function createSlackDispatchSetup(prepared) {
|
|
|
1863
1704
|
transformReplyPayload: sanitizeSlackMonitorReplyPayload,
|
|
1864
1705
|
typing: {
|
|
1865
1706
|
start: async () => {
|
|
1866
|
-
if (!threadStatusGate.hasVisibleOutput()) {
|
|
1707
|
+
if (!didSetStatus && !threadStatusGate.hasVisibleOutput()) {
|
|
1867
1708
|
didSetStatus = true;
|
|
1868
|
-
await ctx.
|
|
1709
|
+
await ctx.setSlackSessionStatus({
|
|
1869
1710
|
channelId: message.channel,
|
|
1870
1711
|
threadTs: statusThreadTs,
|
|
1871
|
-
status: "
|
|
1712
|
+
status: "processing",
|
|
1713
|
+
title: prepared.sessionDisplayName ?? prepared.ctxPayload.ThreadLabel,
|
|
1872
1714
|
eventScope: prepared.eventScope
|
|
1873
1715
|
});
|
|
1874
1716
|
}
|
|
@@ -1885,10 +1727,10 @@ async function createSlackDispatchSetup(prepared) {
|
|
|
1885
1727
|
stop: async () => {
|
|
1886
1728
|
if (didSetStatus) {
|
|
1887
1729
|
didSetStatus = false;
|
|
1888
|
-
await ctx.
|
|
1730
|
+
await ctx.setSlackSessionStatus({
|
|
1889
1731
|
channelId: message.channel,
|
|
1890
1732
|
threadTs: statusThreadTs,
|
|
1891
|
-
status: "",
|
|
1733
|
+
status: "active",
|
|
1892
1734
|
eventScope: prepared.eventScope
|
|
1893
1735
|
});
|
|
1894
1736
|
}
|
|
@@ -1943,15 +1785,6 @@ async function createSlackDispatchSetup(prepared) {
|
|
|
1943
1785
|
});
|
|
1944
1786
|
const shouldUseDraftStream = previewStreamingEnabled && !useStreaming;
|
|
1945
1787
|
const blockStreamingEnabled = resolveChannelStreamingBlockEnabled(account.config);
|
|
1946
|
-
const disableBlockStreaming = sourceRepliesAreToolOnly ? true : resolveSlackDisableBlockStreaming({
|
|
1947
|
-
useStreaming,
|
|
1948
|
-
shouldUseDraftStream,
|
|
1949
|
-
blockStreamingEnabled
|
|
1950
|
-
});
|
|
1951
|
-
const onSlackDeliveryError = (err, info) => {
|
|
1952
|
-
runtime.error?.(danger(`slack ${info.kind} reply failed: ${formatSlackError(err)}`));
|
|
1953
|
-
replyPipeline.typingCallbacks?.onIdle?.();
|
|
1954
|
-
};
|
|
1955
1788
|
return {
|
|
1956
1789
|
prepared,
|
|
1957
1790
|
ctx,
|
|
@@ -1959,6 +1792,7 @@ async function createSlackDispatchSetup(prepared) {
|
|
|
1959
1792
|
message,
|
|
1960
1793
|
route,
|
|
1961
1794
|
slackClient,
|
|
1795
|
+
slackClientOptions,
|
|
1962
1796
|
slackStreamFallbackTeamId,
|
|
1963
1797
|
cfg,
|
|
1964
1798
|
runtime,
|
|
@@ -1988,15 +1822,18 @@ async function createSlackDispatchSetup(prepared) {
|
|
|
1988
1822
|
previewStreamingEnabled,
|
|
1989
1823
|
hasSlackCustomIdentity,
|
|
1990
1824
|
shouldUseDraftStream,
|
|
1991
|
-
disableBlockStreaming
|
|
1992
|
-
|
|
1993
|
-
|
|
1825
|
+
disableBlockStreaming: sourceRepliesAreToolOnly ? true : resolveSlackDisableBlockStreaming({
|
|
1826
|
+
useStreaming,
|
|
1827
|
+
shouldUseDraftStream,
|
|
1828
|
+
blockStreamingEnabled
|
|
1829
|
+
}),
|
|
1830
|
+
useStreaming
|
|
1994
1831
|
};
|
|
1995
1832
|
}
|
|
1996
1833
|
//#endregion
|
|
1997
1834
|
//#region extensions/slack/src/monitor/message-handler/dispatch-streaming.ts
|
|
1998
1835
|
function createSlackStreamingDeliveryRuntime(setup) {
|
|
1999
|
-
const { account, ctx, forcedReplyThreadTs, isThreadReply, message, messageSentDeliveryHookContext, messageSentHookContext, messageSentHookTarget, prepared, replyDeliveryMode, replyPlan, runtime, slackClient, slackIdentity, slackMessageMetadata, slackStreamFallbackTeamId } = setup;
|
|
1836
|
+
const { account, ctx, forcedReplyThreadTs, isThreadReply, message, messageSentDeliveryHookContext, messageSentHookContext, messageSentHookTarget, prepared, replyDeliveryMode, replyPlan, runtime, slackClient, slackClientOptions, slackIdentity, slackMessageMetadata, slackStreamFallbackTeamId } = setup;
|
|
2000
1837
|
const state = {
|
|
2001
1838
|
streamSession: null,
|
|
2002
1839
|
nativeProgressStreamStartPromise: null,
|
|
@@ -2007,69 +1844,14 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
2007
1844
|
observedReplyDelivery: false,
|
|
2008
1845
|
observedFinalReplyDelivery: false
|
|
2009
1846
|
};
|
|
2010
|
-
const
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
const delivery = {
|
|
2016
|
-
kind,
|
|
1847
|
+
const emitStreamedDelivery = (content, result) => {
|
|
1848
|
+
emitSlackMessageSentHooks({
|
|
1849
|
+
...messageSentHookContext,
|
|
1850
|
+
to: messageSentHookTarget,
|
|
1851
|
+
accountId: account.accountId,
|
|
2017
1852
|
content,
|
|
2018
|
-
|
|
2019
|
-
};
|
|
2020
|
-
streamedDeliveries.push(delivery);
|
|
2021
|
-
return delivery;
|
|
2022
|
-
};
|
|
2023
|
-
const rememberStreamedDelivery = (kind, content, session) => {
|
|
2024
|
-
recordStreamedDelivery(kind, content);
|
|
2025
|
-
refreshStreamedAcknowledgements(session);
|
|
2026
|
-
};
|
|
2027
|
-
const emitAcknowledgedStreamedDeliveries = (messageId) => {
|
|
2028
|
-
for (const delivery of streamedDeliveries) {
|
|
2029
|
-
if (!delivery.acknowledged || delivery.outcome) continue;
|
|
2030
|
-
emitSlackMessageSentHooks({
|
|
2031
|
-
...messageSentHookContext,
|
|
2032
|
-
to: messageSentHookTarget,
|
|
2033
|
-
accountId: account.accountId,
|
|
2034
|
-
content: delivery.content,
|
|
2035
|
-
success: true,
|
|
2036
|
-
...messageId ? { messageId } : {}
|
|
2037
|
-
});
|
|
2038
|
-
delivery.outcome = "success";
|
|
2039
|
-
}
|
|
2040
|
-
};
|
|
2041
|
-
const acknowledgeStoppedStreamedDeliveries = (session, messageId) => {
|
|
2042
|
-
refreshStreamedAcknowledgements(session);
|
|
2043
|
-
for (const delivery of streamedDeliveries) delivery.acknowledged = true;
|
|
2044
|
-
emitAcknowledgedStreamedDeliveries(messageId);
|
|
2045
|
-
};
|
|
2046
|
-
const emitFailedPendingStreamedDeliveries = (error) => {
|
|
2047
|
-
for (const delivery of streamedDeliveries) {
|
|
2048
|
-
if (delivery.acknowledged || delivery.outcome) continue;
|
|
2049
|
-
emitSlackMessageSentHooks({
|
|
2050
|
-
...messageSentHookContext,
|
|
2051
|
-
to: messageSentHookTarget,
|
|
2052
|
-
accountId: account.accountId,
|
|
2053
|
-
content: delivery.content,
|
|
2054
|
-
success: false,
|
|
2055
|
-
error
|
|
2056
|
-
});
|
|
2057
|
-
delivery.outcome = "failure";
|
|
2058
|
-
}
|
|
2059
|
-
};
|
|
2060
|
-
const emitSuccessfulPendingStreamedDeliveries = (messageId) => {
|
|
2061
|
-
for (const delivery of streamedDeliveries) {
|
|
2062
|
-
if (delivery.acknowledged || delivery.outcome) continue;
|
|
2063
|
-
emitSlackMessageSentHooks({
|
|
2064
|
-
...messageSentHookContext,
|
|
2065
|
-
to: messageSentHookTarget,
|
|
2066
|
-
accountId: account.accountId,
|
|
2067
|
-
content: delivery.content,
|
|
2068
|
-
success: true,
|
|
2069
|
-
...messageId ? { messageId } : {}
|
|
2070
|
-
});
|
|
2071
|
-
delivery.outcome = "success";
|
|
2072
|
-
}
|
|
1853
|
+
...result
|
|
1854
|
+
});
|
|
2073
1855
|
};
|
|
2074
1856
|
let deliveryTracker = createSlackEventDeliveryTracker();
|
|
2075
1857
|
const markPreviewPayloadDelivered = (params) => {
|
|
@@ -2090,62 +1872,75 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
2090
1872
|
if (kind === "block") state.usedBlockReplyThreadTs = deliveredThreadTs;
|
|
2091
1873
|
};
|
|
2092
1874
|
const deliverPendingStreamFallback = async (session, err) => {
|
|
1875
|
+
if (session.stoppedBySlack) return;
|
|
2093
1876
|
let fallbackError = err;
|
|
2094
1877
|
if (!session.stopped) try {
|
|
2095
1878
|
const stopResult = await stopSlackStream({
|
|
2096
1879
|
session,
|
|
2097
1880
|
...slackMessageMetadata ? { metadata: slackMessageMetadata } : {}
|
|
2098
1881
|
});
|
|
2099
|
-
|
|
1882
|
+
if (session.stoppedBySlack) return;
|
|
2100
1883
|
state.observedReplyDelivery = true;
|
|
2101
1884
|
state.usedReplyThreadTs ??= session.threadTs;
|
|
2102
|
-
return
|
|
1885
|
+
return stopResult;
|
|
2103
1886
|
} catch (stopErr) {
|
|
2104
1887
|
if (stopErr instanceof SlackStreamNotDeliveredError) fallbackError = stopErr;
|
|
2105
|
-
else
|
|
1888
|
+
else throw stopErr;
|
|
2106
1889
|
}
|
|
2107
|
-
emitAcknowledgedStreamedDeliveries();
|
|
2108
1890
|
const fallbackText = fallbackError.pendingText.trim();
|
|
2109
|
-
if (!fallbackText) return
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
1891
|
+
if (!fallbackText) return;
|
|
1892
|
+
await deliverReplies({
|
|
1893
|
+
cfg: ctx.cfg,
|
|
1894
|
+
replies: [{ text: fallbackText }],
|
|
1895
|
+
target: prepared.replyTarget,
|
|
1896
|
+
token: ctx.botToken,
|
|
1897
|
+
accountId: account.accountId,
|
|
1898
|
+
runtime,
|
|
1899
|
+
textLimit: ctx.textLimit,
|
|
1900
|
+
mediaMaxBytes: ctx.mediaMaxBytes,
|
|
1901
|
+
replyThreadTs: session.threadTs,
|
|
1902
|
+
replyToMode: replyDeliveryMode,
|
|
1903
|
+
...slackIdentity ? { identity: slackIdentity } : {},
|
|
1904
|
+
...slackMessageMetadata ? { metadata: slackMessageMetadata } : {},
|
|
1905
|
+
...messageSentDeliveryHookContext,
|
|
1906
|
+
deferMessageSentHooks: true,
|
|
1907
|
+
eventScope: prepared.eventScope
|
|
1908
|
+
});
|
|
1909
|
+
markSlackStreamFallbackDelivered(session);
|
|
1910
|
+
if (!session.stopped) try {
|
|
1911
|
+
await stopSlackStream({
|
|
1912
|
+
session,
|
|
1913
|
+
...slackMessageMetadata ? { metadata: slackMessageMetadata } : {}
|
|
2127
1914
|
});
|
|
2128
|
-
|
|
2129
|
-
|
|
1915
|
+
} catch (finalizeErr) {
|
|
1916
|
+
runtime.error?.(danger(`slack-stream: failed to finalize native stream after fallback delivery: ${formatSlackError(finalizeErr)}`));
|
|
1917
|
+
}
|
|
1918
|
+
state.observedReplyDelivery = true;
|
|
1919
|
+
state.usedReplyThreadTs ??= session.threadTs;
|
|
1920
|
+
logVerbose(`slack-stream: streamed delivery failed (${fallbackError.slackCode}); delivered ${fallbackText.length} chars via deliverReplies fallback`);
|
|
1921
|
+
return {};
|
|
1922
|
+
};
|
|
1923
|
+
const finishStream = async (chunks) => {
|
|
1924
|
+
const session = state.streamSession;
|
|
1925
|
+
if (session && !session.stopped) try {
|
|
1926
|
+
try {
|
|
2130
1927
|
await stopSlackStream({
|
|
2131
1928
|
session,
|
|
1929
|
+
...chunks?.length ? { chunks } : {},
|
|
2132
1930
|
...slackMessageMetadata ? { metadata: slackMessageMetadata } : {}
|
|
2133
1931
|
});
|
|
2134
|
-
|
|
2135
|
-
|
|
1932
|
+
state.observedReplyDelivery ||= session.delivered;
|
|
1933
|
+
} catch (error) {
|
|
1934
|
+
if (!(error instanceof SlackStreamNotDeliveredError)) throw error;
|
|
1935
|
+
await deliverPendingStreamFallback(session, error);
|
|
2136
1936
|
}
|
|
2137
|
-
|
|
2138
|
-
state.
|
|
2139
|
-
|
|
2140
|
-
logVerbose(`slack-stream: streamed delivery failed (${fallbackError.slackCode}); delivered ${fallbackText.length} chars via deliverReplies fallback`);
|
|
2141
|
-
return true;
|
|
2142
|
-
} catch (postErr) {
|
|
2143
|
-
emitFailedPendingStreamedDeliveries(formatErrorMessage(postErr));
|
|
2144
|
-
runtime.error?.(danger(`slack-stream: fallback deliverReplies failed after ${fallbackError.slackCode}: ${formatErrorMessage(postErr)}`));
|
|
2145
|
-
return false;
|
|
1937
|
+
} catch (error) {
|
|
1938
|
+
state.streamFailed = true;
|
|
1939
|
+
runtime.error?.(danger(`slack-stream: failed to stop stream: ${formatSlackError(error)}`));
|
|
2146
1940
|
}
|
|
2147
1941
|
};
|
|
2148
1942
|
const deliverNormally = async (params) => {
|
|
1943
|
+
if (state.streamSession?.stoppedBySlack) return;
|
|
2149
1944
|
const replyThreadTs = resolveDeliveryThreadTs(params);
|
|
2150
1945
|
const deliveryReplyThreadTs = replyDeliveryMode === "off" && !forcedReplyThreadTs && !isThreadReply ? void 0 : replyThreadTs;
|
|
2151
1946
|
if (deliveryTracker.hasDelivered({
|
|
@@ -2174,10 +1969,11 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
2174
1969
|
});
|
|
2175
1970
|
state.observedReplyDelivery = true;
|
|
2176
1971
|
if (params.kind === "final") state.observedFinalReplyDelivery = true;
|
|
2177
|
-
const deliveredThreadTs =
|
|
1972
|
+
const deliveredThreadTs = resolveSlackReplyThreadTs({
|
|
2178
1973
|
replyToMode: replyDeliveryMode,
|
|
2179
|
-
|
|
2180
|
-
|
|
1974
|
+
replyToId: params.payload.replyToId,
|
|
1975
|
+
threadId: deliveryReplyThreadTs,
|
|
1976
|
+
replyToCurrent: params.payload.replyToCurrent
|
|
2181
1977
|
});
|
|
2182
1978
|
rememberDeliveredThreadTs(params.kind, deliveredThreadTs);
|
|
2183
1979
|
replyPlan.markSent();
|
|
@@ -2188,19 +1984,6 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
2188
1984
|
});
|
|
2189
1985
|
return deliveryReplyThreadTs;
|
|
2190
1986
|
};
|
|
2191
|
-
const deliverBufferedStreamFallback = async (params) => {
|
|
2192
|
-
if (!await deliverPendingStreamFallback(params.session, params.err)) return false;
|
|
2193
|
-
replyPlan.markSent();
|
|
2194
|
-
if (params.kind === "final") state.observedFinalReplyDelivery = true;
|
|
2195
|
-
deliveryTracker.markDelivered({
|
|
2196
|
-
kind: params.kind,
|
|
2197
|
-
payload: params.payload,
|
|
2198
|
-
threadTs: params.session.threadTs,
|
|
2199
|
-
textOverride: params.textOverride
|
|
2200
|
-
});
|
|
2201
|
-
rememberDeliveredThreadTs(params.kind, params.session.threadTs);
|
|
2202
|
-
return true;
|
|
2203
|
-
};
|
|
2204
1987
|
const isStreamingEligible = (payload, options) => {
|
|
2205
1988
|
const reply = resolveSendableOutboundReplyParts(payload);
|
|
2206
1989
|
const renderPlan = resolveSlackReplyRenderPlan(payload);
|
|
@@ -2208,7 +1991,7 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
2208
1991
|
return !state.streamFailed && !reply.hasMedia && renderPlan.mode !== "split" && !plannedBlocks?.length && !readSlackReplyBlocks(payload)?.length && reply.hasText && (!options?.maxTextBytes || countSlackTextUtf8Bytes(reply.trimmedText) <= options.maxTextBytes);
|
|
2209
1992
|
};
|
|
2210
1993
|
const deliverWithStreaming = async (params) => {
|
|
2211
|
-
|
|
1994
|
+
if (state.streamSession?.stoppedBySlack) return;
|
|
2212
1995
|
if (!isStreamingEligible(params.payload)) {
|
|
2213
1996
|
await deliverNormally({
|
|
2214
1997
|
payload: params.payload,
|
|
@@ -2217,45 +2000,44 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
2217
2000
|
});
|
|
2218
2001
|
return;
|
|
2219
2002
|
}
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
let
|
|
2003
|
+
if (!state.streamSession && state.nativeProgressStreamStartPromise) await state.nativeProgressStreamStartPromise;
|
|
2004
|
+
if (state.streamSession?.stoppedBySlack) return;
|
|
2005
|
+
let session = state.streamSession;
|
|
2006
|
+
const threadTs = session?.threadTs ?? replyPlan.nextThreadTs();
|
|
2007
|
+
if (state.streamFailed || !threadTs) {
|
|
2008
|
+
state.streamFailed = true;
|
|
2009
|
+
await deliverNormally({
|
|
2010
|
+
payload: params.payload,
|
|
2011
|
+
kind: params.kind,
|
|
2012
|
+
forcedThreadTs: threadTs ?? state.nativeProgressStreamThreadTs
|
|
2013
|
+
});
|
|
2014
|
+
return;
|
|
2015
|
+
}
|
|
2016
|
+
const hookContent = resolveSendableOutboundReplyParts(params.payload).trimmedText;
|
|
2017
|
+
const text = params.streamText ?? hookContent;
|
|
2018
|
+
if (deliveryTracker.hasDelivered({
|
|
2019
|
+
...params,
|
|
2020
|
+
threadTs,
|
|
2021
|
+
textOverride: text
|
|
2022
|
+
})) {
|
|
2023
|
+
logVerbose("slack-stream: suppressed duplicate reply payload");
|
|
2024
|
+
return;
|
|
2025
|
+
}
|
|
2026
|
+
let messageId;
|
|
2223
2027
|
try {
|
|
2224
|
-
if (
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
return;
|
|
2232
|
-
}
|
|
2233
|
-
if (!state.streamSession) {
|
|
2234
|
-
const streamThreadTs = replyPlan.nextThreadTs();
|
|
2235
|
-
plannedThreadTs = streamThreadTs;
|
|
2236
|
-
if (!streamThreadTs) {
|
|
2237
|
-
logVerbose("slack-stream: no reply thread target for stream start, falling back to normal delivery");
|
|
2238
|
-
state.streamFailed = true;
|
|
2239
|
-
await deliverNormally({
|
|
2240
|
-
payload: params.payload,
|
|
2241
|
-
kind: params.kind
|
|
2242
|
-
});
|
|
2243
|
-
return;
|
|
2244
|
-
}
|
|
2245
|
-
if (deliveryTracker.hasDelivered({
|
|
2246
|
-
kind: params.kind,
|
|
2247
|
-
payload: params.payload,
|
|
2248
|
-
threadTs: streamThreadTs,
|
|
2249
|
-
textOverride: text
|
|
2250
|
-
})) {
|
|
2251
|
-
logVerbose("slack-stream: suppressed duplicate stream start payload");
|
|
2252
|
-
return;
|
|
2253
|
-
}
|
|
2254
|
-
state.streamSession = await startSlackStream({
|
|
2028
|
+
if (session) await appendSlackStream({
|
|
2029
|
+
session,
|
|
2030
|
+
text: `${params.appendSeparator === false ? "" : "\n"}${text}`,
|
|
2031
|
+
chunks: []
|
|
2032
|
+
});
|
|
2033
|
+
else {
|
|
2034
|
+
session = await startSlackStream({
|
|
2255
2035
|
client: slackClient,
|
|
2036
|
+
clientOptions: slackClientOptions,
|
|
2256
2037
|
channel: message.channel,
|
|
2257
|
-
threadTs
|
|
2038
|
+
threadTs,
|
|
2258
2039
|
text,
|
|
2040
|
+
chunks: [],
|
|
2259
2041
|
...params.taskDisplayMode ? { taskDisplayMode: params.taskDisplayMode } : {},
|
|
2260
2042
|
...slackIdentity ? { identity: slackIdentity } : {},
|
|
2261
2043
|
teamId: await resolveSlackStreamRecipientTeamId({
|
|
@@ -2266,94 +2048,63 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
2266
2048
|
}),
|
|
2267
2049
|
userId: message.user
|
|
2268
2050
|
});
|
|
2269
|
-
|
|
2270
|
-
if (state.streamSession.delivered) {
|
|
2271
|
-
state.observedReplyDelivery = true;
|
|
2272
|
-
if (params.kind === "final") state.observedFinalReplyDelivery = true;
|
|
2273
|
-
}
|
|
2274
|
-
if (hookContent) rememberStreamedDelivery(params.kind, hookContent, state.streamSession);
|
|
2275
|
-
rememberDeliveredThreadTs(params.kind, streamThreadTs);
|
|
2276
|
-
replyPlan.markSent();
|
|
2277
|
-
deliveryTracker.markDelivered({
|
|
2278
|
-
kind: params.kind,
|
|
2279
|
-
payload: params.payload,
|
|
2280
|
-
threadTs: streamThreadTs,
|
|
2281
|
-
textOverride: text
|
|
2282
|
-
});
|
|
2283
|
-
return;
|
|
2051
|
+
state.streamSession = session;
|
|
2284
2052
|
}
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
if (hookContent) recordStreamedDelivery(params.kind, hookContent);
|
|
2295
|
-
await appendSlackStream({
|
|
2296
|
-
session: state.streamSession,
|
|
2297
|
-
text: `${params.appendSeparator === false ? "" : "\n"}${text}`
|
|
2298
|
-
});
|
|
2299
|
-
refreshStreamedAcknowledgements(state.streamSession);
|
|
2300
|
-
if (state.streamSession.delivered && state.streamSession.pendingText.length === 0) {
|
|
2301
|
-
state.observedReplyDelivery = true;
|
|
2302
|
-
if (params.kind === "final") state.observedFinalReplyDelivery = true;
|
|
2053
|
+
messageId = session.streamer.ts;
|
|
2054
|
+
} catch (error) {
|
|
2055
|
+
state.streamFailed = true;
|
|
2056
|
+
if (!(error instanceof SlackStreamNotDeliveredError)) {
|
|
2057
|
+
emitStreamedDelivery(hookContent, {
|
|
2058
|
+
success: false,
|
|
2059
|
+
error: formatErrorMessage(error)
|
|
2060
|
+
});
|
|
2061
|
+
throw error;
|
|
2303
2062
|
}
|
|
2304
|
-
|
|
2305
|
-
kind: params.kind,
|
|
2306
|
-
payload: params.payload,
|
|
2307
|
-
threadTs: state.streamSession.threadTs,
|
|
2308
|
-
textOverride: text
|
|
2309
|
-
});
|
|
2310
|
-
} catch (err) {
|
|
2311
|
-
if (err instanceof SlackStreamNotDeliveredError) {
|
|
2312
|
-
state.streamFailed = true;
|
|
2313
|
-
if (state.streamSession) {
|
|
2314
|
-
if (await deliverBufferedStreamFallback({
|
|
2315
|
-
session: state.streamSession,
|
|
2316
|
-
err,
|
|
2317
|
-
payload: params.payload,
|
|
2318
|
-
kind: params.kind,
|
|
2319
|
-
textOverride: text
|
|
2320
|
-
})) return;
|
|
2321
|
-
throw err;
|
|
2322
|
-
}
|
|
2063
|
+
if (!session) {
|
|
2323
2064
|
await deliverNormally({
|
|
2324
2065
|
payload: params.payload,
|
|
2325
2066
|
kind: params.kind,
|
|
2326
|
-
forcedThreadTs:
|
|
2067
|
+
forcedThreadTs: threadTs
|
|
2327
2068
|
});
|
|
2328
2069
|
return;
|
|
2329
2070
|
}
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
})) return;
|
|
2341
|
-
throw err;
|
|
2071
|
+
try {
|
|
2072
|
+
const fallback = await deliverPendingStreamFallback(session, error);
|
|
2073
|
+
if (!fallback && !session.stoppedBySlack) throw error;
|
|
2074
|
+
messageId = fallback?.messageId;
|
|
2075
|
+
} catch (fallbackError) {
|
|
2076
|
+
emitStreamedDelivery(hookContent, {
|
|
2077
|
+
success: false,
|
|
2078
|
+
error: formatErrorMessage(fallbackError)
|
|
2079
|
+
});
|
|
2080
|
+
throw fallbackError;
|
|
2342
2081
|
}
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2082
|
+
}
|
|
2083
|
+
if (session.stoppedBySlack && session.pendingText) {
|
|
2084
|
+
emitStreamedDelivery(hookContent, {
|
|
2085
|
+
success: false,
|
|
2086
|
+
error: "Stopped by Slack user"
|
|
2347
2087
|
});
|
|
2088
|
+
return;
|
|
2348
2089
|
}
|
|
2090
|
+
state.observedReplyDelivery = true;
|
|
2091
|
+
if (params.kind === "final") state.observedFinalReplyDelivery = true;
|
|
2092
|
+
rememberDeliveredThreadTs(params.kind, threadTs);
|
|
2093
|
+
replyPlan.markSent();
|
|
2094
|
+
deliveryTracker.markDelivered({
|
|
2095
|
+
...params,
|
|
2096
|
+
threadTs,
|
|
2097
|
+
textOverride: text
|
|
2098
|
+
});
|
|
2099
|
+
emitStreamedDelivery(hookContent, {
|
|
2100
|
+
success: true,
|
|
2101
|
+
...messageId ? { messageId } : {}
|
|
2102
|
+
});
|
|
2349
2103
|
};
|
|
2350
2104
|
return Object.assign(state, {
|
|
2351
|
-
acknowledgeStoppedStreamedDeliveries,
|
|
2352
2105
|
deliverNormally,
|
|
2353
|
-
deliverPendingStreamFallback,
|
|
2354
2106
|
deliverWithStreaming,
|
|
2355
|
-
|
|
2356
|
-
emitFailedPendingStreamedDeliveries,
|
|
2107
|
+
finishStream,
|
|
2357
2108
|
hasDelivered: (params) => deliveryTracker.hasDelivered(params),
|
|
2358
2109
|
isStreamingEligible,
|
|
2359
2110
|
markPreviewPayloadDelivered,
|
|
@@ -2367,7 +2118,35 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
2367
2118
|
//#region extensions/slack/src/monitor/message-handler/dispatch.ts
|
|
2368
2119
|
async function dispatchPreparedSlackMessage(prepared) {
|
|
2369
2120
|
const setup = await createSlackDispatchSetup(prepared);
|
|
2370
|
-
const
|
|
2121
|
+
const beginSessionRun = () => registerSlackSessionRun(prepared.ctx, {
|
|
2122
|
+
channelId: prepared.message.channel,
|
|
2123
|
+
threadTs: setup.streamThreadHint,
|
|
2124
|
+
eventScope: prepared.eventScope
|
|
2125
|
+
}, {
|
|
2126
|
+
...prepared.route,
|
|
2127
|
+
sessionKey: prepared.ctxPayload.SessionKey ?? prepared.route.sessionKey
|
|
2128
|
+
});
|
|
2129
|
+
const upstreamLifecycle = prepared.turnAdoptionLifecycle;
|
|
2130
|
+
let releaseDeferred;
|
|
2131
|
+
const turnAdoptionLifecycle = upstreamLifecycle && {
|
|
2132
|
+
...upstreamLifecycle,
|
|
2133
|
+
onDeferred: () => {
|
|
2134
|
+
const accepted = upstreamLifecycle.onDeferred?.();
|
|
2135
|
+
if (accepted !== false) releaseDeferred ??= beginSessionRun();
|
|
2136
|
+
return accepted;
|
|
2137
|
+
},
|
|
2138
|
+
onSettled: () => {
|
|
2139
|
+
releaseDeferred?.();
|
|
2140
|
+
upstreamLifecycle.onSettled?.();
|
|
2141
|
+
}
|
|
2142
|
+
};
|
|
2143
|
+
const release = beginSessionRun();
|
|
2144
|
+
await dispatchSlackMessageWithSetup(setup, beginSessionRun, turnAdoptionLifecycle).finally(release);
|
|
2145
|
+
}
|
|
2146
|
+
async function dispatchSlackMessageWithSetup(setup, beginSessionRun, turnAdoptionLifecycle) {
|
|
2147
|
+
const { prepared } = setup;
|
|
2148
|
+
const { account, cfg, ctx, disableBlockStreaming, hasSlackCustomIdentity, hasRepliedRef, message, messageSentHookContext, messageSentHookTarget, onModelSelected, previewStreamingEnabled, replyPipeline, replyPlan, route, runtime, slackClient, slackStreaming, sourceReplyDeliveryMode, statusReactions, statusReactionsEnabled, statusThreadTs, suppressRoomEventTyping, useStreaming } = setup;
|
|
2149
|
+
let dispatchError;
|
|
2371
2150
|
const delivery = createSlackStreamingDeliveryRuntime(setup);
|
|
2372
2151
|
const draftPreviewCommitted = { value: false };
|
|
2373
2152
|
const progress = createSlackProgressRuntime({
|
|
@@ -2408,6 +2187,14 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2408
2187
|
pendingFailureNotice = notice;
|
|
2409
2188
|
return payload;
|
|
2410
2189
|
};
|
|
2190
|
+
let compactFinalDeliveryStarted = false;
|
|
2191
|
+
const clearCompactProgress = async () => {
|
|
2192
|
+
try {
|
|
2193
|
+
await draftStream?.clear();
|
|
2194
|
+
} catch (err) {
|
|
2195
|
+
logVerbose(`slack: progress preview cleanup failed (${formatSlackError(err)})`);
|
|
2196
|
+
}
|
|
2197
|
+
};
|
|
2411
2198
|
const deliverSlackPayload = async (payload, info) => {
|
|
2412
2199
|
if (info.kind === "final" && slackStreaming.mode === "progress" && progress.isProgressMode) {
|
|
2413
2200
|
if (progress.useNativeProgressStreaming) {
|
|
@@ -2415,6 +2202,24 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2415
2202
|
return;
|
|
2416
2203
|
}
|
|
2417
2204
|
progress.progressDraft.markFinalReplyStarted();
|
|
2205
|
+
if (!progress.useDraftProgressCard) {
|
|
2206
|
+
compactFinalDeliveryStarted = true;
|
|
2207
|
+
await draftStream?.discardPending();
|
|
2208
|
+
const supplement = getReplyPayloadTtsSupplement(payload);
|
|
2209
|
+
await delivery.deliverNormally({
|
|
2210
|
+
payload: supplement && !supplement.visibleTextAlreadyDelivered && !payload.text?.trim() ? {
|
|
2211
|
+
...payload,
|
|
2212
|
+
text: supplement.spokenText
|
|
2213
|
+
} : payload,
|
|
2214
|
+
kind: info.kind,
|
|
2215
|
+
forcedThreadTs: delivery.usedReplyThreadTs
|
|
2216
|
+
});
|
|
2217
|
+
if (delivery.observedFinalReplyDelivery) {
|
|
2218
|
+
progress.progressDraft.markFinalReplyDelivered();
|
|
2219
|
+
await clearCompactProgress();
|
|
2220
|
+
}
|
|
2221
|
+
return;
|
|
2222
|
+
}
|
|
2418
2223
|
if (progress.useDraftProgressCard) {
|
|
2419
2224
|
await delivery.deliverNormally({
|
|
2420
2225
|
payload,
|
|
@@ -2459,7 +2264,11 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2459
2264
|
const slackBlocks = replyRenderPlan.mode === "single" ? replyRenderPlan.blocks : replyRenderPlan.blockPart?.blocks;
|
|
2460
2265
|
const requiresSeparateFallbackDelivery = replyRenderPlan.mode === "split";
|
|
2461
2266
|
const trimmedFinalText = replyRenderPlan.mode === "single" ? replyRenderPlan.text.trim() : replyRenderPlan.fallbackText.trim();
|
|
2462
|
-
const previewFinalText = replyRenderPlan.mode === "single" && replyRenderPlan.textIsSlackMrkdwn ? trimmedFinalText : normalizeSlackOutboundText((replySourceText ?? "").trim()
|
|
2267
|
+
const previewFinalText = replyRenderPlan.mode === "single" && replyRenderPlan.textIsSlackMrkdwn ? trimmedFinalText : normalizeSlackOutboundText((replySourceText ?? "").trim(), { tableMode: resolveMarkdownTableMode({
|
|
2268
|
+
cfg,
|
|
2269
|
+
channel: "slack",
|
|
2270
|
+
accountId: account.accountId
|
|
2271
|
+
}) });
|
|
2463
2272
|
const previewFinalTextFitsEdit = countSlackTextUtf8Bytes(previewFinalText) <= SLACK_EDIT_TEXT_MAX_BYTES;
|
|
2464
2273
|
const shouldRestoreTtsSupplementTextForPreviewFallback = Boolean(ttsSupplement) && ttsSupplement?.visibleTextAlreadyDelivered !== true && Boolean(draftStream) && !draftPreviewCommitted.value && !delivery.observedFinalReplyDelivery && previewStreamingEnabled && !payload.text?.trim();
|
|
2465
2274
|
let ttsPreviewFinalization;
|
|
@@ -2562,7 +2371,6 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2562
2371
|
});
|
|
2563
2372
|
if (info.kind === "final") progress.progressDraft.markFinalReplyDelivered();
|
|
2564
2373
|
};
|
|
2565
|
-
let dispatchError;
|
|
2566
2374
|
let agentRunFailed = false;
|
|
2567
2375
|
let settledDispatchResult;
|
|
2568
2376
|
try {
|
|
@@ -2586,13 +2394,18 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2586
2394
|
},
|
|
2587
2395
|
delivery: {
|
|
2588
2396
|
deliver: deliverSlackPayload,
|
|
2589
|
-
onError:
|
|
2397
|
+
onError: (err, info) => {
|
|
2398
|
+
dispatchError ??= err;
|
|
2399
|
+
runtime.error?.(danger(`slack ${info.kind} reply failed: ${formatSlackError(err)}`));
|
|
2400
|
+
replyPipeline.typingCallbacks?.onIdle?.();
|
|
2401
|
+
}
|
|
2590
2402
|
},
|
|
2591
2403
|
record: prepared.turn.record,
|
|
2592
2404
|
history: prepared.turn.history,
|
|
2593
2405
|
botLoopProtection: resolveSlackBotLoopProtection(prepared),
|
|
2594
2406
|
replyOptions: {
|
|
2595
|
-
|
|
2407
|
+
queuedDeliveryCorrelations: [{ begin: beginSessionRun }],
|
|
2408
|
+
...turnAdoptionLifecycle ? { turnAdoptionLifecycle } : {},
|
|
2596
2409
|
skillFilter: prepared.channelConfig?.skills,
|
|
2597
2410
|
sourceReplyDeliveryMode,
|
|
2598
2411
|
suppressTyping: suppressRoomEventTyping ? true : void 0,
|
|
@@ -2622,18 +2435,18 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2622
2435
|
},
|
|
2623
2436
|
onQueuedFollowupAdmitted: progress.onQueuedFollowupAdmitted,
|
|
2624
2437
|
onQueuedFollowupSettled: progress.onQueuedFollowupSettled,
|
|
2625
|
-
onReasoningStream:
|
|
2438
|
+
onReasoningStream: async (payload) => {
|
|
2626
2439
|
const visible = await progress.pushReasoningProgress(payload);
|
|
2627
|
-
if (
|
|
2628
|
-
await statusReactions.setThinking();
|
|
2440
|
+
if (statusReactionsEnabled) await statusReactions.setThinking();
|
|
2629
2441
|
return visible;
|
|
2630
|
-
}
|
|
2442
|
+
},
|
|
2631
2443
|
onToolStart: async (payload) => {
|
|
2632
2444
|
if (statusReactionsEnabled) await statusReactions.setTool(payload.name);
|
|
2633
2445
|
if (payload.phase === "start") progress.progressWorkCounter.noteToolCall(payload.name);
|
|
2634
2446
|
return await progress.progressDraft.pushToolEvent(payload);
|
|
2635
2447
|
},
|
|
2636
2448
|
onItemEvent: async (payload) => {
|
|
2449
|
+
if (payload.kind === "preamble" && (payload.phase === "start" || payload.phase === "update") && !draftStream?.messageId() && !delivery.streamSession?.delivered) return false;
|
|
2637
2450
|
if (progress.isProgressMode && payload.kind === "preamble") {
|
|
2638
2451
|
if (progress.shouldYieldDraftProgress()) return false;
|
|
2639
2452
|
const headlineVisible = await progress.progressDraft.pushPreambleHeadline(payload.progressText, { itemId: payload.itemId });
|
|
@@ -2670,48 +2483,28 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2670
2483
|
});
|
|
2671
2484
|
}
|
|
2672
2485
|
} catch (err) {
|
|
2673
|
-
dispatchError
|
|
2486
|
+
dispatchError ??= err;
|
|
2674
2487
|
} finally {
|
|
2675
|
-
progress.
|
|
2488
|
+
await progress.cancel();
|
|
2676
2489
|
if (!progress.useDraftProgressCard) await draftStream?.discardPending();
|
|
2677
2490
|
}
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
const stopResult = await stopSlackStream({
|
|
2684
|
-
session: finalStream,
|
|
2685
|
-
...completionChunks?.length ? { chunks: completionChunks } : {},
|
|
2686
|
-
...slackMessageMetadata ? { metadata: slackMessageMetadata } : {}
|
|
2687
|
-
});
|
|
2688
|
-
delivery.acknowledgeStoppedStreamedDeliveries(finalStream, stopResult?.messageId);
|
|
2689
|
-
} catch (err) {
|
|
2690
|
-
if (err instanceof SlackStreamNotDeliveredError) {
|
|
2691
|
-
streamFallbackDelivered = await delivery.deliverPendingStreamFallback(finalStream, err);
|
|
2692
|
-
if (!streamFallbackDelivered) dispatchError ??= err;
|
|
2693
|
-
} else {
|
|
2694
|
-
const error = formatSlackError(err);
|
|
2695
|
-
delivery.emitAcknowledgedStreamedDeliveries();
|
|
2696
|
-
delivery.emitFailedPendingStreamedDeliveries(error);
|
|
2697
|
-
runtime.error?.(danger(`slack-stream: failed to stop stream: ${error}`));
|
|
2698
|
-
if (!finalStream.delivered) dispatchError ??= err;
|
|
2699
|
-
}
|
|
2700
|
-
}
|
|
2701
|
-
const anyReplyDelivered = hasVisibleInboundReplyDispatch(settledDispatchResult, {
|
|
2702
|
-
observedReplyDelivery: delivery.observedReplyDelivery,
|
|
2703
|
-
fallbackDelivered: streamFallbackDelivered
|
|
2704
|
-
});
|
|
2491
|
+
const completionChunks = progress.useNativeProgressStreaming && !progress.nativeProgressCompletionSent ? progress.buildNativeProgressCompletionChunks(dispatchError || agentRunFailed ? "error" : progress.nativeProgressTerminalStatus) : void 0;
|
|
2492
|
+
if (completionChunks?.length) progress.nativeProgressCompletionSent = true;
|
|
2493
|
+
await delivery.finishStream(completionChunks);
|
|
2494
|
+
const anyReplyDelivered = hasVisibleInboundReplyDispatch(settledDispatchResult, { observedReplyDelivery: delivery.observedReplyDelivery });
|
|
2495
|
+
if (progress.isProgressMode && !progress.useNativeProgressStreaming && !progress.useDraftProgressCard && !compactFinalDeliveryStarted && !dispatchError && !agentRunFailed && anyReplyDelivered) await clearCompactProgress();
|
|
2705
2496
|
if (pendingFailureNotice && anyReplyDelivered) recordSlackThreadFailureNotice(pendingFailureNotice);
|
|
2706
2497
|
if (dispatchError || agentRunFailed) await progress.finalizeDraftProgressCard("error");
|
|
2707
2498
|
await progress.dropDetachedProgressCards();
|
|
2708
|
-
if (statusReactionsEnabled)
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2499
|
+
if (statusReactionsEnabled) {
|
|
2500
|
+
if (dispatchError || agentRunFailed) {
|
|
2501
|
+
await statusReactions.setError();
|
|
2502
|
+
statusReactions.restoreInitial();
|
|
2503
|
+
} else if (anyReplyDelivered) {
|
|
2504
|
+
await statusReactions.setDone();
|
|
2505
|
+
statusReactions.restoreInitial();
|
|
2506
|
+
} else await statusReactions.restoreInitial();
|
|
2507
|
+
}
|
|
2715
2508
|
const participationThreadTs = delivery.usedReplyThreadTs ?? statusThreadTs;
|
|
2716
2509
|
if (anyReplyDelivered && participationThreadTs) recordSlackThreadParticipation(account.accountId, message.channel, participationThreadTs, {
|
|
2717
2510
|
agentId: route.agentId,
|
|
@@ -2770,7 +2563,7 @@ function normalizeSlackAppContextEntities(value) {
|
|
|
2770
2563
|
//#endregion
|
|
2771
2564
|
//#region extensions/slack/src/monitor/thread.ts
|
|
2772
2565
|
const THREAD_STARTER_CACHE = /* @__PURE__ */ new Map();
|
|
2773
|
-
const THREAD_STARTER_CACHE_TTL_MS =
|
|
2566
|
+
const THREAD_STARTER_CACHE_TTL_MS = 216e5;
|
|
2774
2567
|
const THREAD_STARTER_CACHE_MAX = 2e3;
|
|
2775
2568
|
function evictThreadStarterCache() {
|
|
2776
2569
|
const now = asDateTimestampMs(Date.now());
|
|
@@ -2965,7 +2758,24 @@ async function discardSlackPreflightMedia(media) {
|
|
|
2965
2758
|
const SLACK_MENTION_RESOLUTION_CONCURRENCY = 4;
|
|
2966
2759
|
const SLACK_MENTION_RESOLUTION_MAX_LOOKUPS_PER_MESSAGE = 20;
|
|
2967
2760
|
const SLACK_USER_MENTION_RE$1 = /<@([A-Z0-9]+)(?:\|[^>]+)?>/gi;
|
|
2968
|
-
const
|
|
2761
|
+
const MAX_SLACK_UNAVAILABLE_FILE_TEXT_CHARS = 2e3;
|
|
2762
|
+
const loadSlackMediaModule$1 = createLazyRuntimeModule(() => import("./actions-CFkvEv1d.js").then((n) => n._));
|
|
2763
|
+
function formatSlackUnavailableMedia(params) {
|
|
2764
|
+
let fileReferences = params.files?.map((file) => `${formatSlackFileReference(file)} unavailable (${file.reason})`).join(", ");
|
|
2765
|
+
if (fileReferences && fileReferences.length > MAX_SLACK_UNAVAILABLE_FILE_TEXT_CHARS) fileReferences = `${truncateUtf16Safe(fileReferences, MAX_SLACK_UNAVAILABLE_FILE_TEXT_CHARS)}; … (file references truncated)`;
|
|
2766
|
+
const fileBlock = fileReferences ? `[Slack file: ${fileReferences}]` : void 0;
|
|
2767
|
+
const body = [params.body, fileBlock].filter(Boolean).join("\n");
|
|
2768
|
+
if (params.unavailableMediaCount === 0) return body;
|
|
2769
|
+
const notice = `[slack ${params.unavailableMediaCount > 1 ? `${params.unavailableMediaCount} attachments` : "attachment"} unavailable]`;
|
|
2770
|
+
return params.prependUnavailable ? [
|
|
2771
|
+
notice,
|
|
2772
|
+
fileBlock,
|
|
2773
|
+
params.body
|
|
2774
|
+
].filter(Boolean).join("\n") : formatInboundMediaUnavailableText({
|
|
2775
|
+
body,
|
|
2776
|
+
notice
|
|
2777
|
+
});
|
|
2778
|
+
}
|
|
2969
2779
|
function collectUniqueSlackMentionIds$1(texts) {
|
|
2970
2780
|
const seen = /* @__PURE__ */ new Set();
|
|
2971
2781
|
const mentionIds = [];
|
|
@@ -3016,7 +2826,6 @@ async function resolveSlackMessageContent(params) {
|
|
|
3016
2826
|
})) : null;
|
|
3017
2827
|
const effectiveDirectMedia = attachmentContent?.media.length ? attachmentContent.media : null;
|
|
3018
2828
|
const mediaPlaceholder = effectiveDirectMedia?.map((item) => item.placeholder).join(" ");
|
|
3019
|
-
const fileOnlyFallback = attachmentContent?.files?.map(formatSlackFileReference).join(", ");
|
|
3020
2829
|
let botAttachmentText;
|
|
3021
2830
|
if (params.isBotMessage && !attachmentContent?.text) {
|
|
3022
2831
|
const botAttachmentTextParts = [];
|
|
@@ -3040,7 +2849,8 @@ async function resolveSlackMessageContent(params) {
|
|
|
3040
2849
|
if (skippedLookups > 0) logVerbose(`slack: skipping ${skippedLookups} mention lookup(s) beyond per-message cap (${SLACK_MENTION_RESOLUTION_MAX_LOOKUPS_PER_MESSAGE})`);
|
|
3041
2850
|
const { results } = await runTasksWithConcurrency({
|
|
3042
2851
|
tasks: lookupIds.map((userId) => async () => {
|
|
3043
|
-
const
|
|
2852
|
+
const user = await resolveUserName(userId);
|
|
2853
|
+
const renderedName = normalizeOptionalString(user?.name);
|
|
3044
2854
|
return {
|
|
3045
2855
|
userId,
|
|
3046
2856
|
rendered: renderedName ? `<@${userId}> (${renderedName})` : null
|
|
@@ -3053,17 +2863,15 @@ async function resolveSlackMessageContent(params) {
|
|
|
3053
2863
|
renderedMentions.set(result.userId, result.rendered);
|
|
3054
2864
|
}
|
|
3055
2865
|
}
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
body: rawBody,
|
|
3066
|
-
notice: `[slack ${unavailableImageCount > 1 ? `${unavailableImageCount} forwarded images` : "forwarded image"} unavailable]`
|
|
2866
|
+
const rawBody = formatSlackUnavailableMedia({
|
|
2867
|
+
body: [
|
|
2868
|
+
renderSlackUserMentions(textParts[0], renderedMentions),
|
|
2869
|
+
renderSlackUserMentions(textParts[1], renderedMentions),
|
|
2870
|
+
renderSlackUserMentions(textParts[2], renderedMentions),
|
|
2871
|
+
mediaPlaceholder
|
|
2872
|
+
].filter(Boolean).join("\n") || "",
|
|
2873
|
+
files: attachmentContent?.files,
|
|
2874
|
+
unavailableMediaCount: attachmentContent?.unavailableMediaCount ?? 0
|
|
3067
2875
|
});
|
|
3068
2876
|
return rawBody ? {
|
|
3069
2877
|
rawBody,
|
|
@@ -3143,104 +2951,6 @@ async function resolveSlackDmHistoryContext(params) {
|
|
|
3143
2951
|
}
|
|
3144
2952
|
}
|
|
3145
2953
|
//#endregion
|
|
3146
|
-
//#region extensions/slack/src/monitor/message-handler/prepare-routing.ts
|
|
3147
|
-
function resolveSlackBaseConversationId(params) {
|
|
3148
|
-
return qualifySlackConversationId(params.isDirectMessage ? `user:${params.message.user ?? "unknown"}` : params.message.channel, params.eventScope);
|
|
3149
|
-
}
|
|
3150
|
-
function resolveSlackInitialAgentRoute(params) {
|
|
3151
|
-
const route = resolveAgentRoute({
|
|
3152
|
-
cfg: normalizeSlackRouteBindingConfig(params.ctx.cfg),
|
|
3153
|
-
channel: "slack",
|
|
3154
|
-
accountId: params.account.accountId,
|
|
3155
|
-
teamId: params.eventScope?.teamId || params.ctx.teamId || void 0,
|
|
3156
|
-
peer: {
|
|
3157
|
-
kind: params.isDirectMessage ? "direct" : params.isRoom ? "channel" : "group",
|
|
3158
|
-
id: qualifySlackRoutePeerId({
|
|
3159
|
-
id: params.isDirectMessage ? params.message.user ?? "unknown" : params.message.channel,
|
|
3160
|
-
kind: params.isDirectMessage ? "user" : "channel",
|
|
3161
|
-
eventScope: params.eventScope
|
|
3162
|
-
})
|
|
3163
|
-
}
|
|
3164
|
-
});
|
|
3165
|
-
if (!params.eventScope || !params.isDirectMessage || route.dmScope !== "main") return route;
|
|
3166
|
-
const sessionKey = resolveSlackEnterpriseMainDmSessionKey({
|
|
3167
|
-
baseSessionKey: route.sessionKey,
|
|
3168
|
-
accountId: params.account.accountId,
|
|
3169
|
-
eventScope: params.eventScope
|
|
3170
|
-
});
|
|
3171
|
-
return {
|
|
3172
|
-
...route,
|
|
3173
|
-
sessionKey,
|
|
3174
|
-
mainSessionKey: sessionKey
|
|
3175
|
-
};
|
|
3176
|
-
}
|
|
3177
|
-
function resolveSlackRoutingContext(params) {
|
|
3178
|
-
const { ctx, account, message, isDirectMessage, isGroupDm, isRoom, isRoomish, channelConfig, seedTopLevelRoomThread, assistantThreadTs, agentViewThreadTs, eventScope } = params;
|
|
3179
|
-
let route = resolveSlackInitialAgentRoute({
|
|
3180
|
-
ctx,
|
|
3181
|
-
account,
|
|
3182
|
-
message,
|
|
3183
|
-
isDirectMessage,
|
|
3184
|
-
isRoom,
|
|
3185
|
-
eventScope
|
|
3186
|
-
});
|
|
3187
|
-
const chatType = isDirectMessage ? "direct" : isGroupDm ? "group" : "channel";
|
|
3188
|
-
const replyToMode = channelConfig?.replyToMode ?? resolveSlackReplyToMode(account, chatType);
|
|
3189
|
-
const threadContext = resolveSlackThreadContext({
|
|
3190
|
-
message,
|
|
3191
|
-
replyToMode,
|
|
3192
|
-
isDirectMessage
|
|
3193
|
-
});
|
|
3194
|
-
const threadTs = threadContext.incomingThreadTs;
|
|
3195
|
-
const isThreadReply = threadContext.isThreadReply;
|
|
3196
|
-
const autoThreadId = !isThreadReply && replyToMode === "all" && threadContext.messageTs ? threadContext.messageTs : void 0;
|
|
3197
|
-
const seedCandidateThreadId = threadContext.incomingThreadTs ?? threadContext.messageTs;
|
|
3198
|
-
const routedThreadId = (isDirectMessage ? assistantThreadTs ?? agentViewThreadTs : isRoomish ? isThreadReply && threadTs ? threadTs : void 0 : isThreadReply ? threadTs : autoThreadId) ?? (isRoomish ? !isThreadReply && isRoom && seedTopLevelRoomThread && replyToMode !== "off" && seedCandidateThreadId ? seedCandidateThreadId : void 0 : void 0);
|
|
3199
|
-
const baseConversationId = resolveSlackBaseConversationId({
|
|
3200
|
-
message,
|
|
3201
|
-
isDirectMessage,
|
|
3202
|
-
eventScope
|
|
3203
|
-
});
|
|
3204
|
-
const runtimeBindingThreadId = routedThreadId ?? (isDirectMessage && isThreadReply ? threadTs : void 0);
|
|
3205
|
-
const bindingRoute = resolveSlackConversationBindingRoute({
|
|
3206
|
-
cfg: ctx.cfg,
|
|
3207
|
-
route,
|
|
3208
|
-
accountId: account.accountId,
|
|
3209
|
-
baseConversationId,
|
|
3210
|
-
runtimeBindingThreadId,
|
|
3211
|
-
bindingsEnabled: !eventScope
|
|
3212
|
-
});
|
|
3213
|
-
const runtimeRoute = bindingRoute.runtimeRoute;
|
|
3214
|
-
const configuredBinding = bindingRoute.configuredRoute?.bindingResolution ?? null;
|
|
3215
|
-
const configuredBindingSessionKey = bindingRoute.configuredRoute?.boundSessionKey ?? "";
|
|
3216
|
-
route = bindingRoute.route;
|
|
3217
|
-
const threadKeys = runtimeRoute.boundSessionKey || configuredBindingSessionKey ? {
|
|
3218
|
-
sessionKey: route.sessionKey,
|
|
3219
|
-
parentSessionKey: void 0
|
|
3220
|
-
} : resolveThreadSessionKeys({
|
|
3221
|
-
baseSessionKey: route.sessionKey,
|
|
3222
|
-
threadId: routedThreadId,
|
|
3223
|
-
parentSessionKey: routedThreadId && ctx.threadInheritParent ? route.sessionKey : void 0
|
|
3224
|
-
});
|
|
3225
|
-
const sessionKey = threadKeys.sessionKey;
|
|
3226
|
-
const historyKey = isThreadReply && ctx.threadHistoryScope === "thread" ? sessionKey : eventScope ? `${account.accountId}:${eventScope.teamId}:${message.channel}` : message.channel;
|
|
3227
|
-
return {
|
|
3228
|
-
route,
|
|
3229
|
-
runtimeBinding: runtimeRoute.bindingRecord,
|
|
3230
|
-
runtimeBoundSessionKey: runtimeRoute.boundSessionKey,
|
|
3231
|
-
configuredBinding,
|
|
3232
|
-
configuredBindingSessionKey,
|
|
3233
|
-
chatType,
|
|
3234
|
-
replyToMode,
|
|
3235
|
-
threadContext,
|
|
3236
|
-
threadTs,
|
|
3237
|
-
isThreadReply,
|
|
3238
|
-
threadKeys,
|
|
3239
|
-
sessionKey,
|
|
3240
|
-
historyKey
|
|
3241
|
-
};
|
|
3242
|
-
}
|
|
3243
|
-
//#endregion
|
|
3244
2954
|
//#region extensions/slack/src/monitor/message-handler/prepare-thread-context-root.ts
|
|
3245
2955
|
function isSlackThreadAuthorCurrentBot(params) {
|
|
3246
2956
|
const { identity, author } = params;
|
|
@@ -3296,7 +3006,7 @@ function formatSlackThreadLabelSnippet(text) {
|
|
|
3296
3006
|
}
|
|
3297
3007
|
//#endregion
|
|
3298
3008
|
//#region extensions/slack/src/monitor/message-handler/prepare-thread-context.ts
|
|
3299
|
-
const loadSlackMediaModule = createLazyRuntimeModule(() => import("./actions-
|
|
3009
|
+
const loadSlackMediaModule = createLazyRuntimeModule(() => import("./actions-CFkvEv1d.js").then((n) => n._));
|
|
3300
3010
|
const SLACK_THREAD_CONTEXT_USER_LOOKUP_CONCURRENCY = 4;
|
|
3301
3011
|
function resolveSlackThreadSessionFreshness(params) {
|
|
3302
3012
|
return params.ctx.channelRuntime?.session?.resolveEntryResetFreshness?.({
|
|
@@ -3408,14 +3118,24 @@ async function resolveSlackThreadContextData(params) {
|
|
|
3408
3118
|
const snippet = formatSlackThreadLabelSnippet(starter.text);
|
|
3409
3119
|
threadLabel = `Slack thread ${params.roomLabel}${snippet ? `: ${snippet}` : ""}`;
|
|
3410
3120
|
if (shouldSeedInitialThreadContext && !params.effectiveDirectMedia && starter.files && starter.files.length > 0) {
|
|
3411
|
-
const {
|
|
3412
|
-
|
|
3121
|
+
const { resolveSlackAttachmentContent } = await loadSlackMediaModule();
|
|
3122
|
+
const attachmentContent = await resolveSlackAttachmentContent({
|
|
3413
3123
|
files: starter.files,
|
|
3414
3124
|
client: params.eventScope?.client ?? params.ctx.app.client,
|
|
3415
3125
|
token: params.ctx.botToken,
|
|
3416
3126
|
maxBytes: params.ctx.mediaMaxBytes
|
|
3417
3127
|
});
|
|
3418
|
-
|
|
3128
|
+
threadStarterMedia = attachmentContent?.media.length ? attachmentContent.media : null;
|
|
3129
|
+
if (attachmentContent) threadStarterBody = formatSlackUnavailableMedia({
|
|
3130
|
+
body: threadStarterBody,
|
|
3131
|
+
files: attachmentContent.files,
|
|
3132
|
+
unavailableMediaCount: attachmentContent.unavailableMediaCount,
|
|
3133
|
+
prependUnavailable: true
|
|
3134
|
+
});
|
|
3135
|
+
if (threadStarterMedia) {
|
|
3136
|
+
const starterPlaceholders = threadStarterMedia.map((item) => item.placeholder).join(", ");
|
|
3137
|
+
logVerbose(`slack: hydrated thread starter file ${starterPlaceholders} from root message`);
|
|
3138
|
+
}
|
|
3419
3139
|
}
|
|
3420
3140
|
} else threadLabel = `Slack thread ${params.roomLabel}`;
|
|
3421
3141
|
const includeBotStarterAsRootContext = shouldIncludeBotThreadStarterContext({
|
|
@@ -3435,14 +3155,20 @@ async function resolveSlackThreadContextData(params) {
|
|
|
3435
3155
|
const threadInitialHistoryLimit = params.account.config?.thread?.initialHistoryLimit ?? 20;
|
|
3436
3156
|
if (threadInitialHistoryLimit > 0 && shouldLoadInitialThreadHistory) {
|
|
3437
3157
|
const currentBotRootTs = starter?.ts ?? params.threadTs;
|
|
3158
|
+
const threadHistory = await resolveSlackThreadHistory({
|
|
3159
|
+
channelId: params.message.channel,
|
|
3160
|
+
threadTs: params.threadTs,
|
|
3161
|
+
client: params.eventScope?.client ?? params.ctx.app.client,
|
|
3162
|
+
currentMessageTs: params.message.ts,
|
|
3163
|
+
limit: threadInitialHistoryLimit
|
|
3164
|
+
});
|
|
3165
|
+
const enrichedStarter = starter && threadStarterBody && threadStarterBody !== starter.text ? {
|
|
3166
|
+
...starter,
|
|
3167
|
+
text: threadStarterBody,
|
|
3168
|
+
ts: currentBotRootTs
|
|
3169
|
+
} : null;
|
|
3438
3170
|
const threadHistoryWithBotRoot = ensureSlackThreadHistoryHasBotRoot({
|
|
3439
|
-
history:
|
|
3440
|
-
channelId: params.message.channel,
|
|
3441
|
-
threadTs: params.threadTs,
|
|
3442
|
-
client: params.eventScope?.client ?? params.ctx.app.client,
|
|
3443
|
-
currentMessageTs: params.message.ts,
|
|
3444
|
-
limit: threadInitialHistoryLimit
|
|
3445
|
-
}),
|
|
3171
|
+
history: enrichedStarter && !threadHistory.some((entry) => entry.ts === currentBotRootTs) ? [enrichedStarter, ...threadHistory.length >= threadInitialHistoryLimit ? threadHistory.slice(1) : threadHistory] : threadHistory,
|
|
3446
3172
|
includeBotStarterAsRootContext,
|
|
3447
3173
|
threadStarter: starter ? {
|
|
3448
3174
|
...starter,
|
|
@@ -3501,7 +3227,7 @@ async function resolveSlackThreadContextData(params) {
|
|
|
3501
3227
|
});
|
|
3502
3228
|
const role = isCurrentBot || isOtherBot || Boolean(historyMsg.botId) ? "assistant" : "user";
|
|
3503
3229
|
const msgSenderName = isCurrentBot ? "Bot (this assistant)" : msgUser?.name ?? (historyMsg.botId ? `Bot (${historyMsg.botId})` : "Unknown");
|
|
3504
|
-
const msgWithId = `${historyMsg.text}\n[slack message id: ${historyMsg.ts ?? "unknown"} channel: ${params.message.channel}]`;
|
|
3230
|
+
const msgWithId = `${historyMsg.ts === currentBotRootTs && threadStarterBody ? threadStarterBody : historyMsg.text}\n[slack message id: ${historyMsg.ts ?? "unknown"} channel: ${params.message.channel}]`;
|
|
3505
3231
|
historyParts.push(formatInboundEnvelope({
|
|
3506
3232
|
channel: "Slack",
|
|
3507
3233
|
from: `${msgSenderName} (${role})`,
|
|
@@ -3528,7 +3254,7 @@ async function resolveSlackThreadContextData(params) {
|
|
|
3528
3254
|
//#endregion
|
|
3529
3255
|
//#region extensions/slack/src/monitor/message-handler/subteam-mentions.ts
|
|
3530
3256
|
const SUBTEAM_MENTION_RE = /<!subteam\^([A-Z0-9]+)(?:\|[^>]*)?>/gi;
|
|
3531
|
-
const SUBTEAM_MEMBER_CACHE_TTL_MS =
|
|
3257
|
+
const SUBTEAM_MEMBER_CACHE_TTL_MS = 3e5;
|
|
3532
3258
|
const subteamMemberCache = /* @__PURE__ */ new WeakMap();
|
|
3533
3259
|
function normalizeSlackId(value) {
|
|
3534
3260
|
return typeof value === "string" && value.trim() ? value.trim().toUpperCase() : void 0;
|
|
@@ -3599,7 +3325,7 @@ const SLACK_USER_MENTION_RE = /<@([^>|]+)(?:\|[^>]+)?>/g;
|
|
|
3599
3325
|
const SLACK_SUBTEAM_MENTION_RE = /<!subteam\^([^>|]+)(?:\|[^>]+)?>/g;
|
|
3600
3326
|
const SLACK_SUBTEAM_MENTION_MARKER = "<!subteam^";
|
|
3601
3327
|
const SLACK_HISTORY_MEDIA_MAX_ATTACHMENTS = 4;
|
|
3602
|
-
const SLACK_HISTORY_MEDIA_MAX_BYTES =
|
|
3328
|
+
const SLACK_HISTORY_MEDIA_MAX_BYTES = 10485760;
|
|
3603
3329
|
const SLACK_HISTORY_MEDIA_IDLE_TIMEOUT_MS = 1e3;
|
|
3604
3330
|
const SLACK_HISTORY_MEDIA_TOTAL_TIMEOUT_MS = 3e3;
|
|
3605
3331
|
const SLACK_CHANNEL_ACCESS_DOCS_URL = "https://docs.openclaw.ai/channels/slack#access-control-and-routing";
|
|
@@ -3653,25 +3379,12 @@ async function restoreSlackAssistantThreadContextFromMetadata(params) {
|
|
|
3653
3379
|
const parentUserId = params.message.parent_user_id?.trim();
|
|
3654
3380
|
if (!params.message.channel || !threadTs || !parentUserId || parentUserId !== params.ctx.botUserId && parentUserId !== params.ctx.botId) return;
|
|
3655
3381
|
try {
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
limit: 4
|
|
3382
|
+
return await readSlackAssistantThreadContext({
|
|
3383
|
+
client: params.eventScope?.client ?? params.ctx.app.client,
|
|
3384
|
+
channelId: params.message.channel,
|
|
3385
|
+
threadTs,
|
|
3386
|
+
userId: params.message.user
|
|
3662
3387
|
});
|
|
3663
|
-
for (const message of response.messages ?? []) {
|
|
3664
|
-
const context = parseSlackAssistantThreadMetadata(message.metadata);
|
|
3665
|
-
if (!context) continue;
|
|
3666
|
-
return {
|
|
3667
|
-
assistantChannelId: params.message.channel,
|
|
3668
|
-
threadTs,
|
|
3669
|
-
userId: params.message.user,
|
|
3670
|
-
channelId: context.channelId,
|
|
3671
|
-
teamId: context.teamId,
|
|
3672
|
-
enterpriseId: context.enterpriseId
|
|
3673
|
-
};
|
|
3674
|
-
}
|
|
3675
3388
|
} catch (err) {
|
|
3676
3389
|
logVerbose(`slack assistant context restore failed channel=${params.message.channel} ts=${threadTs}: ${formatErrorMessage(err)}`);
|
|
3677
3390
|
}
|
|
@@ -3733,7 +3446,7 @@ function buildSlackHistoryMediaCandidateMessage(message) {
|
|
|
3733
3446
|
async function resolveSlackHistoryMediaForPendingRecord(params) {
|
|
3734
3447
|
const mediaMessage = buildSlackHistoryMediaCandidateMessage(params.message);
|
|
3735
3448
|
if (!mediaMessage) return [];
|
|
3736
|
-
|
|
3449
|
+
const content = await resolveSlackMessageContent({
|
|
3737
3450
|
message: mediaMessage,
|
|
3738
3451
|
isThreadReply: params.isThreadReply,
|
|
3739
3452
|
threadStarter: params.threadStarter,
|
|
@@ -3743,7 +3456,8 @@ async function resolveSlackHistoryMediaForPendingRecord(params) {
|
|
|
3743
3456
|
mediaMaxBytes: Math.min(params.ctx.mediaMaxBytes, SLACK_HISTORY_MEDIA_MAX_BYTES),
|
|
3744
3457
|
mediaReadIdleTimeoutMs: SLACK_HISTORY_MEDIA_IDLE_TIMEOUT_MS,
|
|
3745
3458
|
mediaTotalTimeoutMs: SLACK_HISTORY_MEDIA_TOTAL_TIMEOUT_MS
|
|
3746
|
-
})
|
|
3459
|
+
});
|
|
3460
|
+
return await toInboundMediaFactsWithMetadata(content?.effectiveDirectMedia, {
|
|
3747
3461
|
kind: "image",
|
|
3748
3462
|
messageId: params.message.ts
|
|
3749
3463
|
});
|
|
@@ -3839,24 +3553,15 @@ async function resolveSlackConversationContext(params) {
|
|
|
3839
3553
|
};
|
|
3840
3554
|
}
|
|
3841
3555
|
async function authorizeSlackInboundMessage(params) {
|
|
3842
|
-
const { ctx, account, message, conversation } = params;
|
|
3556
|
+
const { ctx, account, message, conversation, drop } = params;
|
|
3843
3557
|
const { isDirectMessage, channelName, resolvedChannelType, isBotMessage, allowBotsMode } = conversation;
|
|
3844
3558
|
if (isBotMessage) {
|
|
3845
3559
|
if (message.user && ctx.botUserId && message.user === ctx.botUserId) return null;
|
|
3846
|
-
if (allowBotsMode === "off")
|
|
3847
|
-
logVerbose(`slack: drop bot message ${message.bot_id ?? "unknown"} (allowBots=false)`);
|
|
3848
|
-
return null;
|
|
3849
|
-
}
|
|
3850
|
-
}
|
|
3851
|
-
if (isDirectMessage && !message.user) {
|
|
3852
|
-
logVerbose("slack: drop dm message (missing user id)");
|
|
3853
|
-
return null;
|
|
3560
|
+
if (allowBotsMode === "off") return drop("bot-disabled");
|
|
3854
3561
|
}
|
|
3562
|
+
if (isDirectMessage && !message.user) return drop("missing-user");
|
|
3855
3563
|
const senderId = message.user ?? (isBotMessage ? message.bot_id : void 0);
|
|
3856
|
-
if (!senderId)
|
|
3857
|
-
logVerbose("slack: drop message (missing sender id)");
|
|
3858
|
-
return null;
|
|
3859
|
-
}
|
|
3564
|
+
if (!senderId) return drop("missing-sender");
|
|
3860
3565
|
if (!ctx.isChannelAllowed({
|
|
3861
3566
|
teamId: params.eventScope?.teamId ?? ctx.teamId,
|
|
3862
3567
|
channelId: message.channel,
|
|
@@ -3866,7 +3571,8 @@ async function authorizeSlackInboundMessage(params) {
|
|
|
3866
3571
|
if (conversation.isRoom && ctx.groupPolicy === "allowlist" && params.explicitBotMention && !isBotMessage && message.user) {
|
|
3867
3572
|
let subject = "This OpenClaw bot";
|
|
3868
3573
|
if (ctx.botUserId) try {
|
|
3869
|
-
const
|
|
3574
|
+
const botIdentity = await ctx.resolveUserName(ctx.botUserId, params.eventScope);
|
|
3575
|
+
const botName = normalizeOptionalString(botIdentity?.name);
|
|
3870
3576
|
if (botName) subject = escapeSlackMrkdwn(botName);
|
|
3871
3577
|
} catch (error) {
|
|
3872
3578
|
logVerbose(`slack allowlist denial bot-name lookup failed: ${formatSlackError(error)}`);
|
|
@@ -3883,8 +3589,7 @@ async function authorizeSlackInboundMessage(params) {
|
|
|
3883
3589
|
ctx.runtime.error?.(`slack allowlist denial notice failed for channel ${message.channel}: ${formatSlackError(error)}`);
|
|
3884
3590
|
}
|
|
3885
3591
|
}
|
|
3886
|
-
|
|
3887
|
-
return null;
|
|
3592
|
+
return drop("channel-not-allowed");
|
|
3888
3593
|
}
|
|
3889
3594
|
const allowFromLower = await resolveSlackEffectiveAllowFrom(ctx, {
|
|
3890
3595
|
includePairingStore: isDirectMessage,
|
|
@@ -3892,10 +3597,8 @@ async function authorizeSlackInboundMessage(params) {
|
|
|
3892
3597
|
});
|
|
3893
3598
|
if (isDirectMessage) {
|
|
3894
3599
|
const directUserId = message.user;
|
|
3895
|
-
if (!directUserId)
|
|
3896
|
-
|
|
3897
|
-
return null;
|
|
3898
|
-
}
|
|
3600
|
+
if (!directUserId) return drop("missing-user");
|
|
3601
|
+
let dropReason = "dm-unauthorized";
|
|
3899
3602
|
if (!await authorizeSlackDirectMessage({
|
|
3900
3603
|
ctx,
|
|
3901
3604
|
accountId: account.accountId,
|
|
@@ -3913,13 +3616,13 @@ async function authorizeSlackInboundMessage(params) {
|
|
|
3913
3616
|
});
|
|
3914
3617
|
},
|
|
3915
3618
|
onDisabled: () => {
|
|
3916
|
-
|
|
3619
|
+
dropReason = "dm-disabled";
|
|
3917
3620
|
},
|
|
3918
3621
|
onUnauthorized: ({ allowMatchMeta }) => {
|
|
3919
3622
|
logVerbose(`Blocked unauthorized slack sender ${message.user} (dmPolicy=${ctx.dmPolicy}, ${allowMatchMeta})`);
|
|
3920
3623
|
},
|
|
3921
3624
|
log: logVerbose
|
|
3922
|
-
})) return
|
|
3625
|
+
})) return drop(dropReason);
|
|
3923
3626
|
}
|
|
3924
3627
|
return {
|
|
3925
3628
|
senderId,
|
|
@@ -3928,6 +3631,19 @@ async function authorizeSlackInboundMessage(params) {
|
|
|
3928
3631
|
}
|
|
3929
3632
|
async function prepareSlackMessage(params) {
|
|
3930
3633
|
const { ctx, account, message, opts } = params;
|
|
3634
|
+
const drop = (reason, parentUserId) => {
|
|
3635
|
+
ctx.logger.info({
|
|
3636
|
+
provider: "slack",
|
|
3637
|
+
accountId: account.accountId,
|
|
3638
|
+
teamId: opts.eventScope?.teamId ?? ctx.teamId,
|
|
3639
|
+
channelId: message.channel,
|
|
3640
|
+
messageTs: message.ts,
|
|
3641
|
+
source: opts.source,
|
|
3642
|
+
reason,
|
|
3643
|
+
...parentUserId ? { parentUserId } : {}
|
|
3644
|
+
}, "Slack inbound event rejected during preparation");
|
|
3645
|
+
return null;
|
|
3646
|
+
};
|
|
3931
3647
|
const slackClient = opts.eventScope?.client ?? ctx.app.client;
|
|
3932
3648
|
const threadStarterWorkspaceScope = {
|
|
3933
3649
|
accountId: account.accountId,
|
|
@@ -3951,7 +3667,8 @@ async function prepareSlackMessage(params) {
|
|
|
3951
3667
|
conversation,
|
|
3952
3668
|
explicitBotMention: opts.source === "app_mention" || Boolean(normalizedBotUserId && mentionMetadata.mentionedUserIds.includes(normalizedBotUserId)),
|
|
3953
3669
|
eventScope: opts.eventScope,
|
|
3954
|
-
onVisibleDrop: opts.onVisibleDrop
|
|
3670
|
+
onVisibleDrop: opts.onVisibleDrop,
|
|
3671
|
+
drop
|
|
3955
3672
|
});
|
|
3956
3673
|
if (!authorization) return null;
|
|
3957
3674
|
const { senderId, allowFromLower } = authorization;
|
|
@@ -3959,7 +3676,8 @@ async function prepareSlackMessage(params) {
|
|
|
3959
3676
|
const resolveSenderName = async () => {
|
|
3960
3677
|
if (resolvedSenderName) return resolvedSenderName;
|
|
3961
3678
|
if (message.user) {
|
|
3962
|
-
const
|
|
3679
|
+
const sender = await ctx.resolveUserName(message.user, opts.eventScope);
|
|
3680
|
+
const normalized = normalizeOptionalString(sender?.name);
|
|
3963
3681
|
if (normalized) {
|
|
3964
3682
|
resolvedSenderName = normalized;
|
|
3965
3683
|
return resolvedSenderName;
|
|
@@ -4055,13 +3773,7 @@ async function prepareSlackMessage(params) {
|
|
|
4055
3773
|
if (shouldLogVerbose()) logVerbose(`slack: using configured ACP binding for ${configuredBinding.record.conversation.conversationId} -> ${configuredBindingSessionKey}`);
|
|
4056
3774
|
} else {
|
|
4057
3775
|
if (shouldLogVerbose()) logVerbose(`slack: configured ACP binding unavailable for ${configuredBinding.record.conversation.conversationId}: ${ensured.error}`);
|
|
4058
|
-
|
|
4059
|
-
log: logVerbose,
|
|
4060
|
-
channel: "slack",
|
|
4061
|
-
reason: "configured ACP binding unavailable",
|
|
4062
|
-
target: configuredBinding.record.conversation.conversationId
|
|
4063
|
-
});
|
|
4064
|
-
return null;
|
|
3776
|
+
return drop("configured-binding-unavailable");
|
|
4065
3777
|
}
|
|
4066
3778
|
}
|
|
4067
3779
|
const senderNameForAuthPromise = ctx.allowNameMatching ? resolveSenderName().then((name) => ({
|
|
@@ -4176,14 +3888,7 @@ async function prepareSlackMessage(params) {
|
|
|
4176
3888
|
channel: "slack",
|
|
4177
3889
|
accountId: account.accountId
|
|
4178
3890
|
});
|
|
4179
|
-
if (message["_ambiguousThreadReply"])
|
|
4180
|
-
ctx.logger.info({
|
|
4181
|
-
channel: message.channel,
|
|
4182
|
-
ts: message.ts,
|
|
4183
|
-
parentUserId: message.parent_user_id
|
|
4184
|
-
}, "skipping ambiguous slack thread reply");
|
|
4185
|
-
return null;
|
|
4186
|
-
}
|
|
3891
|
+
if (message["_ambiguousThreadReply"]) return drop("ambiguous-thread", message.parent_user_id);
|
|
4187
3892
|
let canDetectMention = Boolean(ctx.botUserId) || mentionRegexes.length > 0;
|
|
4188
3893
|
const textForCommandDetection = stripSlackMentionsForCommandDetection(message.text ?? "");
|
|
4189
3894
|
const hasControlCommandInMessage = hasControlCommand(textForCommandDetection, cfg);
|
|
@@ -4216,10 +3921,7 @@ async function prepareSlackMessage(params) {
|
|
|
4216
3921
|
});
|
|
4217
3922
|
let messageIngress = await resolveMessageIngress();
|
|
4218
3923
|
const senderGate = messageIngress.senderAccess.gate;
|
|
4219
|
-
if (isRoomish && senderGate?.allowed === false)
|
|
4220
|
-
logVerbose(`Blocked unauthorized slack sender ${senderId} (not in sender allowlist)`);
|
|
4221
|
-
return null;
|
|
4222
|
-
}
|
|
3924
|
+
if (isRoomish && senderGate?.allowed === false) return drop("unauthorized-sender");
|
|
4223
3925
|
if (isRoom && isBotMessage && allowBotsMode !== "off" && !await authorizeSlackBotRoomMessage({
|
|
4224
3926
|
ctx,
|
|
4225
3927
|
channelId: message.channel,
|
|
@@ -4228,7 +3930,7 @@ async function prepareSlackMessage(params) {
|
|
|
4228
3930
|
channelUsers: channelConfig?.users,
|
|
4229
3931
|
allowFromLower,
|
|
4230
3932
|
eventScope: opts.eventScope
|
|
4231
|
-
})) return
|
|
3933
|
+
})) return drop("unauthorized-bot");
|
|
4232
3934
|
const threadContextAllowFromLower = isRoom ? channelUsersAllowlistConfigured ? normalizeAllowListLower(channelConfig?.users) : [] : isDirectMessage ? allowFromLower : [];
|
|
4233
3935
|
const contextVisibilityMode = resolveChannelContextVisibilityMode({
|
|
4234
3936
|
cfg: ctx.cfg,
|
|
@@ -4248,15 +3950,7 @@ async function prepareSlackMessage(params) {
|
|
|
4248
3950
|
explicitKind: true
|
|
4249
3951
|
});
|
|
4250
3952
|
const commandAuthorized = messageIngress.commandAccess.authorized;
|
|
4251
|
-
if (isRoomish && messageIngress.commandAccess.shouldBlockControlCommand)
|
|
4252
|
-
logInboundDrop({
|
|
4253
|
-
log: logVerbose,
|
|
4254
|
-
channel: "slack",
|
|
4255
|
-
reason: "control command (unauthorized)",
|
|
4256
|
-
target: senderId
|
|
4257
|
-
});
|
|
4258
|
-
return null;
|
|
4259
|
-
}
|
|
3953
|
+
if (isRoomish && messageIngress.commandAccess.shouldBlockControlCommand) return drop("control-command-unauthorized");
|
|
4260
3954
|
const canSeedMentionedRoomThread = !seedTopLevelRoomThreadBySource && isRoom && !routing.isThreadReply && !hasBoundSession;
|
|
4261
3955
|
let seededMentionRouting;
|
|
4262
3956
|
const getSeededMentionRouting = () => {
|
|
@@ -4351,37 +4045,20 @@ async function prepareSlackMessage(params) {
|
|
|
4351
4045
|
wasMentioned
|
|
4352
4046
|
});
|
|
4353
4047
|
if (isBotMessage && allowBotsMode === "mentions") {
|
|
4354
|
-
if (!(isDirectMessage || effectiveWasMentioned || shouldBypassMention))
|
|
4355
|
-
logVerbose("slack: drop bot message (allowBots=mentions, missing mention)");
|
|
4356
|
-
return null;
|
|
4357
|
-
}
|
|
4048
|
+
if (!(isDirectMessage || effectiveWasMentioned || shouldBypassMention)) return drop("bot-missing-mention");
|
|
4358
4049
|
}
|
|
4359
4050
|
if (isRoom && shouldRequireMention && !canDetectMention && !effectiveWasMentioned) {
|
|
4360
|
-
ctx.logger.info({
|
|
4361
|
-
channel: message.channel,
|
|
4362
|
-
reason: "mention-detection-unavailable"
|
|
4363
|
-
}, "skipping channel message");
|
|
4364
4051
|
await recordDroppedHistory("slack-mention-detection-unavailable");
|
|
4365
|
-
return
|
|
4052
|
+
return drop("mention-detection-unavailable");
|
|
4366
4053
|
}
|
|
4367
4054
|
const ignoreOtherMentions = channelConfig?.ignoreOtherMentions ?? false;
|
|
4368
4055
|
if (isRoom && ignoreOtherMentions && Boolean(ctx.botUserId) && hasAnyMention && !wasMentioned) {
|
|
4369
|
-
logInboundDrop({
|
|
4370
|
-
log: logVerbose,
|
|
4371
|
-
channel: "slack",
|
|
4372
|
-
reason: "other-mention",
|
|
4373
|
-
target: senderId
|
|
4374
|
-
});
|
|
4375
4056
|
await recordDroppedHistory("slack-other-mention");
|
|
4376
|
-
return
|
|
4057
|
+
return drop("other-mention");
|
|
4377
4058
|
}
|
|
4378
4059
|
if (isRoom && shouldRequireMention && mentionDecision.shouldSkip) {
|
|
4379
|
-
ctx.logger.info({
|
|
4380
|
-
channel: message.channel,
|
|
4381
|
-
reason: "no-mention"
|
|
4382
|
-
}, "skipping channel message");
|
|
4383
4060
|
await recordDroppedHistory("slack-no-mention");
|
|
4384
|
-
return
|
|
4061
|
+
return drop("missing-mention");
|
|
4385
4062
|
}
|
|
4386
4063
|
const chatType = resolveSlackChatType(conversation.resolvedChannelType);
|
|
4387
4064
|
const inboundEventKind = classifyChannelInboundEvent({
|
|
@@ -4396,7 +4073,7 @@ async function prepareSlackMessage(params) {
|
|
|
4396
4073
|
});
|
|
4397
4074
|
const threadStarter = await getThreadStarter();
|
|
4398
4075
|
const resolvedMessageContent = await getMessageContent();
|
|
4399
|
-
if (!resolvedMessageContent) return
|
|
4076
|
+
if (!resolvedMessageContent) return drop("empty-content");
|
|
4400
4077
|
const { rawBody, effectiveDirectMedia } = resolvedMessageContent;
|
|
4401
4078
|
const bodyForAgent = preflightAudioTranscript ? formatSlackAudioTranscriptForAgent({
|
|
4402
4079
|
transcript: preflightAudioTranscript,
|
|
@@ -4417,7 +4094,7 @@ async function prepareSlackMessage(params) {
|
|
|
4417
4094
|
const statusReactionsExplicitlyEnabled = cfg.messages?.statusReactions?.enabled === true;
|
|
4418
4095
|
const isRoomEvent = inboundEventKind === "room_event";
|
|
4419
4096
|
const shouldAckReaction$1 = () => Boolean(ackReaction && shouldAckReaction({
|
|
4420
|
-
scope:
|
|
4097
|
+
scope: cfg.messages?.ackReactionScope,
|
|
4421
4098
|
inboundEventKind,
|
|
4422
4099
|
isDirect: isDirectMessage,
|
|
4423
4100
|
isGroup: isRoomish,
|
|
@@ -4465,10 +4142,11 @@ async function prepareSlackMessage(params) {
|
|
|
4465
4142
|
const textWithId = `${bodyForAgent}\n[slack message id: ${message.ts} channel: ${message.channel}${threadInfo}]`;
|
|
4466
4143
|
const storePath = resolveStorePath(ctx.cfg.session?.store, { agentId: route.agentId });
|
|
4467
4144
|
const envelopeOptions = resolveEnvelopeFormatOptions(ctx.cfg);
|
|
4468
|
-
const
|
|
4145
|
+
const sessionEntry = getSessionEntry({
|
|
4469
4146
|
storePath,
|
|
4470
4147
|
sessionKey
|
|
4471
4148
|
});
|
|
4149
|
+
const previousTimestamp = sessionEntry?.updatedAt;
|
|
4472
4150
|
if (opts.source === "app_mention" && !ctx.botUserId && message.ts) {
|
|
4473
4151
|
const pendingHistory = ctx.channelHistories.get(historyKey);
|
|
4474
4152
|
if (pendingHistory) ctx.channelHistories.set(historyKey, pendingHistory.filter((entry) => entry.messageId !== message.ts));
|
|
@@ -4559,10 +4237,7 @@ async function prepareSlackMessage(params) {
|
|
|
4559
4237
|
messageId: threadContext.messageTs,
|
|
4560
4238
|
inboundEventKind
|
|
4561
4239
|
}, boundMessageThreadId);
|
|
4562
|
-
if (messageIngress.ingress.admission !== "dispatch")
|
|
4563
|
-
logVerbose(`Blocked slack sender ${senderId} after final route binding`);
|
|
4564
|
-
return null;
|
|
4565
|
-
}
|
|
4240
|
+
if (messageIngress.ingress.admission !== "dispatch") return drop("final-route-denied");
|
|
4566
4241
|
const agentContextEntities = isAgentViewMessage ? normalizeSlackAppContextEntities(message.app_context) : [];
|
|
4567
4242
|
const ctxPayload = (ctx.buildContext ?? buildChannelInboundEventContext)({
|
|
4568
4243
|
channelIngress: messageIngress,
|
|
@@ -4647,6 +4322,7 @@ async function prepareSlackMessage(params) {
|
|
|
4647
4322
|
payload: { entities: agentContextEntities }
|
|
4648
4323
|
}] : void 0,
|
|
4649
4324
|
TransportThreadId: directThreadRoutedToDmSession ? threadContext.messageThreadId : void 0,
|
|
4325
|
+
ReplyThreading: isThreadReply ? { implicitCurrentMessage: "deny" } : void 0,
|
|
4650
4326
|
SlackAssistantThread: assistantThreadContext ? true : void 0,
|
|
4651
4327
|
SlackAgentThread: agentViewThreadTs ? true : void 0,
|
|
4652
4328
|
SlackAssistantThreadContextChannelId: assistantThreadContext?.channelId,
|
|
@@ -4682,7 +4358,6 @@ async function prepareSlackMessage(params) {
|
|
|
4682
4358
|
normalizeEntry: normalizeSlackAllowOwnerEntry
|
|
4683
4359
|
}) : null;
|
|
4684
4360
|
const replyTarget = `channel:${message.channel}`;
|
|
4685
|
-
if (!replyTarget) return null;
|
|
4686
4361
|
if (preflightAudioTranscript) await sendSlackPreflightAudioTranscriptEcho({
|
|
4687
4362
|
transcript: preflightAudioTranscript,
|
|
4688
4363
|
cfg,
|
|
@@ -4705,6 +4380,7 @@ async function prepareSlackMessage(params) {
|
|
|
4705
4380
|
channelConfig,
|
|
4706
4381
|
replyTarget,
|
|
4707
4382
|
ctxPayload,
|
|
4383
|
+
sessionDisplayName: sessionEntry?.displayName,
|
|
4708
4384
|
turn: {
|
|
4709
4385
|
storePath,
|
|
4710
4386
|
record: {
|