@openclaw/slack 2026.9.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/{action-runtime-DND4Pyp2.js → action-runtime-DftQWpI4.js} +4 -4
- package/dist/action-runtime.runtime-rHI3UAh4.js +2 -0
- package/dist/{actions-fh66A6lc.js → actions-CFkvEv1d.js} +3 -3
- package/dist/api.js +9 -9
- package/dist/{approval-handler.runtime-D7d8sKM0.js → approval-handler.runtime-BeILKdw4.js} +31 -15
- package/dist/{channel-4m8IAdMD.js → channel-CmOuxbOD.js} +24 -23
- package/dist/channel-config-api.js +1 -1
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel-type-3-SqEMVc.js → channel-type-BWt2CRaI.js} +2 -2
- package/dist/{channel.setup-t_ghLGb_.js → channel.setup-D5z1ReaT.js} +8 -3
- package/dist/{config-schema-bP3eV2ra.js → config-schema-JDx_f9Zb.js} +2 -2
- package/dist/{directory-live-q32AW7N1.js → directory-live-BuBPHmju.js} +1 -1
- package/dist/{monitor-Chwu_SiZ.js → monitor-DI07pfJl.js} +3 -3
- package/dist/{outbound-adapter-BB8wAjm5.js → outbound-adapter-B5QyTRpJ.js} +3 -2
- package/dist/{pipeline.runtime-BklS_NEj.js → pipeline.runtime-BdIwZgm4.js} +567 -733
- package/dist/{policy-D7PKkiet.js → policy-Bh5TOqII.js} +2 -2
- package/dist/{probe-gj5yQgN2.js → probe-Biy--yhL.js} +42 -10
- package/dist/{provider-Do50wMWn.js → provider-DoaUvAM4.js} +5583 -4921
- package/dist/{relay-source-DWuduiJ2.js → relay-source-D4uecp68.js} +1 -1
- package/dist/{replies-Dc6WVh_Q.js → replies-DENuTcPO.js} +4 -3
- package/dist/reply-blocks-PRkhyAN7.js +790 -0
- package/dist/{resolve-channels-CU7Bg5Ka.js → resolve-channels-DQGV2VrE.js} +1 -1
- package/dist/{resolve-users-DrhttvRQ.js → resolve-users-DRGt_Q4I.js} +1 -1
- package/dist/runtime-api.js +10 -10
- package/dist/{scopes-B9HRguDe.js → scopes-CZzeGdWV.js} +1 -1
- package/dist/{send-Djswy6lf.js → send-DElUcgIs.js} +10 -10
- package/dist/{send.runtime-__WVTWLQ.js → send.runtime-C8D6cKGm.js} +1 -1
- package/dist/{group-policy-BKjMqT-8.js → session-status-BynwcZSO.js} +169 -799
- package/dist/setup-plugin-api.js +1 -1
- package/dist/{setup-surface-DO0e-MMS.js → setup-surface-B1_gpGBl.js} +4 -4
- package/dist/{shared-DEQBEKBL.js → shared-CNQsWJT7.js} +2 -2
- package/dist/{slash-dispatch.runtime-C3B4h5ZM.js → slash-dispatch.runtime-DhsXavSY.js} +1 -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 +3 -3
- 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-CEhbmS-Z.js +0 -2
- package/dist/conversation-binding-route-Vee8Dgbf.js +0 -115
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
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
2
|
import { n as formatSlackTarget } from "./target-parsing-PbxGDbr9.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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
5
|
import { n as resolveSlackReplyThreadTs } from "./thread-ts-CukseRVL.js";
|
|
6
6
|
import { n as resolveSlackStreamingMode, t as resolveSlackNativeStreaming } from "./streaming-compat-B-e0mqM0.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-
|
|
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-
|
|
9
|
-
import {
|
|
10
|
-
import { n as resolveSlackChannelConfig } from "./policy-
|
|
11
|
-
import { a as resolveSlackThreadTs, i as readSlackReplyBlocks, n as deliverReplies, o as sanitizeSlackMonitorReplyPayload, s as emitSlackMessageSentHooks, t as createSlackReplyDeliveryPlan } from "./replies-
|
|
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 { createChannelMessageReplyPipeline, createChannelProgressDraftCompositor, createDraftStreamLoop, createFinalizableDraftStreamControlsForState, defineFinalizableLivePreviewAdapter, deliverWithFinalizableLivePreviewAdapter, formatChannelProgressDraftText, isChannelProgressDraftWorkToolName, resolveAgentOutboundIdentity, resolveChannelMessageSourceReplyDeliveryMode, resolveChannelProgressDraftConfig, resolveChannelProgressDraftMaxLineChars, resolveChannelStreamingBlockEnabled, resolveChannelStreamingPreviewToolProgress, resolveChannelStreamingSuppressDefaultToolProgressMessages } from "openclaw/plugin-sdk/channel-outbound";
|
|
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
18
|
import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
|
|
19
19
|
import { buildTtsSupplementMediaPayload, getReplyPayloadTtsSupplement, isReplyPayloadNonTerminalToolErrorWarning, resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
|
|
20
20
|
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
21
|
+
import { createHash } from "node:crypto";
|
|
21
22
|
import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
|
|
22
23
|
import { ensureConfiguredBindingRouteReady } from "openclaw/plugin-sdk/conversation-runtime";
|
|
23
24
|
import { resolveChannelImplicitMentions } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
@@ -28,11 +29,11 @@ import { filterSupplementalContextItems, resolvePinnedMainDmOwnerFromAllowlist,
|
|
|
28
29
|
import { mimeTypeFromFilePath } from "openclaw/plugin-sdk/media-mime";
|
|
29
30
|
import { createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
|
|
30
31
|
import { mergePairLoopGuardConfig } from "openclaw/plugin-sdk/pair-loop-guard-runtime";
|
|
31
|
-
import { enqueueRoutedSystemEvent } from "openclaw/plugin-sdk/system-event-runtime";
|
|
32
32
|
import { asDateTimestampMs, asFiniteNumberInRange, parseStrictFiniteNumber, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
|
|
33
|
+
import { resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
|
|
34
|
+
import { enqueueRoutedSystemEvent } from "openclaw/plugin-sdk/system-event-runtime";
|
|
33
35
|
import { getGlobalHookRunner } from "openclaw/plugin-sdk/plugin-runtime";
|
|
34
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";
|
|
35
|
-
import { resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
|
|
36
37
|
import { resolveGatewayPublicOrigin } from "openclaw/plugin-sdk/config-contracts";
|
|
37
38
|
import { buildControlUiSessionPath } from "openclaw/plugin-sdk/session-discussion";
|
|
38
39
|
import { createStatusReactionController, logAckFailure, logTypingFailure, resolveAckReaction, shouldAckReaction } from "openclaw/plugin-sdk/channel-feedback";
|
|
@@ -40,186 +41,6 @@ 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 ? { 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 ? { 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;
|
|
@@ -574,11 +395,65 @@ function createSlackDraftStream(params) {
|
|
|
574
395
|
};
|
|
575
396
|
}
|
|
576
397
|
//#endregion
|
|
398
|
+
//#region extensions/slack/src/stream-mode.ts
|
|
399
|
+
function resolveSlackStreamingConfig(params) {
|
|
400
|
+
return {
|
|
401
|
+
mode: resolveSlackStreamingMode(params),
|
|
402
|
+
nativeStreaming: resolveSlackNativeStreaming(params)
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
function applyAppendOnlyStreamUpdate(params) {
|
|
406
|
+
const incoming = params.incoming.trimEnd();
|
|
407
|
+
if (!incoming) return {
|
|
408
|
+
rendered: params.rendered,
|
|
409
|
+
source: params.source,
|
|
410
|
+
changed: false
|
|
411
|
+
};
|
|
412
|
+
if (!params.rendered) return {
|
|
413
|
+
rendered: incoming,
|
|
414
|
+
source: incoming,
|
|
415
|
+
changed: true
|
|
416
|
+
};
|
|
417
|
+
if (incoming === params.source) return {
|
|
418
|
+
rendered: params.rendered,
|
|
419
|
+
source: params.source,
|
|
420
|
+
changed: false
|
|
421
|
+
};
|
|
422
|
+
if (incoming.startsWith(params.rendered)) return {
|
|
423
|
+
rendered: incoming,
|
|
424
|
+
source: incoming,
|
|
425
|
+
changed: incoming !== params.rendered
|
|
426
|
+
};
|
|
427
|
+
if (incoming.startsWith(params.source)) {
|
|
428
|
+
const delta = incoming.slice(params.source.length);
|
|
429
|
+
return {
|
|
430
|
+
rendered: `${params.rendered}${delta}`,
|
|
431
|
+
source: incoming,
|
|
432
|
+
changed: delta.length > 0
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
if (params.source.startsWith(incoming)) return {
|
|
436
|
+
rendered: params.rendered,
|
|
437
|
+
source: params.source,
|
|
438
|
+
changed: false
|
|
439
|
+
};
|
|
440
|
+
const separator = params.separator ?? (params.rendered.endsWith("\n") ? "" : "\n");
|
|
441
|
+
return {
|
|
442
|
+
rendered: `${params.rendered}${separator}${incoming}`,
|
|
443
|
+
source: incoming,
|
|
444
|
+
changed: true
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
//#endregion
|
|
577
448
|
//#region extensions/slack/src/progress-blocks.ts
|
|
578
449
|
const SLACK_PROGRESS_FIELD_MAX = 1800;
|
|
579
450
|
const DEFAULT_SLACK_PROGRESS_DETAIL_MAX_CHARS = 120;
|
|
451
|
+
const DEFAULT_SLACK_PROGRESS_TASK_DETAIL_MAX_CHARS = 48;
|
|
580
452
|
const SLACK_PROGRESS_CHUNK_TEXT_MAX = 256;
|
|
581
453
|
const SLACK_PROGRESS_TASK_TITLE_MAX = 120;
|
|
454
|
+
const SLACK_PROGRESS_PLAN_FALLBACK_TITLE = "Thinking";
|
|
455
|
+
const SLACK_PROGRESS_LINE_DELTA_RE = /(?:^|\s)\+(\d+)\s+[−-](\d+)(?=\s|$)/u;
|
|
456
|
+
const SLACK_ATTENTION_TASK_PREFIX = "openclaw-attention-";
|
|
582
457
|
function buildSessionSources(url) {
|
|
583
458
|
return [{
|
|
584
459
|
type: "url_source",
|
|
@@ -586,6 +461,12 @@ function buildSessionSources(url) {
|
|
|
586
461
|
text: "Open in OpenClaw"
|
|
587
462
|
}];
|
|
588
463
|
}
|
|
464
|
+
function field(text) {
|
|
465
|
+
return {
|
|
466
|
+
type: "mrkdwn",
|
|
467
|
+
text: truncateSlackText(text, SLACK_PROGRESS_FIELD_MAX)
|
|
468
|
+
};
|
|
469
|
+
}
|
|
589
470
|
function resolveMaxLineChars(value, fallback) {
|
|
590
471
|
return value && value > 0 ? Math.floor(value) : fallback;
|
|
591
472
|
}
|
|
@@ -604,58 +485,148 @@ function compactTitle(value) {
|
|
|
604
485
|
function compactChunkText(value) {
|
|
605
486
|
return truncateSlackText(value.replace(/\s+/g, " ").trim(), SLACK_PROGRESS_CHUNK_TEXT_MAX);
|
|
606
487
|
}
|
|
607
|
-
function
|
|
608
|
-
return
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
const approval = lines.findLast((line) => line.kind === "approval" && line.status === "requested");
|
|
612
|
-
if (approval) return {
|
|
613
|
-
id: "openclaw_attention",
|
|
614
|
-
title: compactTitle(`Approval required: ${approval.detail || approval.label}`),
|
|
615
|
-
status: "pending"
|
|
616
|
-
};
|
|
617
|
-
const failure = lines.findLast((line) => {
|
|
618
|
-
const status = line.status?.trim().toLowerCase();
|
|
619
|
-
return status === "error" || status === "failed" || status === "failure" || status?.startsWith("exit ") && status !== "exit 0";
|
|
488
|
+
function renderProgressCardText(text, enclosingStyle) {
|
|
489
|
+
return normalizeSlackOutboundText(text, {
|
|
490
|
+
mentions: "escape",
|
|
491
|
+
enclosingStyle
|
|
620
492
|
});
|
|
621
|
-
if (!failure) return;
|
|
622
|
-
return {
|
|
623
|
-
id: "openclaw_attention",
|
|
624
|
-
title: compactTitle([...new Set([
|
|
625
|
-
failure.label,
|
|
626
|
-
failure.detail,
|
|
627
|
-
failure.status
|
|
628
|
-
].filter(Boolean))].join(" — ")),
|
|
629
|
-
status: "error"
|
|
630
|
-
};
|
|
631
493
|
}
|
|
632
|
-
function
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
494
|
+
function lineDetailParts(line) {
|
|
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));
|
|
496
|
+
}
|
|
497
|
+
function activityLineDetail(line, maxChars) {
|
|
498
|
+
const detail = lineDetailParts(line).join(" · ");
|
|
499
|
+
if (detail) return escapeSlackMrkdwn(compactDetail(detail, maxChars));
|
|
500
|
+
const text = line.text.replace(/^(?:🧠|💬)\s+/u, "").trim();
|
|
501
|
+
return line.kind === "item" && !line.toolName && text && text !== line.label ? renderProgressCardText(compactDetail(text, maxChars)) : "—";
|
|
502
|
+
}
|
|
503
|
+
function lineTaskTitle(line) {
|
|
504
|
+
const label = line.label.replace(/\s+/g, " ").trim() || line.toolName || line.kind || "Update";
|
|
505
|
+
const fallback = line.text.replace(/\s+/g, " ").trim();
|
|
506
|
+
if (fallback && fallback !== label) return compactTitle(lineDetailParts(line).length > 0 || line.status ? label : fallback);
|
|
507
|
+
return compactTitle(label);
|
|
508
|
+
}
|
|
509
|
+
function lineTaskDetails(line, maxLineChars) {
|
|
510
|
+
const detail = line.detail?.replace(SLACK_PROGRESS_LINE_DELTA_RE, "").replace(/\s+·\s*$/u, "").trim();
|
|
511
|
+
return detail && detail !== line.status?.trim() ? compactDetail(detail, maxLineChars) : void 0;
|
|
512
|
+
}
|
|
513
|
+
function lineTaskOutput(line) {
|
|
514
|
+
const match = line.detail ? SLACK_PROGRESS_LINE_DELTA_RE.exec(line.detail) : null;
|
|
515
|
+
if (match) return `+${match[1]} −${match[2]}`;
|
|
516
|
+
const status = line.status?.replace(/\s+/g, " ").trim();
|
|
517
|
+
return status && lineTaskStatus(line) === "error" ? status : void 0;
|
|
518
|
+
}
|
|
519
|
+
function lineTaskStatus(line) {
|
|
520
|
+
const normalized = line.status?.replace(/\s+/g, " ").trim().toLowerCase();
|
|
521
|
+
if (!normalized) return "in_progress";
|
|
522
|
+
if (normalized === "complete" || normalized === "completed" || normalized === "done" || normalized === "ok" || normalized === "success" || normalized === "succeeded" || normalized === "successful" || normalized === "exit 0") return "complete";
|
|
523
|
+
if (normalized === "error" || normalized === "failed" || normalized === "failure" || normalized.startsWith("exit ")) return "error";
|
|
524
|
+
return "in_progress";
|
|
525
|
+
}
|
|
526
|
+
function stableTaskIdPart(value, slugValue = value) {
|
|
527
|
+
const slug = slugValue.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
528
|
+
const suffix = createHash("sha256").update(value).digest("hex").slice(0, 8);
|
|
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) => ({
|
|
636
545
|
id: `plan_step_${index + 1}`,
|
|
637
546
|
title: compactTitle(entry.step),
|
|
638
547
|
status: entry.status === "completed" ? "complete" : entry.status
|
|
639
|
-
}))
|
|
548
|
+
}));
|
|
549
|
+
const maxLineChars = resolveMaxLineChars(params.maxLineChars, DEFAULT_SLACK_PROGRESS_TASK_DETAIL_MAX_CHARS);
|
|
550
|
+
const contentIdOccurrences = /* @__PURE__ */ new Map();
|
|
551
|
+
for (const line of params.lines) {
|
|
552
|
+
const task = {
|
|
553
|
+
id: resolveLineTaskIdentity(line, contentIdOccurrences),
|
|
554
|
+
title: lineTaskTitle(line),
|
|
555
|
+
status: lineTaskStatus(line)
|
|
556
|
+
};
|
|
557
|
+
const details = lineTaskDetails(line, maxLineChars);
|
|
558
|
+
const output = lineTaskOutput(line);
|
|
559
|
+
if (details) task.details = details;
|
|
560
|
+
if (output) task.output = output;
|
|
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
|
+
}];
|
|
581
|
+
});
|
|
582
|
+
}
|
|
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;
|
|
593
|
+
}
|
|
594
|
+
function buildSlackProgressStreamChunks(params) {
|
|
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"),
|
|
598
|
+
plan: params.plan,
|
|
599
|
+
maxLineChars: params.maxLineChars
|
|
600
|
+
});
|
|
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({
|
|
640
607
|
id: "openclaw_summary",
|
|
641
|
-
title: compactTitle(title),
|
|
642
|
-
status: "in_progress"
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
if (params.finalInProgressStatus === "error" && !tasks.some((task) => task.status === "in_progress"))
|
|
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({
|
|
646
613
|
id: "openclaw_attention",
|
|
647
|
-
title:
|
|
614
|
+
title: "Failed",
|
|
648
615
|
status: "error"
|
|
649
|
-
};
|
|
650
|
-
if (
|
|
616
|
+
});
|
|
617
|
+
if (!headline && tasks.length === 0) return;
|
|
651
618
|
const finalTaskIndex = tasks.length - 1;
|
|
652
619
|
const taskChunks = tasks.map((task, index) => {
|
|
620
|
+
const recovered = params.finalInProgressStatus === "complete" && task.status === "error";
|
|
653
621
|
const chunk = {
|
|
654
622
|
type: "task_update",
|
|
655
623
|
id: task.id,
|
|
656
|
-
title: task.title,
|
|
657
|
-
status: task.status === "in_progress" ? params.finalInProgressStatus ?? task.status : task.status
|
|
624
|
+
title: recovered ? compactTitle(`Recovered: ${task.title}`) : task.title,
|
|
625
|
+
status: recovered ? "complete" : task.status === "in_progress" ? params.finalInProgressStatus ?? task.status : task.status
|
|
658
626
|
};
|
|
627
|
+
if (task.details) chunk.details = task.details;
|
|
628
|
+
if (task.output) chunk.output = task.output;
|
|
629
|
+
if (index === finalTaskIndex && diffOutput) chunk.output = [task.output, diffOutput].filter(Boolean).join(" · ");
|
|
659
630
|
if (index === finalTaskIndex && params.sessionUrl) chunk.sources = buildSessionSources(params.sessionUrl);
|
|
660
631
|
return chunk;
|
|
661
632
|
});
|
|
@@ -664,30 +635,53 @@ function buildSlackProgressStreamChunks(params) {
|
|
|
664
635
|
title
|
|
665
636
|
}, ...taskChunks];
|
|
666
637
|
}
|
|
638
|
+
function joinRecentProgressRows(rows) {
|
|
639
|
+
const rendered = [];
|
|
640
|
+
let length = 0;
|
|
641
|
+
for (const row of rows.toReversed()) {
|
|
642
|
+
const nextLength = length + row.length + (rendered.length > 0 ? 1 : 0);
|
|
643
|
+
if (nextLength > SLACK_PROGRESS_FIELD_MAX) break;
|
|
644
|
+
rendered.push(row);
|
|
645
|
+
length = nextLength;
|
|
646
|
+
}
|
|
647
|
+
return rendered.toReversed().join("\n");
|
|
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
|
+
}
|
|
667
654
|
function buildSlackProgressCardBlocks(params) {
|
|
668
655
|
const maxLineChars = resolveMaxLineChars(params.maxLineChars, DEFAULT_SLACK_PROGRESS_DETAIL_MAX_CHARS);
|
|
669
|
-
const
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
};
|
|
674
|
-
const planLines = (params.plan ?? []).slice(-50).map((entry) => `${statusLabels[entry.status]}: ${compactDetail(entry.step, maxLineChars)}`);
|
|
656
|
+
const planLines = formatPlanChecklistLines(params.plan ?? [], {
|
|
657
|
+
maxLines: 50,
|
|
658
|
+
maxLineChars
|
|
659
|
+
});
|
|
675
660
|
const narration = params.narration?.replace(/\s+/g, " ").trim();
|
|
676
|
-
const
|
|
677
|
-
const
|
|
661
|
+
const diffStat = formatDiffStat(params.diffStat);
|
|
662
|
+
const workingFooter = [
|
|
663
|
+
...params.toolCalls && params.toolCalls > 0 ? [`🛠️ ${params.toolCalls} tools`] : [],
|
|
664
|
+
...diffStat ? [diffStat] : [],
|
|
665
|
+
...params.elapsedSeconds && params.elapsedSeconds > 0 ? [`⏱ ${params.elapsedSeconds}s`] : []
|
|
666
|
+
].join(" · ");
|
|
667
|
+
const footer = params.state === "working" ? workingFooter : diffStat;
|
|
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));
|
|
678
671
|
const blocks = [
|
|
679
|
-
`${
|
|
680
|
-
narration ? `_${
|
|
681
|
-
planLines.map((line) =>
|
|
682
|
-
|
|
683
|
-
|
|
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)
|
|
684
677
|
].filter(Boolean).map((text) => ({
|
|
685
678
|
type: "section",
|
|
686
|
-
text:
|
|
687
|
-
type: "mrkdwn",
|
|
688
|
-
text: truncateSlackText(text, SLACK_PROGRESS_FIELD_MAX)
|
|
689
|
-
}
|
|
679
|
+
text: field(text)
|
|
690
680
|
}));
|
|
681
|
+
if (footer) blocks.push({
|
|
682
|
+
type: "context",
|
|
683
|
+
elements: [field(footer)]
|
|
684
|
+
});
|
|
691
685
|
if (params.state !== "working" && params.sessionUrl) blocks.push({
|
|
692
686
|
type: "actions",
|
|
693
687
|
elements: [{
|
|
@@ -703,11 +697,39 @@ function buildSlackProgressCardBlocks(params) {
|
|
|
703
697
|
return blocks.slice(0, 50);
|
|
704
698
|
}
|
|
705
699
|
const EMPTY_SLACK_NATIVE_STREAM_SNAPSHOT = { tasks: /* @__PURE__ */ new Map() };
|
|
700
|
+
const SLACK_TASK_FIELD_SEPARATOR = " · ";
|
|
701
|
+
function resolveTaskFieldDelta(previous, incoming) {
|
|
702
|
+
if (!incoming) return {
|
|
703
|
+
field: previous,
|
|
704
|
+
delta: void 0
|
|
705
|
+
};
|
|
706
|
+
if (previous?.rendered.includes(incoming)) return {
|
|
707
|
+
field: {
|
|
708
|
+
rendered: previous.rendered,
|
|
709
|
+
source: incoming
|
|
710
|
+
},
|
|
711
|
+
delta: void 0
|
|
712
|
+
};
|
|
713
|
+
const next = applyAppendOnlyStreamUpdate({
|
|
714
|
+
incoming,
|
|
715
|
+
rendered: previous?.rendered ?? "",
|
|
716
|
+
source: previous?.source ?? "",
|
|
717
|
+
separator: SLACK_TASK_FIELD_SEPARATOR
|
|
718
|
+
});
|
|
719
|
+
const delta = next.changed ? next.rendered.slice(previous?.rendered.length ?? 0) : void 0;
|
|
720
|
+
return {
|
|
721
|
+
field: {
|
|
722
|
+
rendered: next.rendered,
|
|
723
|
+
source: next.source
|
|
724
|
+
},
|
|
725
|
+
delta
|
|
726
|
+
};
|
|
727
|
+
}
|
|
706
728
|
/**
|
|
707
729
|
* Turns a full task snapshot into the delta Slack must receive. Native streams
|
|
708
730
|
* key rows by persistent id with no removal chunk: unchanged rows are omitted,
|
|
709
|
-
* changed
|
|
710
|
-
* (plan shrinks,
|
|
731
|
+
* changed rows carry only their unsent field text, and rows that dropped out
|
|
732
|
+
* (plan shrinks, tool-line <-> plan source switches) get a final complete
|
|
711
733
|
* update or they linger in_progress forever.
|
|
712
734
|
*/
|
|
713
735
|
function reconcileSlackNativeTaskChunks(params) {
|
|
@@ -727,38 +749,46 @@ function reconcileSlackNativeTaskChunks(params) {
|
|
|
727
749
|
continue;
|
|
728
750
|
}
|
|
729
751
|
const previousRow = params.previous.tasks.get(chunk.id);
|
|
730
|
-
const
|
|
752
|
+
const details = resolveTaskFieldDelta(previousRow?.details, chunk.details);
|
|
753
|
+
const output = resolveTaskFieldDelta(previousRow?.output, chunk.output);
|
|
731
754
|
const sourcesChanged = Boolean(chunk.sources) && !previousRow?.sourcesSent;
|
|
732
755
|
const row = {
|
|
733
756
|
title: chunk.title,
|
|
734
|
-
status
|
|
757
|
+
status: chunk.status
|
|
735
758
|
};
|
|
759
|
+
if (details.field) row.details = details.field;
|
|
760
|
+
if (output.field) row.output = output.field;
|
|
736
761
|
if (sourcesChanged || previousRow?.sourcesSent) row.sourcesSent = true;
|
|
737
762
|
nextTasks.set(chunk.id, row);
|
|
738
|
-
if (!(!previousRow || previousRow.title !== chunk.title || previousRow.status !== status || sourcesChanged)) continue;
|
|
763
|
+
if (!(!previousRow || previousRow.title !== chunk.title || previousRow.status !== chunk.status || Boolean(details.delta) || Boolean(output.delta) || sourcesChanged)) continue;
|
|
739
764
|
const update = {
|
|
740
765
|
type: "task_update",
|
|
741
766
|
id: chunk.id,
|
|
742
767
|
title: chunk.title,
|
|
743
|
-
status
|
|
768
|
+
status: chunk.status
|
|
744
769
|
};
|
|
770
|
+
if (details.delta) update.details = details.delta;
|
|
771
|
+
if (output.delta) update.output = output.delta;
|
|
745
772
|
if (sourcesChanged) update.sources = chunk.sources;
|
|
746
773
|
emitted.push(update);
|
|
747
774
|
}
|
|
748
775
|
for (const [id, row] of params.previous.tasks) {
|
|
749
776
|
if (nextTasks.has(id)) continue;
|
|
750
|
-
|
|
777
|
+
const recovered = row.status === "error" && (id.startsWith(SLACK_ATTENTION_TASK_PREFIX) || params.finalStatus === "complete");
|
|
778
|
+
if (row.status === "complete" || row.status === "error" && !recovered) {
|
|
751
779
|
nextTasks.set(id, row);
|
|
752
780
|
continue;
|
|
753
781
|
}
|
|
782
|
+
const title = recovered ? compactTitle(`Recovered: ${row.title}`) : row.title;
|
|
754
783
|
nextTasks.set(id, {
|
|
755
784
|
...row,
|
|
785
|
+
title,
|
|
756
786
|
status: "complete"
|
|
757
787
|
});
|
|
758
788
|
emitted.push({
|
|
759
789
|
type: "task_update",
|
|
760
790
|
id,
|
|
761
|
-
title
|
|
791
|
+
title,
|
|
762
792
|
status: "complete"
|
|
763
793
|
});
|
|
764
794
|
}
|
|
@@ -771,62 +801,30 @@ function reconcileSlackNativeTaskChunks(params) {
|
|
|
771
801
|
};
|
|
772
802
|
}
|
|
773
803
|
//#endregion
|
|
774
|
-
//#region extensions/slack/src/stream-mode.ts
|
|
775
|
-
function resolveSlackStreamingConfig(params) {
|
|
776
|
-
return {
|
|
777
|
-
mode: resolveSlackStreamingMode(params),
|
|
778
|
-
nativeStreaming: resolveSlackNativeStreaming(params)
|
|
779
|
-
};
|
|
780
|
-
}
|
|
781
|
-
function applyAppendOnlyStreamUpdate(params) {
|
|
782
|
-
const incoming = params.incoming.trimEnd();
|
|
783
|
-
if (!incoming) return {
|
|
784
|
-
rendered: params.rendered,
|
|
785
|
-
source: params.source,
|
|
786
|
-
changed: false
|
|
787
|
-
};
|
|
788
|
-
if (!params.rendered) return {
|
|
789
|
-
rendered: incoming,
|
|
790
|
-
source: incoming,
|
|
791
|
-
changed: true
|
|
792
|
-
};
|
|
793
|
-
if (incoming === params.source) return {
|
|
794
|
-
rendered: params.rendered,
|
|
795
|
-
source: params.source,
|
|
796
|
-
changed: false
|
|
797
|
-
};
|
|
798
|
-
if (incoming.startsWith(params.rendered)) return {
|
|
799
|
-
rendered: incoming,
|
|
800
|
-
source: incoming,
|
|
801
|
-
changed: incoming !== params.rendered
|
|
802
|
-
};
|
|
803
|
-
if (incoming.startsWith(params.source)) {
|
|
804
|
-
const delta = incoming.slice(params.source.length);
|
|
805
|
-
return {
|
|
806
|
-
rendered: `${params.rendered}${delta}`,
|
|
807
|
-
source: incoming,
|
|
808
|
-
changed: delta.length > 0
|
|
809
|
-
};
|
|
810
|
-
}
|
|
811
|
-
if (params.source.startsWith(incoming)) return {
|
|
812
|
-
rendered: params.rendered,
|
|
813
|
-
source: params.source,
|
|
814
|
-
changed: false
|
|
815
|
-
};
|
|
816
|
-
const separator = params.separator ?? (params.rendered.endsWith("\n") ? "" : "\n");
|
|
817
|
-
return {
|
|
818
|
-
rendered: `${params.rendered}${separator}${incoming}`,
|
|
819
|
-
source: incoming,
|
|
820
|
-
changed: true
|
|
821
|
-
};
|
|
822
|
-
}
|
|
823
|
-
//#endregion
|
|
824
804
|
//#region extensions/slack/src/monitor/message-handler/dispatch-progress-render.ts
|
|
825
805
|
function resolveStructuredProgressLines(lines) {
|
|
826
|
-
return lines.
|
|
806
|
+
return lines.map((line) => {
|
|
807
|
+
if (typeof line !== "string") return line;
|
|
808
|
+
const reasoning = line.startsWith("🧠 ");
|
|
809
|
+
const text = line.replace(/^(?:🧠|💬)\s+/u, "").replace(/^_(.*)_$/su, "$1").trim();
|
|
810
|
+
return {
|
|
811
|
+
...reasoning ? { id: "reasoning" } : {},
|
|
812
|
+
kind: "item",
|
|
813
|
+
text,
|
|
814
|
+
label: reasoning ? "Reasoning" : "Update",
|
|
815
|
+
prefix: false
|
|
816
|
+
};
|
|
817
|
+
});
|
|
827
818
|
}
|
|
828
819
|
function resolveNativeProgressLines(snapshot) {
|
|
829
|
-
|
|
820
|
+
const lines = resolveStructuredProgressLines(snapshot.lines).filter((line) => line.id !== "reasoning" && line.id?.startsWith("commentary:") !== true);
|
|
821
|
+
if (snapshot.plan?.length || !snapshot.planExplanation) return lines;
|
|
822
|
+
const explanationLine = buildChannelProgressDraftLine({
|
|
823
|
+
event: "plan",
|
|
824
|
+
phase: "update",
|
|
825
|
+
explanation: snapshot.planExplanation
|
|
826
|
+
});
|
|
827
|
+
return explanationLine ? [...lines, explanationLine] : lines;
|
|
830
828
|
}
|
|
831
829
|
function resolveNativeProgressNarration(snapshot) {
|
|
832
830
|
const paragraphs = resolveStructuredProgressLines(snapshot.lines).filter((line) => line.id === "reasoning" || line.id?.startsWith("commentary:") === true).map((line) => line.text.trim()).filter((text, index, values) => Boolean(text) && values.indexOf(text) === index);
|
|
@@ -964,7 +962,6 @@ function createSlackDraftProgressCardRuntime(params) {
|
|
|
964
962
|
return url.toString();
|
|
965
963
|
};
|
|
966
964
|
const resolveText = (snapshot) => latestFallbackText || formatChannelProgressDraftText({
|
|
967
|
-
presentation: "summary",
|
|
968
965
|
entry: account.config,
|
|
969
966
|
lines: [...snapshot.lines],
|
|
970
967
|
seed: params.progressSeed,
|
|
@@ -974,14 +971,20 @@ function createSlackDraftProgressCardRuntime(params) {
|
|
|
974
971
|
});
|
|
975
972
|
const resolvePresentation = (snapshot, state) => {
|
|
976
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();
|
|
977
977
|
return buildSlackProgressCardBlocks({
|
|
978
978
|
state,
|
|
979
979
|
title,
|
|
980
|
-
narration
|
|
980
|
+
narration,
|
|
981
981
|
plan: snapshot.plan,
|
|
982
982
|
lines: resolveStructuredProgressLines(snapshot.lines),
|
|
983
983
|
maxLineChars: params.maxLineChars,
|
|
984
|
-
|
|
984
|
+
diffStat: snapshot.diffStat,
|
|
985
|
+
toolCalls: workCounter?.toolCalls,
|
|
986
|
+
elapsedSeconds: workCounter?.elapsedSeconds,
|
|
987
|
+
sessionUrl
|
|
985
988
|
});
|
|
986
989
|
};
|
|
987
990
|
const finalize = async (status, snapshot = params.getSnapshot(), fallbackText = resolveText(snapshot)) => {
|
|
@@ -1035,10 +1038,10 @@ function formatSlackProgressDraftLine(line) {
|
|
|
1035
1038
|
if (/^(?:🧠|💬)\s/u.test(line)) return line;
|
|
1036
1039
|
const italicCommentary = /^_(.*)_$/su.exec(line);
|
|
1037
1040
|
if (!italicCommentary) return escapeSlackMrkdwn(line);
|
|
1038
|
-
return `_${italicCommentary[1]
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
})
|
|
1041
|
+
return `_${normalizeSlackOutboundText(italicCommentary[1], {
|
|
1042
|
+
mentions: "escape",
|
|
1043
|
+
enclosingStyle: "italic"
|
|
1044
|
+
})}_`;
|
|
1042
1045
|
}
|
|
1043
1046
|
//#endregion
|
|
1044
1047
|
//#region extensions/slack/src/monitor/message-handler/dispatch-progress-native.ts
|
|
@@ -1075,6 +1078,7 @@ function createSlackNativeProgressTransport(params) {
|
|
|
1075
1078
|
const startPromise = (async () => {
|
|
1076
1079
|
const session = await startSlackStream({
|
|
1077
1080
|
client: slackClient,
|
|
1081
|
+
clientOptions: params.setup.slackClientOptions,
|
|
1078
1082
|
channel: message.channel,
|
|
1079
1083
|
threadTs: streamThreadTs,
|
|
1080
1084
|
...update.text ? { text: update.text } : {},
|
|
@@ -1145,7 +1149,7 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1145
1149
|
const isProgressMode = slackStreaming.mode === "progress";
|
|
1146
1150
|
const useNativeProgressStreaming = useStreaming && slackStreaming.mode === "progress";
|
|
1147
1151
|
const progressDraftActive = Boolean(draftStream) || useNativeProgressStreaming;
|
|
1148
|
-
const previewToolProgressEnabled = progressDraftActive && resolveChannelStreamingPreviewToolProgress(account.config,
|
|
1152
|
+
const previewToolProgressEnabled = progressDraftActive && resolveChannelStreamingPreviewToolProgress(account.config, slackStreaming.mode !== "progress", slackStreaming.mode);
|
|
1149
1153
|
let shouldYieldDraftProgress = () => false;
|
|
1150
1154
|
const suppressDefaultToolProgressMessages = resolveChannelStreamingSuppressDefaultToolProgressMessages(account.config, {
|
|
1151
1155
|
draftStreamActive: Boolean(draftStream) || useNativeProgressStreaming,
|
|
@@ -1167,6 +1171,7 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1167
1171
|
nativeStreamOrder = run.catch(() => void 0);
|
|
1168
1172
|
return run;
|
|
1169
1173
|
};
|
|
1174
|
+
const progressWorkCounter = createChannelProgressWorkCounter();
|
|
1170
1175
|
const progressSeed = `${account.accountId}:${message.channel}`;
|
|
1171
1176
|
const slackProgressStyle = resolveSlackProgressStyle(account.config);
|
|
1172
1177
|
const useDraftProgressCard = Boolean(draftStream) && isProgressMode && slackProgressStyle === "card";
|
|
@@ -1182,6 +1187,7 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1182
1187
|
},
|
|
1183
1188
|
draftStream,
|
|
1184
1189
|
enabled: useDraftProgressCard,
|
|
1190
|
+
progressWorkCounter: previewToolProgressEnabled ? progressWorkCounter : void 0,
|
|
1185
1191
|
progressSeed,
|
|
1186
1192
|
explicitTitle: explicitProgressTitle,
|
|
1187
1193
|
maxLineChars: progressDraftMaxLineChars,
|
|
@@ -1246,9 +1252,11 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1246
1252
|
const reconciled = reconcileSlackNativeTaskChunks({
|
|
1247
1253
|
previous: nativeStreamSnapshot,
|
|
1248
1254
|
chunks: buildSlackProgressStreamChunks({
|
|
1249
|
-
title: resolveNativeProgressTitle(snapshot)
|
|
1255
|
+
title: resolveNativeProgressTitle(snapshot),
|
|
1250
1256
|
lines: resolveNativeProgressLines(snapshot),
|
|
1251
|
-
plan: snapshot.plan
|
|
1257
|
+
plan: snapshot.plan,
|
|
1258
|
+
maxLineChars: progressDraftMaxLineChars,
|
|
1259
|
+
summaryRow: !previewToolProgressEnabled
|
|
1252
1260
|
})
|
|
1253
1261
|
});
|
|
1254
1262
|
const chunks = reconciled.chunks;
|
|
@@ -1307,18 +1315,17 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1307
1315
|
return true;
|
|
1308
1316
|
};
|
|
1309
1317
|
const resetProgressTurnState = () => {
|
|
1318
|
+
progressWorkCounter.reset();
|
|
1310
1319
|
nativeNarrationRenderedText = "";
|
|
1311
1320
|
nativeNarrationSourceText = "";
|
|
1312
1321
|
};
|
|
1313
1322
|
const progressDraft = createChannelProgressDraftCompositor({
|
|
1314
|
-
presentation: isProgressMode && slackProgressStyle === "card" ? "summary" : void 0,
|
|
1315
1323
|
entry: account.config,
|
|
1316
1324
|
mode: slackStreaming.mode,
|
|
1317
1325
|
active: progressDraftActive,
|
|
1318
1326
|
seed: progressSeed,
|
|
1319
1327
|
formatLine: formatSlackProgressDraftLine,
|
|
1320
1328
|
reasoningLinePrefix: "🧠 ",
|
|
1321
|
-
reasoningGate: previewToolProgressEnabled,
|
|
1322
1329
|
updateOnLineChange: useNativeProgressStreaming || useDraftProgressCard,
|
|
1323
1330
|
update: async (previewText, options) => {
|
|
1324
1331
|
if (useNativeProgressStreaming) {
|
|
@@ -1372,14 +1379,18 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1372
1379
|
const sessionUrl = progressCard.resolveSessionUrl();
|
|
1373
1380
|
const narrationUpdate = resolveNarrationUpdate(resolveNativeProgressNarration(snapshot));
|
|
1374
1381
|
const hasRetirableNativeTasks = [...nativeStreamSnapshot.tasks.values()].some((task) => task.status !== "complete" && task.status !== "error");
|
|
1375
|
-
if (lines.length === 0 && !snapshot.plan?.length && !hasRetirableNativeTasks && !narrationUpdate.delta && !sessionUrl) return;
|
|
1382
|
+
if (lines.length === 0 && !snapshot.plan?.length && !hasRetirableNativeTasks && !snapshot.diffStat && !narrationUpdate.delta && !sessionUrl) return;
|
|
1376
1383
|
const completion = reconcileSlackNativeTaskChunks({
|
|
1377
1384
|
previous: nativeStreamSnapshot,
|
|
1385
|
+
finalStatus: finalInProgressStatus,
|
|
1378
1386
|
chunks: buildSlackProgressStreamChunks({
|
|
1379
1387
|
title: resolveNativeProgressTitle(snapshot) ?? (lines.length === 0 && !snapshot.plan?.length ? "Working" : void 0),
|
|
1380
1388
|
lines,
|
|
1381
1389
|
plan: snapshot.plan,
|
|
1390
|
+
maxLineChars: progressDraftMaxLineChars,
|
|
1391
|
+
summaryRow: !previewToolProgressEnabled,
|
|
1382
1392
|
finalInProgressStatus,
|
|
1393
|
+
diffStat: snapshot.diffStat,
|
|
1383
1394
|
sessionUrl
|
|
1384
1395
|
})
|
|
1385
1396
|
}).chunks;
|
|
@@ -1390,28 +1401,14 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1390
1401
|
};
|
|
1391
1402
|
const finishNativeProgressTurn = async (completionChunks) => {
|
|
1392
1403
|
if (delivery.nativeProgressStreamStartPromise) await delivery.nativeProgressStreamStartPromise.catch(() => null);
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
if (completionChunks?.length) nativeProgressCompletionSent = true;
|
|
1396
|
-
const stopResult = await stopSlackStream({
|
|
1397
|
-
session,
|
|
1398
|
-
...completionChunks?.length ? { chunks: completionChunks } : {},
|
|
1399
|
-
...slackMessageMetadata ? { metadata: slackMessageMetadata } : {}
|
|
1400
|
-
});
|
|
1401
|
-
delivery.acknowledgeStoppedStreamedDeliveries(session, stopResult?.messageId);
|
|
1402
|
-
} catch (err) {
|
|
1403
|
-
const error = formatSlackError(err);
|
|
1404
|
-
delivery.emitAcknowledgedStreamedDeliveries();
|
|
1405
|
-
delivery.emitFailedPendingStreamedDeliveries(error);
|
|
1406
|
-
logVerbose(`slack-stream: failed to rotate native progress stream (${error})`);
|
|
1407
|
-
}
|
|
1404
|
+
if (completionChunks?.length) nativeProgressCompletionSent = true;
|
|
1405
|
+
await delivery.finishStream(completionChunks);
|
|
1408
1406
|
delivery.streamSession = null;
|
|
1409
1407
|
delivery.nativeProgressStreamStartPromise = null;
|
|
1410
1408
|
delivery.nativeProgressStreamThreadTs = void 0;
|
|
1411
1409
|
delivery.streamFailed = false;
|
|
1412
1410
|
};
|
|
1413
1411
|
const pushPlanProgress = async (steps, explanation) => {
|
|
1414
|
-
if (!previewToolProgressEnabled) return false;
|
|
1415
1412
|
if (isProgressMode) {
|
|
1416
1413
|
if (slackProgressStyle === "compact") return false;
|
|
1417
1414
|
return await progressDraft.pushPlanProgress(steps, { explanation });
|
|
@@ -1553,6 +1550,7 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1553
1550
|
previewToolProgressEnabled,
|
|
1554
1551
|
suppressDefaultToolProgressMessages,
|
|
1555
1552
|
progressDraft,
|
|
1553
|
+
progressWorkCounter,
|
|
1556
1554
|
commentaryProgressEnabled,
|
|
1557
1555
|
async cancel() {
|
|
1558
1556
|
progressDraft.cancel();
|
|
@@ -1589,6 +1587,10 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1589
1587
|
async function createSlackDispatchSetup(prepared) {
|
|
1590
1588
|
const { ctx, account, message, route } = prepared;
|
|
1591
1589
|
const slackClient = prepared.eventScope?.client ?? ctx.app.client;
|
|
1590
|
+
const slackClientOptions = {
|
|
1591
|
+
...ctx.app.webClientOptions,
|
|
1592
|
+
...prepared.eventScope ? { teamId: prepared.eventScope.teamId } : {}
|
|
1593
|
+
};
|
|
1592
1594
|
const slackStreamFallbackTeamId = prepared.eventScope?.teamId ?? ctx.teamId;
|
|
1593
1595
|
const cfg = ctx.cfg;
|
|
1594
1596
|
const runtime = ctx.runtime;
|
|
@@ -1702,12 +1704,13 @@ async function createSlackDispatchSetup(prepared) {
|
|
|
1702
1704
|
transformReplyPayload: sanitizeSlackMonitorReplyPayload,
|
|
1703
1705
|
typing: {
|
|
1704
1706
|
start: async () => {
|
|
1705
|
-
if (!threadStatusGate.hasVisibleOutput()) {
|
|
1707
|
+
if (!didSetStatus && !threadStatusGate.hasVisibleOutput()) {
|
|
1706
1708
|
didSetStatus = true;
|
|
1707
|
-
await ctx.
|
|
1709
|
+
await ctx.setSlackSessionStatus({
|
|
1708
1710
|
channelId: message.channel,
|
|
1709
1711
|
threadTs: statusThreadTs,
|
|
1710
|
-
status: "
|
|
1712
|
+
status: "processing",
|
|
1713
|
+
title: prepared.sessionDisplayName ?? prepared.ctxPayload.ThreadLabel,
|
|
1711
1714
|
eventScope: prepared.eventScope
|
|
1712
1715
|
});
|
|
1713
1716
|
}
|
|
@@ -1724,10 +1727,10 @@ async function createSlackDispatchSetup(prepared) {
|
|
|
1724
1727
|
stop: async () => {
|
|
1725
1728
|
if (didSetStatus) {
|
|
1726
1729
|
didSetStatus = false;
|
|
1727
|
-
await ctx.
|
|
1730
|
+
await ctx.setSlackSessionStatus({
|
|
1728
1731
|
channelId: message.channel,
|
|
1729
1732
|
threadTs: statusThreadTs,
|
|
1730
|
-
status: "",
|
|
1733
|
+
status: "active",
|
|
1731
1734
|
eventScope: prepared.eventScope
|
|
1732
1735
|
});
|
|
1733
1736
|
}
|
|
@@ -1782,15 +1785,6 @@ async function createSlackDispatchSetup(prepared) {
|
|
|
1782
1785
|
});
|
|
1783
1786
|
const shouldUseDraftStream = previewStreamingEnabled && !useStreaming;
|
|
1784
1787
|
const blockStreamingEnabled = resolveChannelStreamingBlockEnabled(account.config);
|
|
1785
|
-
const disableBlockStreaming = sourceRepliesAreToolOnly ? true : resolveSlackDisableBlockStreaming({
|
|
1786
|
-
useStreaming,
|
|
1787
|
-
shouldUseDraftStream,
|
|
1788
|
-
blockStreamingEnabled
|
|
1789
|
-
});
|
|
1790
|
-
const onSlackDeliveryError = (err, info) => {
|
|
1791
|
-
runtime.error?.(danger(`slack ${info.kind} reply failed: ${formatSlackError(err)}`));
|
|
1792
|
-
replyPipeline.typingCallbacks?.onIdle?.();
|
|
1793
|
-
};
|
|
1794
1788
|
return {
|
|
1795
1789
|
prepared,
|
|
1796
1790
|
ctx,
|
|
@@ -1798,6 +1792,7 @@ async function createSlackDispatchSetup(prepared) {
|
|
|
1798
1792
|
message,
|
|
1799
1793
|
route,
|
|
1800
1794
|
slackClient,
|
|
1795
|
+
slackClientOptions,
|
|
1801
1796
|
slackStreamFallbackTeamId,
|
|
1802
1797
|
cfg,
|
|
1803
1798
|
runtime,
|
|
@@ -1827,15 +1822,18 @@ async function createSlackDispatchSetup(prepared) {
|
|
|
1827
1822
|
previewStreamingEnabled,
|
|
1828
1823
|
hasSlackCustomIdentity,
|
|
1829
1824
|
shouldUseDraftStream,
|
|
1830
|
-
disableBlockStreaming
|
|
1831
|
-
|
|
1832
|
-
|
|
1825
|
+
disableBlockStreaming: sourceRepliesAreToolOnly ? true : resolveSlackDisableBlockStreaming({
|
|
1826
|
+
useStreaming,
|
|
1827
|
+
shouldUseDraftStream,
|
|
1828
|
+
blockStreamingEnabled
|
|
1829
|
+
}),
|
|
1830
|
+
useStreaming
|
|
1833
1831
|
};
|
|
1834
1832
|
}
|
|
1835
1833
|
//#endregion
|
|
1836
1834
|
//#region extensions/slack/src/monitor/message-handler/dispatch-streaming.ts
|
|
1837
1835
|
function createSlackStreamingDeliveryRuntime(setup) {
|
|
1838
|
-
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;
|
|
1839
1837
|
const state = {
|
|
1840
1838
|
streamSession: null,
|
|
1841
1839
|
nativeProgressStreamStartPromise: null,
|
|
@@ -1846,69 +1844,14 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
1846
1844
|
observedReplyDelivery: false,
|
|
1847
1845
|
observedFinalReplyDelivery: false
|
|
1848
1846
|
};
|
|
1849
|
-
const
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
const delivery = {
|
|
1855
|
-
kind,
|
|
1847
|
+
const emitStreamedDelivery = (content, result) => {
|
|
1848
|
+
emitSlackMessageSentHooks({
|
|
1849
|
+
...messageSentHookContext,
|
|
1850
|
+
to: messageSentHookTarget,
|
|
1851
|
+
accountId: account.accountId,
|
|
1856
1852
|
content,
|
|
1857
|
-
|
|
1858
|
-
};
|
|
1859
|
-
streamedDeliveries.push(delivery);
|
|
1860
|
-
return delivery;
|
|
1861
|
-
};
|
|
1862
|
-
const rememberStreamedDelivery = (kind, content, session) => {
|
|
1863
|
-
recordStreamedDelivery(kind, content);
|
|
1864
|
-
refreshStreamedAcknowledgements(session);
|
|
1865
|
-
};
|
|
1866
|
-
const emitAcknowledgedStreamedDeliveries = (messageId) => {
|
|
1867
|
-
for (const delivery of streamedDeliveries) {
|
|
1868
|
-
if (!delivery.acknowledged || delivery.outcome) continue;
|
|
1869
|
-
emitSlackMessageSentHooks({
|
|
1870
|
-
...messageSentHookContext,
|
|
1871
|
-
to: messageSentHookTarget,
|
|
1872
|
-
accountId: account.accountId,
|
|
1873
|
-
content: delivery.content,
|
|
1874
|
-
success: true,
|
|
1875
|
-
...messageId ? { messageId } : {}
|
|
1876
|
-
});
|
|
1877
|
-
delivery.outcome = "success";
|
|
1878
|
-
}
|
|
1879
|
-
};
|
|
1880
|
-
const acknowledgeStoppedStreamedDeliveries = (session, messageId) => {
|
|
1881
|
-
refreshStreamedAcknowledgements(session);
|
|
1882
|
-
for (const delivery of streamedDeliveries) delivery.acknowledged = true;
|
|
1883
|
-
emitAcknowledgedStreamedDeliveries(messageId);
|
|
1884
|
-
};
|
|
1885
|
-
const emitFailedPendingStreamedDeliveries = (error) => {
|
|
1886
|
-
for (const delivery of streamedDeliveries) {
|
|
1887
|
-
if (delivery.acknowledged || delivery.outcome) continue;
|
|
1888
|
-
emitSlackMessageSentHooks({
|
|
1889
|
-
...messageSentHookContext,
|
|
1890
|
-
to: messageSentHookTarget,
|
|
1891
|
-
accountId: account.accountId,
|
|
1892
|
-
content: delivery.content,
|
|
1893
|
-
success: false,
|
|
1894
|
-
error
|
|
1895
|
-
});
|
|
1896
|
-
delivery.outcome = "failure";
|
|
1897
|
-
}
|
|
1898
|
-
};
|
|
1899
|
-
const emitSuccessfulPendingStreamedDeliveries = (messageId) => {
|
|
1900
|
-
for (const delivery of streamedDeliveries) {
|
|
1901
|
-
if (delivery.acknowledged || delivery.outcome) continue;
|
|
1902
|
-
emitSlackMessageSentHooks({
|
|
1903
|
-
...messageSentHookContext,
|
|
1904
|
-
to: messageSentHookTarget,
|
|
1905
|
-
accountId: account.accountId,
|
|
1906
|
-
content: delivery.content,
|
|
1907
|
-
success: true,
|
|
1908
|
-
...messageId ? { messageId } : {}
|
|
1909
|
-
});
|
|
1910
|
-
delivery.outcome = "success";
|
|
1911
|
-
}
|
|
1853
|
+
...result
|
|
1854
|
+
});
|
|
1912
1855
|
};
|
|
1913
1856
|
let deliveryTracker = createSlackEventDeliveryTracker();
|
|
1914
1857
|
const markPreviewPayloadDelivered = (params) => {
|
|
@@ -1929,62 +1872,75 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
1929
1872
|
if (kind === "block") state.usedBlockReplyThreadTs = deliveredThreadTs;
|
|
1930
1873
|
};
|
|
1931
1874
|
const deliverPendingStreamFallback = async (session, err) => {
|
|
1875
|
+
if (session.stoppedBySlack) return;
|
|
1932
1876
|
let fallbackError = err;
|
|
1933
1877
|
if (!session.stopped) try {
|
|
1934
1878
|
const stopResult = await stopSlackStream({
|
|
1935
1879
|
session,
|
|
1936
1880
|
...slackMessageMetadata ? { metadata: slackMessageMetadata } : {}
|
|
1937
1881
|
});
|
|
1938
|
-
|
|
1882
|
+
if (session.stoppedBySlack) return;
|
|
1939
1883
|
state.observedReplyDelivery = true;
|
|
1940
1884
|
state.usedReplyThreadTs ??= session.threadTs;
|
|
1941
|
-
return
|
|
1885
|
+
return stopResult;
|
|
1942
1886
|
} catch (stopErr) {
|
|
1943
1887
|
if (stopErr instanceof SlackStreamNotDeliveredError) fallbackError = stopErr;
|
|
1944
|
-
else
|
|
1888
|
+
else throw stopErr;
|
|
1945
1889
|
}
|
|
1946
|
-
emitAcknowledgedStreamedDeliveries();
|
|
1947
1890
|
const fallbackText = fallbackError.pendingText.trim();
|
|
1948
|
-
if (!fallbackText) return
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
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 } : {}
|
|
1966
1914
|
});
|
|
1967
|
-
|
|
1968
|
-
|
|
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 {
|
|
1969
1927
|
await stopSlackStream({
|
|
1970
1928
|
session,
|
|
1929
|
+
...chunks?.length ? { chunks } : {},
|
|
1971
1930
|
...slackMessageMetadata ? { metadata: slackMessageMetadata } : {}
|
|
1972
1931
|
});
|
|
1973
|
-
|
|
1974
|
-
|
|
1932
|
+
state.observedReplyDelivery ||= session.delivered;
|
|
1933
|
+
} catch (error) {
|
|
1934
|
+
if (!(error instanceof SlackStreamNotDeliveredError)) throw error;
|
|
1935
|
+
await deliverPendingStreamFallback(session, error);
|
|
1975
1936
|
}
|
|
1976
|
-
|
|
1977
|
-
state.
|
|
1978
|
-
|
|
1979
|
-
logVerbose(`slack-stream: streamed delivery failed (${fallbackError.slackCode}); delivered ${fallbackText.length} chars via deliverReplies fallback`);
|
|
1980
|
-
return true;
|
|
1981
|
-
} catch (postErr) {
|
|
1982
|
-
emitFailedPendingStreamedDeliveries(formatErrorMessage(postErr));
|
|
1983
|
-
runtime.error?.(danger(`slack-stream: fallback deliverReplies failed after ${fallbackError.slackCode}: ${formatErrorMessage(postErr)}`));
|
|
1984
|
-
return false;
|
|
1937
|
+
} catch (error) {
|
|
1938
|
+
state.streamFailed = true;
|
|
1939
|
+
runtime.error?.(danger(`slack-stream: failed to stop stream: ${formatSlackError(error)}`));
|
|
1985
1940
|
}
|
|
1986
1941
|
};
|
|
1987
1942
|
const deliverNormally = async (params) => {
|
|
1943
|
+
if (state.streamSession?.stoppedBySlack) return;
|
|
1988
1944
|
const replyThreadTs = resolveDeliveryThreadTs(params);
|
|
1989
1945
|
const deliveryReplyThreadTs = replyDeliveryMode === "off" && !forcedReplyThreadTs && !isThreadReply ? void 0 : replyThreadTs;
|
|
1990
1946
|
if (deliveryTracker.hasDelivered({
|
|
@@ -2028,19 +1984,6 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
2028
1984
|
});
|
|
2029
1985
|
return deliveryReplyThreadTs;
|
|
2030
1986
|
};
|
|
2031
|
-
const deliverBufferedStreamFallback = async (params) => {
|
|
2032
|
-
if (!await deliverPendingStreamFallback(params.session, params.err)) return false;
|
|
2033
|
-
replyPlan.markSent();
|
|
2034
|
-
if (params.kind === "final") state.observedFinalReplyDelivery = true;
|
|
2035
|
-
deliveryTracker.markDelivered({
|
|
2036
|
-
kind: params.kind,
|
|
2037
|
-
payload: params.payload,
|
|
2038
|
-
threadTs: params.session.threadTs,
|
|
2039
|
-
textOverride: params.textOverride
|
|
2040
|
-
});
|
|
2041
|
-
rememberDeliveredThreadTs(params.kind, params.session.threadTs);
|
|
2042
|
-
return true;
|
|
2043
|
-
};
|
|
2044
1987
|
const isStreamingEligible = (payload, options) => {
|
|
2045
1988
|
const reply = resolveSendableOutboundReplyParts(payload);
|
|
2046
1989
|
const renderPlan = resolveSlackReplyRenderPlan(payload);
|
|
@@ -2048,7 +1991,7 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
2048
1991
|
return !state.streamFailed && !reply.hasMedia && renderPlan.mode !== "split" && !plannedBlocks?.length && !readSlackReplyBlocks(payload)?.length && reply.hasText && (!options?.maxTextBytes || countSlackTextUtf8Bytes(reply.trimmedText) <= options.maxTextBytes);
|
|
2049
1992
|
};
|
|
2050
1993
|
const deliverWithStreaming = async (params) => {
|
|
2051
|
-
|
|
1994
|
+
if (state.streamSession?.stoppedBySlack) return;
|
|
2052
1995
|
if (!isStreamingEligible(params.payload)) {
|
|
2053
1996
|
await deliverNormally({
|
|
2054
1997
|
payload: params.payload,
|
|
@@ -2057,47 +2000,44 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
2057
2000
|
});
|
|
2058
2001
|
return;
|
|
2059
2002
|
}
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
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;
|
|
2064
2027
|
try {
|
|
2065
|
-
if (
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
return;
|
|
2073
|
-
}
|
|
2074
|
-
if (!state.streamSession) {
|
|
2075
|
-
const streamThreadTs = replyPlan.nextThreadTs();
|
|
2076
|
-
plannedThreadTs = streamThreadTs;
|
|
2077
|
-
if (!streamThreadTs) {
|
|
2078
|
-
logVerbose("slack-stream: no reply thread target for stream start, falling back to normal delivery");
|
|
2079
|
-
state.streamFailed = true;
|
|
2080
|
-
await deliverNormally({
|
|
2081
|
-
payload: params.payload,
|
|
2082
|
-
kind: params.kind
|
|
2083
|
-
});
|
|
2084
|
-
return;
|
|
2085
|
-
}
|
|
2086
|
-
if (deliveryTracker.hasDelivered({
|
|
2087
|
-
kind: params.kind,
|
|
2088
|
-
payload: params.payload,
|
|
2089
|
-
threadTs: streamThreadTs,
|
|
2090
|
-
textOverride: text
|
|
2091
|
-
})) {
|
|
2092
|
-
logVerbose("slack-stream: suppressed duplicate stream start payload");
|
|
2093
|
-
return;
|
|
2094
|
-
}
|
|
2095
|
-
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({
|
|
2096
2035
|
client: slackClient,
|
|
2036
|
+
clientOptions: slackClientOptions,
|
|
2097
2037
|
channel: message.channel,
|
|
2098
|
-
threadTs
|
|
2038
|
+
threadTs,
|
|
2099
2039
|
text,
|
|
2100
|
-
chunks,
|
|
2040
|
+
chunks: [],
|
|
2101
2041
|
...params.taskDisplayMode ? { taskDisplayMode: params.taskDisplayMode } : {},
|
|
2102
2042
|
...slackIdentity ? { identity: slackIdentity } : {},
|
|
2103
2043
|
teamId: await resolveSlackStreamRecipientTeamId({
|
|
@@ -2108,95 +2048,63 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
2108
2048
|
}),
|
|
2109
2049
|
userId: message.user
|
|
2110
2050
|
});
|
|
2111
|
-
|
|
2112
|
-
if (state.streamSession.delivered) {
|
|
2113
|
-
state.observedReplyDelivery = true;
|
|
2114
|
-
if (params.kind === "final") state.observedFinalReplyDelivery = true;
|
|
2115
|
-
}
|
|
2116
|
-
if (hookContent) rememberStreamedDelivery(params.kind, hookContent, state.streamSession);
|
|
2117
|
-
rememberDeliveredThreadTs(params.kind, streamThreadTs);
|
|
2118
|
-
replyPlan.markSent();
|
|
2119
|
-
deliveryTracker.markDelivered({
|
|
2120
|
-
kind: params.kind,
|
|
2121
|
-
payload: params.payload,
|
|
2122
|
-
threadTs: streamThreadTs,
|
|
2123
|
-
textOverride: text
|
|
2124
|
-
});
|
|
2125
|
-
return;
|
|
2051
|
+
state.streamSession = session;
|
|
2126
2052
|
}
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
if (hookContent) recordStreamedDelivery(params.kind, hookContent);
|
|
2137
|
-
await appendSlackStream({
|
|
2138
|
-
session: state.streamSession,
|
|
2139
|
-
text: `${params.appendSeparator === false ? "" : "\n"}${text}`,
|
|
2140
|
-
chunks
|
|
2141
|
-
});
|
|
2142
|
-
refreshStreamedAcknowledgements(state.streamSession);
|
|
2143
|
-
if (state.streamSession.delivered && state.streamSession.pendingText.length === 0) {
|
|
2144
|
-
state.observedReplyDelivery = true;
|
|
2145
|
-
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;
|
|
2146
2062
|
}
|
|
2147
|
-
|
|
2148
|
-
kind: params.kind,
|
|
2149
|
-
payload: params.payload,
|
|
2150
|
-
threadTs: state.streamSession.threadTs,
|
|
2151
|
-
textOverride: text
|
|
2152
|
-
});
|
|
2153
|
-
} catch (err) {
|
|
2154
|
-
if (err instanceof SlackStreamNotDeliveredError) {
|
|
2155
|
-
state.streamFailed = true;
|
|
2156
|
-
if (state.streamSession) {
|
|
2157
|
-
if (await deliverBufferedStreamFallback({
|
|
2158
|
-
session: state.streamSession,
|
|
2159
|
-
err,
|
|
2160
|
-
payload: params.payload,
|
|
2161
|
-
kind: params.kind,
|
|
2162
|
-
textOverride: text
|
|
2163
|
-
})) return;
|
|
2164
|
-
throw err;
|
|
2165
|
-
}
|
|
2063
|
+
if (!session) {
|
|
2166
2064
|
await deliverNormally({
|
|
2167
2065
|
payload: params.payload,
|
|
2168
2066
|
kind: params.kind,
|
|
2169
|
-
forcedThreadTs:
|
|
2067
|
+
forcedThreadTs: threadTs
|
|
2170
2068
|
});
|
|
2171
2069
|
return;
|
|
2172
2070
|
}
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
})) return;
|
|
2184
|
-
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;
|
|
2185
2081
|
}
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2082
|
+
}
|
|
2083
|
+
if (session.stoppedBySlack && session.pendingText) {
|
|
2084
|
+
emitStreamedDelivery(hookContent, {
|
|
2085
|
+
success: false,
|
|
2086
|
+
error: "Stopped by Slack user"
|
|
2190
2087
|
});
|
|
2088
|
+
return;
|
|
2191
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
|
+
});
|
|
2192
2103
|
};
|
|
2193
2104
|
return Object.assign(state, {
|
|
2194
|
-
acknowledgeStoppedStreamedDeliveries,
|
|
2195
2105
|
deliverNormally,
|
|
2196
|
-
deliverPendingStreamFallback,
|
|
2197
2106
|
deliverWithStreaming,
|
|
2198
|
-
|
|
2199
|
-
emitFailedPendingStreamedDeliveries,
|
|
2107
|
+
finishStream,
|
|
2200
2108
|
hasDelivered: (params) => deliveryTracker.hasDelivered(params),
|
|
2201
2109
|
isStreamingEligible,
|
|
2202
2110
|
markPreviewPayloadDelivered,
|
|
@@ -2210,7 +2118,35 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
2210
2118
|
//#region extensions/slack/src/monitor/message-handler/dispatch.ts
|
|
2211
2119
|
async function dispatchPreparedSlackMessage(prepared) {
|
|
2212
2120
|
const setup = await createSlackDispatchSetup(prepared);
|
|
2213
|
-
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;
|
|
2214
2150
|
const delivery = createSlackStreamingDeliveryRuntime(setup);
|
|
2215
2151
|
const draftPreviewCommitted = { value: false };
|
|
2216
2152
|
const progress = createSlackProgressRuntime({
|
|
@@ -2251,6 +2187,14 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2251
2187
|
pendingFailureNotice = notice;
|
|
2252
2188
|
return payload;
|
|
2253
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
|
+
};
|
|
2254
2198
|
const deliverSlackPayload = async (payload, info) => {
|
|
2255
2199
|
if (info.kind === "final" && slackStreaming.mode === "progress" && progress.isProgressMode) {
|
|
2256
2200
|
if (progress.useNativeProgressStreaming) {
|
|
@@ -2258,6 +2202,24 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2258
2202
|
return;
|
|
2259
2203
|
}
|
|
2260
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
|
+
}
|
|
2261
2223
|
if (progress.useDraftProgressCard) {
|
|
2262
2224
|
await delivery.deliverNormally({
|
|
2263
2225
|
payload,
|
|
@@ -2409,7 +2371,6 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2409
2371
|
});
|
|
2410
2372
|
if (info.kind === "final") progress.progressDraft.markFinalReplyDelivered();
|
|
2411
2373
|
};
|
|
2412
|
-
let dispatchError;
|
|
2413
2374
|
let agentRunFailed = false;
|
|
2414
2375
|
let settledDispatchResult;
|
|
2415
2376
|
try {
|
|
@@ -2433,13 +2394,18 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2433
2394
|
},
|
|
2434
2395
|
delivery: {
|
|
2435
2396
|
deliver: deliverSlackPayload,
|
|
2436
|
-
onError:
|
|
2397
|
+
onError: (err, info) => {
|
|
2398
|
+
dispatchError ??= err;
|
|
2399
|
+
runtime.error?.(danger(`slack ${info.kind} reply failed: ${formatSlackError(err)}`));
|
|
2400
|
+
replyPipeline.typingCallbacks?.onIdle?.();
|
|
2401
|
+
}
|
|
2437
2402
|
},
|
|
2438
2403
|
record: prepared.turn.record,
|
|
2439
2404
|
history: prepared.turn.history,
|
|
2440
2405
|
botLoopProtection: resolveSlackBotLoopProtection(prepared),
|
|
2441
2406
|
replyOptions: {
|
|
2442
|
-
|
|
2407
|
+
queuedDeliveryCorrelations: [{ begin: beginSessionRun }],
|
|
2408
|
+
...turnAdoptionLifecycle ? { turnAdoptionLifecycle } : {},
|
|
2443
2409
|
skillFilter: prepared.channelConfig?.skills,
|
|
2444
2410
|
sourceReplyDeliveryMode,
|
|
2445
2411
|
suppressTyping: suppressRoomEventTyping ? true : void 0,
|
|
@@ -2469,14 +2435,14 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2469
2435
|
},
|
|
2470
2436
|
onQueuedFollowupAdmitted: progress.onQueuedFollowupAdmitted,
|
|
2471
2437
|
onQueuedFollowupSettled: progress.onQueuedFollowupSettled,
|
|
2472
|
-
onReasoningStream:
|
|
2438
|
+
onReasoningStream: async (payload) => {
|
|
2473
2439
|
const visible = await progress.pushReasoningProgress(payload);
|
|
2474
|
-
if (
|
|
2475
|
-
await statusReactions.setThinking();
|
|
2440
|
+
if (statusReactionsEnabled) await statusReactions.setThinking();
|
|
2476
2441
|
return visible;
|
|
2477
|
-
}
|
|
2442
|
+
},
|
|
2478
2443
|
onToolStart: async (payload) => {
|
|
2479
2444
|
if (statusReactionsEnabled) await statusReactions.setTool(payload.name);
|
|
2445
|
+
if (payload.phase === "start") progress.progressWorkCounter.noteToolCall(payload.name);
|
|
2480
2446
|
return await progress.progressDraft.pushToolEvent(payload);
|
|
2481
2447
|
},
|
|
2482
2448
|
onItemEvent: async (payload) => {
|
|
@@ -2517,38 +2483,16 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2517
2483
|
});
|
|
2518
2484
|
}
|
|
2519
2485
|
} catch (err) {
|
|
2520
|
-
dispatchError
|
|
2486
|
+
dispatchError ??= err;
|
|
2521
2487
|
} finally {
|
|
2522
2488
|
await progress.cancel();
|
|
2523
2489
|
if (!progress.useDraftProgressCard) await draftStream?.discardPending();
|
|
2524
2490
|
}
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
const stopResult = await stopSlackStream({
|
|
2531
|
-
session: finalStream,
|
|
2532
|
-
...completionChunks?.length ? { chunks: completionChunks } : {},
|
|
2533
|
-
...slackMessageMetadata ? { metadata: slackMessageMetadata } : {}
|
|
2534
|
-
});
|
|
2535
|
-
delivery.acknowledgeStoppedStreamedDeliveries(finalStream, stopResult?.messageId);
|
|
2536
|
-
} catch (err) {
|
|
2537
|
-
if (err instanceof SlackStreamNotDeliveredError) {
|
|
2538
|
-
streamFallbackDelivered = await delivery.deliverPendingStreamFallback(finalStream, err);
|
|
2539
|
-
if (!streamFallbackDelivered) dispatchError ??= err;
|
|
2540
|
-
} else {
|
|
2541
|
-
const error = formatSlackError(err);
|
|
2542
|
-
delivery.emitAcknowledgedStreamedDeliveries();
|
|
2543
|
-
delivery.emitFailedPendingStreamedDeliveries(error);
|
|
2544
|
-
runtime.error?.(danger(`slack-stream: failed to stop stream: ${error}`));
|
|
2545
|
-
if (!finalStream.delivered) dispatchError ??= err;
|
|
2546
|
-
}
|
|
2547
|
-
}
|
|
2548
|
-
const anyReplyDelivered = hasVisibleInboundReplyDispatch(settledDispatchResult, {
|
|
2549
|
-
observedReplyDelivery: delivery.observedReplyDelivery,
|
|
2550
|
-
fallbackDelivered: streamFallbackDelivered
|
|
2551
|
-
});
|
|
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();
|
|
2552
2496
|
if (pendingFailureNotice && anyReplyDelivered) recordSlackThreadFailureNotice(pendingFailureNotice);
|
|
2553
2497
|
if (dispatchError || agentRunFailed) await progress.finalizeDraftProgressCard("error");
|
|
2554
2498
|
await progress.dropDetachedProgressCards();
|
|
@@ -2815,7 +2759,7 @@ const SLACK_MENTION_RESOLUTION_CONCURRENCY = 4;
|
|
|
2815
2759
|
const SLACK_MENTION_RESOLUTION_MAX_LOOKUPS_PER_MESSAGE = 20;
|
|
2816
2760
|
const SLACK_USER_MENTION_RE$1 = /<@([A-Z0-9]+)(?:\|[^>]+)?>/gi;
|
|
2817
2761
|
const MAX_SLACK_UNAVAILABLE_FILE_TEXT_CHARS = 2e3;
|
|
2818
|
-
const loadSlackMediaModule$1 = createLazyRuntimeModule(() => import("./actions-
|
|
2762
|
+
const loadSlackMediaModule$1 = createLazyRuntimeModule(() => import("./actions-CFkvEv1d.js").then((n) => n._));
|
|
2819
2763
|
function formatSlackUnavailableMedia(params) {
|
|
2820
2764
|
let fileReferences = params.files?.map((file) => `${formatSlackFileReference(file)} unavailable (${file.reason})`).join(", ");
|
|
2821
2765
|
if (fileReferences && fileReferences.length > MAX_SLACK_UNAVAILABLE_FILE_TEXT_CHARS) fileReferences = `${truncateUtf16Safe(fileReferences, MAX_SLACK_UNAVAILABLE_FILE_TEXT_CHARS)}; … (file references truncated)`;
|
|
@@ -3007,105 +2951,6 @@ async function resolveSlackDmHistoryContext(params) {
|
|
|
3007
2951
|
}
|
|
3008
2952
|
}
|
|
3009
2953
|
//#endregion
|
|
3010
|
-
//#region extensions/slack/src/monitor/message-handler/prepare-routing.ts
|
|
3011
|
-
function resolveSlackBaseConversationId(params) {
|
|
3012
|
-
const raw = params.isDirectMessage ? `user:${params.message.user ?? "unknown"}` : params.message.channel;
|
|
3013
|
-
return qualifySlackConversationId(raw, params.eventScope);
|
|
3014
|
-
}
|
|
3015
|
-
function resolveSlackInitialAgentRoute(params) {
|
|
3016
|
-
const route = resolveAgentRoute({
|
|
3017
|
-
cfg: normalizeSlackRouteBindingConfig(params.ctx.cfg),
|
|
3018
|
-
channel: "slack",
|
|
3019
|
-
accountId: params.account.accountId,
|
|
3020
|
-
teamId: params.eventScope?.teamId || params.ctx.teamId || void 0,
|
|
3021
|
-
peer: {
|
|
3022
|
-
kind: params.isDirectMessage ? "direct" : params.isRoom ? "channel" : "group",
|
|
3023
|
-
id: qualifySlackRoutePeerId({
|
|
3024
|
-
id: params.isDirectMessage ? params.message.user ?? "unknown" : params.message.channel,
|
|
3025
|
-
kind: params.isDirectMessage ? "user" : "channel",
|
|
3026
|
-
eventScope: params.eventScope
|
|
3027
|
-
})
|
|
3028
|
-
}
|
|
3029
|
-
});
|
|
3030
|
-
if (!params.eventScope || !params.isDirectMessage || route.dmScope !== "main") return route;
|
|
3031
|
-
const sessionKey = resolveSlackEnterpriseMainDmSessionKey({
|
|
3032
|
-
baseSessionKey: route.sessionKey,
|
|
3033
|
-
accountId: params.account.accountId,
|
|
3034
|
-
eventScope: params.eventScope
|
|
3035
|
-
});
|
|
3036
|
-
return {
|
|
3037
|
-
...route,
|
|
3038
|
-
sessionKey,
|
|
3039
|
-
mainSessionKey: sessionKey
|
|
3040
|
-
};
|
|
3041
|
-
}
|
|
3042
|
-
function resolveSlackRoutingContext(params) {
|
|
3043
|
-
const { ctx, account, message, isDirectMessage, isGroupDm, isRoom, isRoomish, channelConfig, seedTopLevelRoomThread, assistantThreadTs, agentViewThreadTs, eventScope } = params;
|
|
3044
|
-
let route = resolveSlackInitialAgentRoute({
|
|
3045
|
-
ctx,
|
|
3046
|
-
account,
|
|
3047
|
-
message,
|
|
3048
|
-
isDirectMessage,
|
|
3049
|
-
isRoom,
|
|
3050
|
-
eventScope
|
|
3051
|
-
});
|
|
3052
|
-
const chatType = isDirectMessage ? "direct" : isGroupDm ? "group" : "channel";
|
|
3053
|
-
const replyToMode = channelConfig?.replyToMode ?? resolveSlackReplyToMode(account, chatType);
|
|
3054
|
-
const threadContext = resolveSlackThreadContext({
|
|
3055
|
-
message,
|
|
3056
|
-
replyToMode,
|
|
3057
|
-
isDirectMessage
|
|
3058
|
-
});
|
|
3059
|
-
const threadTs = threadContext.incomingThreadTs;
|
|
3060
|
-
const isThreadReply = threadContext.isThreadReply;
|
|
3061
|
-
const autoThreadId = !isThreadReply && replyToMode === "all" && threadContext.messageTs ? threadContext.messageTs : void 0;
|
|
3062
|
-
const seedCandidateThreadId = threadContext.incomingThreadTs ?? threadContext.messageTs;
|
|
3063
|
-
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);
|
|
3064
|
-
const baseConversationId = resolveSlackBaseConversationId({
|
|
3065
|
-
message,
|
|
3066
|
-
isDirectMessage,
|
|
3067
|
-
eventScope
|
|
3068
|
-
});
|
|
3069
|
-
const runtimeBindingThreadId = routedThreadId ?? (isDirectMessage && isThreadReply ? threadTs : void 0);
|
|
3070
|
-
const bindingRoute = resolveSlackConversationBindingRoute({
|
|
3071
|
-
cfg: ctx.cfg,
|
|
3072
|
-
route,
|
|
3073
|
-
accountId: account.accountId,
|
|
3074
|
-
baseConversationId,
|
|
3075
|
-
runtimeBindingThreadId,
|
|
3076
|
-
bindingsEnabled: !eventScope
|
|
3077
|
-
});
|
|
3078
|
-
const runtimeRoute = bindingRoute.runtimeRoute;
|
|
3079
|
-
const configuredBinding = bindingRoute.configuredRoute?.bindingResolution ?? null;
|
|
3080
|
-
const configuredBindingSessionKey = bindingRoute.configuredRoute?.boundSessionKey ?? "";
|
|
3081
|
-
route = bindingRoute.route;
|
|
3082
|
-
const threadKeys = runtimeRoute.boundSessionKey || configuredBindingSessionKey ? {
|
|
3083
|
-
sessionKey: route.sessionKey,
|
|
3084
|
-
parentSessionKey: void 0
|
|
3085
|
-
} : resolveThreadSessionKeys({
|
|
3086
|
-
baseSessionKey: route.sessionKey,
|
|
3087
|
-
threadId: routedThreadId,
|
|
3088
|
-
parentSessionKey: routedThreadId && ctx.threadInheritParent ? route.sessionKey : void 0
|
|
3089
|
-
});
|
|
3090
|
-
const sessionKey = threadKeys.sessionKey;
|
|
3091
|
-
const historyKey = isThreadReply && ctx.threadHistoryScope === "thread" ? sessionKey : eventScope ? `${account.accountId}:${eventScope.teamId}:${message.channel}` : message.channel;
|
|
3092
|
-
return {
|
|
3093
|
-
route,
|
|
3094
|
-
runtimeBinding: runtimeRoute.bindingRecord,
|
|
3095
|
-
runtimeBoundSessionKey: runtimeRoute.boundSessionKey,
|
|
3096
|
-
configuredBinding,
|
|
3097
|
-
configuredBindingSessionKey,
|
|
3098
|
-
chatType,
|
|
3099
|
-
replyToMode,
|
|
3100
|
-
threadContext,
|
|
3101
|
-
threadTs,
|
|
3102
|
-
isThreadReply,
|
|
3103
|
-
threadKeys,
|
|
3104
|
-
sessionKey,
|
|
3105
|
-
historyKey
|
|
3106
|
-
};
|
|
3107
|
-
}
|
|
3108
|
-
//#endregion
|
|
3109
2954
|
//#region extensions/slack/src/monitor/message-handler/prepare-thread-context-root.ts
|
|
3110
2955
|
function isSlackThreadAuthorCurrentBot(params) {
|
|
3111
2956
|
const { identity, author } = params;
|
|
@@ -3161,7 +3006,7 @@ function formatSlackThreadLabelSnippet(text) {
|
|
|
3161
3006
|
}
|
|
3162
3007
|
//#endregion
|
|
3163
3008
|
//#region extensions/slack/src/monitor/message-handler/prepare-thread-context.ts
|
|
3164
|
-
const loadSlackMediaModule = createLazyRuntimeModule(() => import("./actions-
|
|
3009
|
+
const loadSlackMediaModule = createLazyRuntimeModule(() => import("./actions-CFkvEv1d.js").then((n) => n._));
|
|
3165
3010
|
const SLACK_THREAD_CONTEXT_USER_LOOKUP_CONCURRENCY = 4;
|
|
3166
3011
|
function resolveSlackThreadSessionFreshness(params) {
|
|
3167
3012
|
return params.ctx.channelRuntime?.session?.resolveEntryResetFreshness?.({
|
|
@@ -3534,25 +3379,12 @@ async function restoreSlackAssistantThreadContextFromMetadata(params) {
|
|
|
3534
3379
|
const parentUserId = params.message.parent_user_id?.trim();
|
|
3535
3380
|
if (!params.message.channel || !threadTs || !parentUserId || parentUserId !== params.ctx.botUserId && parentUserId !== params.ctx.botId) return;
|
|
3536
3381
|
try {
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
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
|
|
3543
3387
|
});
|
|
3544
|
-
for (const message of response.messages ?? []) {
|
|
3545
|
-
const context = parseSlackAssistantThreadMetadata(message.metadata);
|
|
3546
|
-
if (!context) continue;
|
|
3547
|
-
return {
|
|
3548
|
-
assistantChannelId: params.message.channel,
|
|
3549
|
-
threadTs,
|
|
3550
|
-
userId: params.message.user,
|
|
3551
|
-
channelId: context.channelId,
|
|
3552
|
-
teamId: context.teamId,
|
|
3553
|
-
enterpriseId: context.enterpriseId
|
|
3554
|
-
};
|
|
3555
|
-
}
|
|
3556
3388
|
} catch (err) {
|
|
3557
3389
|
logVerbose(`slack assistant context restore failed channel=${params.message.channel} ts=${threadTs}: ${formatErrorMessage(err)}`);
|
|
3558
3390
|
}
|
|
@@ -4262,7 +4094,7 @@ async function prepareSlackMessage(params) {
|
|
|
4262
4094
|
const statusReactionsExplicitlyEnabled = cfg.messages?.statusReactions?.enabled === true;
|
|
4263
4095
|
const isRoomEvent = inboundEventKind === "room_event";
|
|
4264
4096
|
const shouldAckReaction$1 = () => Boolean(ackReaction && shouldAckReaction({
|
|
4265
|
-
scope:
|
|
4097
|
+
scope: cfg.messages?.ackReactionScope,
|
|
4266
4098
|
inboundEventKind,
|
|
4267
4099
|
isDirect: isDirectMessage,
|
|
4268
4100
|
isGroup: isRoomish,
|
|
@@ -4310,10 +4142,11 @@ async function prepareSlackMessage(params) {
|
|
|
4310
4142
|
const textWithId = `${bodyForAgent}\n[slack message id: ${message.ts} channel: ${message.channel}${threadInfo}]`;
|
|
4311
4143
|
const storePath = resolveStorePath(ctx.cfg.session?.store, { agentId: route.agentId });
|
|
4312
4144
|
const envelopeOptions = resolveEnvelopeFormatOptions(ctx.cfg);
|
|
4313
|
-
const
|
|
4145
|
+
const sessionEntry = getSessionEntry({
|
|
4314
4146
|
storePath,
|
|
4315
4147
|
sessionKey
|
|
4316
4148
|
});
|
|
4149
|
+
const previousTimestamp = sessionEntry?.updatedAt;
|
|
4317
4150
|
if (opts.source === "app_mention" && !ctx.botUserId && message.ts) {
|
|
4318
4151
|
const pendingHistory = ctx.channelHistories.get(historyKey);
|
|
4319
4152
|
if (pendingHistory) ctx.channelHistories.set(historyKey, pendingHistory.filter((entry) => entry.messageId !== message.ts));
|
|
@@ -4547,6 +4380,7 @@ async function prepareSlackMessage(params) {
|
|
|
4547
4380
|
channelConfig,
|
|
4548
4381
|
replyTarget,
|
|
4549
4382
|
ctxPayload,
|
|
4383
|
+
sessionDisplayName: sessionEntry?.displayName,
|
|
4550
4384
|
turn: {
|
|
4551
4385
|
storePath,
|
|
4552
4386
|
record: {
|