@openclaw/slack 2026.7.2-beta.7 → 2026.8.1-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{account-inspect-DEKpg9Ip.js → account-inspect-Drw2gAiN.js} +1 -1
- package/dist/account-inspect-api.js +1 -1
- package/dist/{accounts-CHAeNZOX.js → accounts-CK4AVI2_.js} +2 -2
- package/dist/{accounts.runtime-BHD4Ma2R.js → accounts.runtime-Cb7QwWBz.js} +1 -1
- package/dist/{action-runtime-DfuYoMjG.js → action-runtime-BkGe7ny_.js} +148 -76
- package/dist/action-runtime.runtime-D9I6uc0Q.js +2 -0
- package/dist/{actions-D3NuJvva.js → actions-BTDXwwE3.js} +85 -44
- package/dist/actions.runtime-DLFyUkvg.js +2 -0
- package/dist/api.js +15 -15
- package/dist/{approval-handler.runtime-BA05P0QT.js → approval-handler.runtime-CJaNzAq2.js} +51 -14
- package/dist/{block-text-DkHaoGem.js → block-text-YtiNkqmx.js} +1 -1
- package/dist/{channel-B9fVdW3s.js → channel-D_tovcUL.js} +151 -91
- package/dist/channel-config-api.js +1 -1
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel-type-DXyos1d2.js → channel-type-B9MSdoGN.js} +9 -8
- package/dist/{config-adapter-CxBQH8fq.js → channel.setup-Bch9dYmA.js} +83 -25
- package/dist/{config-schema-SgZT6nXV.js → config-schema-xto5jFIx.js} +5 -11
- package/dist/{directory-config-CnCIAktf.js → directory-config-CNBz8lJa.js} +3 -3
- package/dist/directory-contract-api.js +1 -1
- package/dist/{directory-live-DKunWh3A.js → directory-live-CFKUtW9E.js} +13 -22
- package/dist/{doctor-contract-CsGH1Dfc.js → doctor-contract-BAy7-EUQ.js} +30 -3
- package/dist/doctor-contract-api.js +1 -1
- package/dist/{group-policy-NQZti9t-.js → group-policy-c4RP9bjc.js} +248 -163
- package/dist/index.js +4 -1
- package/dist/{message-tool-api-DkhFpMoy.js → message-tool-api-BykQB4AU.js} +14 -3
- package/dist/message-tool-api.js +1 -1
- package/dist/{monitor-CmKz0wtI.js → monitor-1nF1NtqT.js} +3 -3
- package/dist/{outbound-adapter-BkuMp2Vs.js → outbound-adapter-S81tsswN.js} +19 -17
- package/dist/{pipeline.runtime-DgtmuYCP.js → pipeline.runtime-BvYsM5JO.js} +1087 -682
- package/dist/{policy-PRysPn23.js → policy-TapwbeaT.js} +18 -14
- package/dist/{probe-CgDsSops.js → probe-HoEg_WxB.js} +38 -18
- package/dist/{provider-DmRizMZt.js → provider-CDNvBXUi.js} +3422 -2590
- package/dist/{relay-source-Blxj8xb4.js → relay-source-C9leF11u.js} +11 -21
- package/dist/{replies-4s_Vq_z9.js → replies-CeOfv82I.js} +52 -6
- package/dist/{resolve-channels-yEqmM-3I.js → resolve-channels-CNPrxaXY.js} +39 -12
- package/dist/{resolve-users-BOrt5K-5.js → resolve-users-Dzomuwnr.js} +28 -5
- package/dist/{runtime-api-QMb-Q5Ko.js → runtime-api-Cj9nxBNv.js} +3 -3
- package/dist/runtime-api.js +11 -11
- package/dist/{scopes-BJSrkAp6.js → scopes-cYXKkrRD.js} +1 -1
- package/dist/{send-DsLeZpST.js → send-Nor0iBwY.js} +211 -105
- package/dist/{send.runtime-gUdK6V3h.js → send.runtime-BmgNe6S9.js} +1 -1
- package/dist/setup-entry.js +1 -4
- package/dist/setup-plugin-api.js +1 -1
- package/dist/{setup-surface-BeNzrMrJ.js → setup-surface-CbX1-Knh.js} +6 -6
- package/dist/{shared-DgIN3Dcr.js → shared-zdT5AMp-.js} +36 -42
- package/dist/{slash-dispatch.runtime-zPkyl7dB.js → slash-dispatch.runtime-Bnr9GR1S.js} +1 -1
- package/dist/{streaming-compat-3_X-21wW.js → streaming-compat-B-e0mqM0.js} +3 -8
- package/dist/{target-parsing-N6lHogYZ.js → target-parsing-BnMD2ZqZ.js} +40 -2
- package/dist/{targets-KmjP2E8s.js → targets-Cx5W_n3W.js} +1 -1
- package/node_modules/@slack/bolt/node_modules/@types/node/README.md +1 -1
- package/node_modules/@slack/bolt/node_modules/@types/node/crypto.d.ts +1 -1
- package/node_modules/@slack/bolt/node_modules/@types/node/package.json +2 -2
- package/node_modules/@slack/socket-mode/node_modules/@types/node/README.md +1 -1
- package/node_modules/@slack/socket-mode/node_modules/@types/node/crypto.d.ts +1 -1
- package/node_modules/@slack/socket-mode/node_modules/@types/node/package.json +2 -2
- package/node_modules/@slack/web-api/node_modules/@types/node/README.md +1 -1
- package/node_modules/@slack/web-api/node_modules/@types/node/crypto.d.ts +1 -1
- package/node_modules/@slack/web-api/node_modules/@types/node/package.json +2 -2
- package/node_modules/undici/lib/cache/memory-cache-store.js +54 -9
- package/node_modules/undici/lib/cache/sqlite-cache-store.js +7 -1
- package/node_modules/undici/lib/core/request.js +12 -1
- package/node_modules/undici/lib/dispatcher/client-h1.js +11 -2
- package/node_modules/undici/lib/handler/cache-handler.js +246 -64
- package/node_modules/undici/lib/handler/cache-revalidation-handler.js +3 -3
- package/node_modules/undici/lib/handler/retry-handler.js +24 -0
- package/node_modules/undici/lib/interceptor/cache.js +159 -52
- package/node_modules/undici/lib/util/cache.js +324 -62
- package/node_modules/undici/lib/util/date.js +23 -6
- package/node_modules/undici/lib/web/cookies/util.js +79 -9
- package/node_modules/undici/package.json +1 -1
- package/openclaw.plugin.json +239 -34
- package/package.json +9 -9
- package/skills/slack/SKILL.md +10 -68
- package/dist/action-runtime.runtime-DQErZYpz.js +0 -2
- package/dist/actions.runtime-BIRU_G4M.js +0 -2
- package/dist/channel.setup-CLP5oTvh.js +0 -56
- package/dist/preflight-audio.runtime-D5Bgags0.js +0 -3
- package/dist/slash-plugin-commands.runtime-Dosfcx3X.js +0 -2
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { t as resolveSlackReplyToMode } from "./account-reply-mode-DrUXogvO.js";
|
|
2
|
-
import {
|
|
3
|
-
import "./targets-
|
|
4
|
-
import {
|
|
5
|
-
import { r as formatSlackError } from "./probe-
|
|
6
|
-
import { n as
|
|
7
|
-
import {
|
|
8
|
-
import { a as resolveSlackMessageText } from "./block-text-
|
|
9
|
-
import { c as
|
|
10
|
-
import { C as stripSlackMentionsForCommandDetection, _ as
|
|
11
|
-
import { n as resolveSlackChannelConfig } from "./policy-
|
|
12
|
-
import { a as resolveDeliveredSlackReplyThreadTs, i as readSlackReplyBlocks, n as deliverReplies, o as resolveSlackThreadTs, s as emitSlackMessageSentHooks, t as createSlackReplyDeliveryPlan } from "./replies-
|
|
2
|
+
import { a as parseSlackTarget, n as formatSlackTarget } from "./target-parsing-BnMD2ZqZ.js";
|
|
3
|
+
import "./targets-Cx5W_n3W.js";
|
|
4
|
+
import { B as SLACK_TEXT_LIMIT, G as truncateSlackText, K as truncateSlackTextByUtf8Bytes, L as SLACK_EDIT_TEXT_MAX_BYTES, M as normalizeSlackOutboundText, N as buildSlackBlocksFallbackText, T as hasSlackNativeDataBlock, W as countSlackTextUtf8Bytes, Z as SLACK_SESSION_LINK_ACTION_ID, at as escapeSlackMrkdwn, c as normalizeSlackAllowOwnerEntry, s as normalizeAllowListLower, u as resolveSlackAllowListMatch, y as resolveSlackReplyRenderPlan } from "./group-policy-c4RP9bjc.js";
|
|
5
|
+
import { r as formatSlackError } from "./probe-HoEg_WxB.js";
|
|
6
|
+
import { n as resolveSlackStreamingMode, t as resolveSlackNativeStreaming } from "./streaming-compat-B-e0mqM0.js";
|
|
7
|
+
import { b as formatSlackFileReference, i as editSlackRenderedMessage, p as removeSlackReaction, r as editSlackMessage, t as deleteSlackMessage, u as reactSlackMessage, v as resolveSlackThreadHistory, x as buildSlackEditTextPayload, y as resolveSlackThreadStarter } from "./actions-BTDXwwE3.js";
|
|
8
|
+
import { a as resolveSlackMessageText } from "./block-text-YtiNkqmx.js";
|
|
9
|
+
import { c as hasSlackThreadFailureNotice, d as recordSlackThreadFailureNotice, f as recordSlackThreadParticipation, l as hasSlackThreadParticipation, o as clearSlackThreadFailureNotice, p as buildSlackNativeDataDeliveryPlan, r as sendMessageSlack, u as hasSlackThreadParticipationWithPersistence } from "./send-Nor0iBwY.js";
|
|
10
|
+
import { C as resolveChannelResetConfig, D as stripSlackMentionsForCommandDetection, S as resolveChannelContextVisibilityMode, T as updateLastRoute, _ as normalizeSlackChannelType, b as parseSlackAssistantThreadMetadata, c as resolveConversationLabel$1, d as resolveSlackEffectiveAllowFrom, g as resolveSlackEnterpriseMainDmSessionKey, h as qualifySlackRoutePeerId, l as authorizeSlackBotRoomMessage, m as qualifySlackConversationId, o as authorizeSlackDirectMessage, r as resolveSlackRoomContextHints, s as trackSlackDraftMessage, u as resolveSlackCommandIngress, v as resolveSlackChatType, w as resolveStorePath, x as readSessionUpdatedAt, y as buildSlackAssistantThreadMetadata } from "./provider-CDNvBXUi.js";
|
|
11
|
+
import { n as resolveSlackChannelConfig } from "./policy-TapwbeaT.js";
|
|
12
|
+
import { a as resolveDeliveredSlackReplyThreadTs, i as readSlackReplyBlocks, n as deliverReplies, o as resolveSlackThreadTs, s as emitSlackMessageSentHooks, t as createSlackReplyDeliveryPlan } from "./replies-CeOfv82I.js";
|
|
13
13
|
import { asOptionalRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
14
14
|
import { resolveAgentRoute, resolveInboundLastRouteSessionKey, resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
|
|
15
|
-
import { buildChannelProgressDraftLine, buildChannelProgressDraftLineForEntry, createChannelMessageReplyPipeline, createChannelProgressDraftCompositor, createChannelProgressReceiptTracker,
|
|
15
|
+
import { buildChannelProgressDraftLine, buildChannelProgressDraftLineForEntry, createChannelMessageReplyPipeline, createChannelProgressDraftCompositor, createChannelProgressReceiptTracker, createFinalizableDraftStreamControlsForState, defineFinalizableLivePreviewAdapter, deliverWithFinalizableLivePreviewAdapter, formatChannelProgressDraftText, formatPlanChecklistLines, isChannelProgressDraftWorkToolName, resolveAgentOutboundIdentity, resolveChannelMessageSourceReplyDeliveryMode, resolveChannelProgressDraftConfig, resolveChannelProgressDraftMaxLineChars, resolveChannelStreamingBlockEnabled, resolveChannelStreamingPreviewToolProgress, resolveChannelStreamingSuppressDefaultToolProgressMessages } from "openclaw/plugin-sdk/channel-outbound";
|
|
16
16
|
import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
|
17
17
|
import { danger, logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env";
|
|
18
18
|
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
@@ -20,21 +20,24 @@ import { createHash } from "node:crypto";
|
|
|
20
20
|
import fs from "node:fs/promises";
|
|
21
21
|
import { formatErrorMessage, toErrorObject } from "openclaw/plugin-sdk/error-runtime";
|
|
22
22
|
import { asDateTimestampMs, asFiniteNumberInRange, parseStrictFiniteNumber, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
|
|
23
|
-
import { buildTtsSupplementMediaPayload, getReplyPayloadTtsSupplement, resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
|
|
23
|
+
import { buildTtsSupplementMediaPayload, getReplyPayloadTtsSupplement, isReplyPayloadNonTerminalToolErrorWarning, resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
|
|
24
24
|
import { filterSupplementalContextItems, resolvePinnedMainDmOwnerFromAllowlist, shouldIncludeSupplementalContext } from "openclaw/plugin-sdk/security-runtime";
|
|
25
|
+
import { mimeTypeFromFilePath } from "openclaw/plugin-sdk/media-mime";
|
|
25
26
|
import { ensureConfiguredBindingRouteReady, resolveConfiguredBindingRoute, resolveRuntimeConversationBindingRoute } from "openclaw/plugin-sdk/conversation-runtime";
|
|
26
27
|
import { createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
|
|
27
|
-
import { resolveDefaultAgentId, resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
|
|
28
28
|
import { mergePairLoopGuardConfig } from "openclaw/plugin-sdk/pair-loop-guard-runtime";
|
|
29
|
-
import {
|
|
30
|
-
import { getGlobalHookRunner } from "openclaw/plugin-sdk/plugin-runtime";
|
|
29
|
+
import { enqueueRoutedSystemEvent } from "openclaw/plugin-sdk/system-event-runtime";
|
|
31
30
|
import { resolveChannelImplicitMentions } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
32
|
-
import {
|
|
31
|
+
import { getGlobalHookRunner } from "openclaw/plugin-sdk/plugin-runtime";
|
|
32
|
+
import { buildChannelInboundEventContext, buildMentionRegexes, classifyChannelInboundEvent, dispatchChannelInboundTurn, formatInboundEnvelope, formatInboundMediaUnavailableText, hasVisibleInboundReplyDispatch, implicitMentionKindWhen, logInboundDrop, matchesMentionWithExplicit, readAgentRunTerminalOutcome, recordDroppedChannelInboundHistory, resolveEnvelopeFormatOptions, resolveInboundMentionDecision, resolveInboundSupplementalSenderAllowed, resolveUnmentionedGroupInboundPolicy, toInboundMediaFactsWithMetadata } from "openclaw/plugin-sdk/channel-inbound";
|
|
33
|
+
import { resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
|
|
34
|
+
import { resolveGatewayPublicOrigin } from "openclaw/plugin-sdk/config-contracts";
|
|
35
|
+
import { buildControlUiSessionPath } from "openclaw/plugin-sdk/session-discussion";
|
|
33
36
|
import { DEFAULT_TIMING, createStatusReactionController, logAckFailure, logTypingFailure, resolveAckReaction, shouldAckReaction } from "openclaw/plugin-sdk/channel-feedback";
|
|
34
37
|
import { hasControlCommand } from "openclaw/plugin-sdk/command-detection";
|
|
35
38
|
import { isAbortRequestText } from "openclaw/plugin-sdk/command-primitives-runtime";
|
|
36
39
|
import { shouldHandleTextCommands } from "openclaw/plugin-sdk/command-surface";
|
|
37
|
-
import {
|
|
40
|
+
import { createChannelPreflightAudio } from "openclaw/plugin-sdk/media-understanding-runtime";
|
|
38
41
|
import { runTasksWithConcurrency } from "openclaw/plugin-sdk/concurrency-runtime";
|
|
39
42
|
//#region extensions/slack/src/streaming.ts
|
|
40
43
|
/**
|
|
@@ -133,7 +136,7 @@ async function appendSlackStream(params) {
|
|
|
133
136
|
* Stop (finalize) a Slack stream.
|
|
134
137
|
*
|
|
135
138
|
* After calling this the stream message becomes a normal Slack message.
|
|
136
|
-
* Optionally include final
|
|
139
|
+
* Optionally include final chunks to append before stopping.
|
|
137
140
|
*
|
|
138
141
|
* If Slack's `chat.stopStream` responds with a definitive recipient/channel
|
|
139
142
|
* rejection while text is still buffered locally, this function throws a
|
|
@@ -152,17 +155,15 @@ async function appendSlackStream(params) {
|
|
|
152
155
|
* caller can emit the `message_sent` hook with a populated `messageId`.
|
|
153
156
|
*/
|
|
154
157
|
async function stopSlackStream(params) {
|
|
155
|
-
const { session,
|
|
158
|
+
const { session, chunks, metadata } = params;
|
|
156
159
|
if (session.stopped) {
|
|
157
160
|
logVerbose("slack-stream: stream already stopped, ignoring duplicate stop");
|
|
158
161
|
return {};
|
|
159
162
|
}
|
|
160
163
|
session.stopped = true;
|
|
161
|
-
|
|
162
|
-
logVerbose(`slack-stream: stopping stream in ${session.channel} thread=${session.threadTs}${text ? ` (final text: ${text.length} chars)` : ""}`);
|
|
164
|
+
logVerbose(`slack-stream: stopping stream in ${session.channel} thread=${session.threadTs}`);
|
|
163
165
|
try {
|
|
164
|
-
const stopResponse = await session.streamer.stop(
|
|
165
|
-
...text ? { markdown_text: text } : {},
|
|
166
|
+
const stopResponse = await session.streamer.stop(chunks?.length || metadata ? {
|
|
166
167
|
...chunks?.length ? { chunks } : {},
|
|
167
168
|
...metadata ? { metadata } : {}
|
|
168
169
|
} : void 0);
|
|
@@ -212,7 +213,7 @@ function extractSlackErrorCode(err) {
|
|
|
212
213
|
return (typeof record.message === "string" ? record.message : "").match(/An API error occurred:\s*([a-z_][a-z0-9_]*)/i)?.[1];
|
|
213
214
|
}
|
|
214
215
|
function markSlackStreamFallbackDelivered(session) {
|
|
215
|
-
const nativeStreamWasStarted = session.delivered;
|
|
216
|
+
const nativeStreamWasStarted = session.delivered || Boolean(session.streamer.ts);
|
|
216
217
|
session.pendingText = "";
|
|
217
218
|
session.streamer.buffer = "";
|
|
218
219
|
session.stopped = !nativeStreamWasStarted;
|
|
@@ -256,12 +257,6 @@ function isSlackStreamingEnabled(params) {
|
|
|
256
257
|
if (params.mode === "progress") return params.nativeStreaming && params.nativeProgressTaskCards === true;
|
|
257
258
|
return false;
|
|
258
259
|
}
|
|
259
|
-
function shouldEnableSlackPreviewStreaming(params) {
|
|
260
|
-
return params.mode !== "off";
|
|
261
|
-
}
|
|
262
|
-
function shouldInitializeSlackDraftStream(params) {
|
|
263
|
-
return params.previewStreamingEnabled && !params.useStreaming;
|
|
264
|
-
}
|
|
265
260
|
function resolveSlackDisableBlockStreaming(params) {
|
|
266
261
|
if (params.useStreaming || params.shouldUseDraftStream) return true;
|
|
267
262
|
return typeof params.blockStreamingEnabled === "boolean" ? !params.blockStreamingEnabled : void 0;
|
|
@@ -274,9 +269,10 @@ function resolveExplicitSlackProgressTitle(entry) {
|
|
|
274
269
|
}
|
|
275
270
|
function resolveSlackNativeProgressTaskCards(entry) {
|
|
276
271
|
const streaming = entry?.streaming;
|
|
277
|
-
if (!streaming || typeof streaming !== "object" || Array.isArray(streaming)) return
|
|
272
|
+
if (!streaming || typeof streaming !== "object" || Array.isArray(streaming)) return true;
|
|
278
273
|
const progressConfig = streaming.progress;
|
|
279
|
-
|
|
274
|
+
if (!progressConfig || typeof progressConfig !== "object" || Array.isArray(progressConfig)) return true;
|
|
275
|
+
return progressConfig.nativeTaskCards !== false;
|
|
280
276
|
}
|
|
281
277
|
function resolveSlackStreamingThreadHint(params) {
|
|
282
278
|
return resolveSlackThreadTs({
|
|
@@ -386,21 +382,27 @@ function createSlackDraftStream(params) {
|
|
|
386
382
|
const remove = params.remove ?? deleteSlackMessage;
|
|
387
383
|
let streamMessageId;
|
|
388
384
|
let streamChannelId;
|
|
385
|
+
let untrackConversationBoundary;
|
|
386
|
+
let lastVisibleUpdate;
|
|
389
387
|
let lastSentKey = "";
|
|
390
|
-
|
|
391
|
-
|
|
388
|
+
const detachedMessages = [];
|
|
389
|
+
const finalizedMessageIds = /* @__PURE__ */ new Set();
|
|
390
|
+
const streamState = {
|
|
391
|
+
stopped: false,
|
|
392
|
+
final: false
|
|
393
|
+
};
|
|
392
394
|
const normalizeUpdate = (update) => typeof update === "string" ? { text: update } : update;
|
|
393
|
-
const sendOrEditStreamMessage = async (
|
|
394
|
-
if (stopped) return;
|
|
395
|
-
const
|
|
395
|
+
const sendOrEditStreamMessage = async (pending) => {
|
|
396
|
+
if (streamState.stopped) return;
|
|
397
|
+
const update = normalizeUpdate(pending);
|
|
398
|
+
const trimmed = update.text.trimEnd();
|
|
396
399
|
if (!trimmed) return;
|
|
397
400
|
if (trimmed.length > maxChars) {
|
|
398
|
-
stopped = true;
|
|
401
|
+
streamState.stopped = true;
|
|
399
402
|
params.warn?.(`slack stream preview stopped (text length ${trimmed.length} > ${maxChars})`);
|
|
400
403
|
return;
|
|
401
404
|
}
|
|
402
|
-
const
|
|
403
|
-
const blocks = update.text === text ? update.blocks : void 0;
|
|
405
|
+
const blocks = update.blocks;
|
|
404
406
|
const sentKey = `${trimmed}\n${blocks ? JSON.stringify(blocks) : ""}`;
|
|
405
407
|
if (sentKey === lastSentKey) return;
|
|
406
408
|
lastSentKey = sentKey;
|
|
@@ -413,56 +415,73 @@ function createSlackDraftStream(params) {
|
|
|
413
415
|
...params.eventScope ? { client: params.eventScope.client } : {},
|
|
414
416
|
...blocks ? { blocks } : {}
|
|
415
417
|
});
|
|
418
|
+
lastVisibleUpdate = {
|
|
419
|
+
text: trimmed,
|
|
420
|
+
...blocks ? { blocks } : {}
|
|
421
|
+
};
|
|
416
422
|
return;
|
|
417
423
|
}
|
|
424
|
+
const threadTs = params.resolveThreadTs?.();
|
|
425
|
+
const pendingBoundary = params.conversationChannelId ? trackSlackDraftMessage({
|
|
426
|
+
accountId: params.accountId,
|
|
427
|
+
teamId: params.eventScope?.teamId,
|
|
428
|
+
channelId: params.conversationChannelId,
|
|
429
|
+
threadTs,
|
|
430
|
+
onInterveningMessage: forceNewMessage
|
|
431
|
+
}) : void 0;
|
|
432
|
+
untrackConversationBoundary = pendingBoundary?.stop;
|
|
418
433
|
const sent = await send(params.target, trimmed, {
|
|
419
434
|
cfg: params.cfg,
|
|
420
435
|
token: params.token,
|
|
421
436
|
accountId: params.accountId,
|
|
422
|
-
threadTs
|
|
437
|
+
threadTs,
|
|
423
438
|
identity: params.identity,
|
|
424
|
-
|
|
425
|
-
client: params.eventScope.client,
|
|
426
|
-
enterpriseEventScope: params.eventScope
|
|
427
|
-
} : {},
|
|
439
|
+
eventScope: params.eventScope,
|
|
428
440
|
...params.metadata ? { metadata: params.metadata } : {},
|
|
429
441
|
...blocks ? { blocks } : {}
|
|
430
442
|
});
|
|
431
443
|
streamChannelId = sent.channelId || streamChannelId;
|
|
432
444
|
streamMessageId = sent.messageId || streamMessageId;
|
|
433
445
|
if (!streamChannelId || !streamMessageId) {
|
|
434
|
-
|
|
446
|
+
stopTrackingConversationBoundary();
|
|
447
|
+
streamState.stopped = true;
|
|
435
448
|
params.warn?.("slack stream preview stopped (missing identifiers from sendMessage)");
|
|
436
449
|
return;
|
|
437
450
|
}
|
|
438
|
-
|
|
451
|
+
lastVisibleUpdate = {
|
|
452
|
+
text: trimmed,
|
|
453
|
+
...blocks ? { blocks } : {}
|
|
454
|
+
};
|
|
455
|
+
if (pendingBoundary && params.conversationChannelId === streamChannelId) pendingBoundary.setMessageTs(streamMessageId);
|
|
456
|
+
else {
|
|
457
|
+
stopTrackingConversationBoundary();
|
|
458
|
+
untrackConversationBoundary = trackSlackDraftMessage({
|
|
459
|
+
accountId: params.accountId,
|
|
460
|
+
teamId: params.eventScope?.teamId,
|
|
461
|
+
channelId: streamChannelId,
|
|
462
|
+
threadTs,
|
|
463
|
+
messageTs: streamMessageId,
|
|
464
|
+
onInterveningMessage: forceNewMessage
|
|
465
|
+
}).stop;
|
|
466
|
+
}
|
|
439
467
|
} catch (err) {
|
|
440
|
-
|
|
468
|
+
stopTrackingConversationBoundary();
|
|
469
|
+
streamState.stopped = true;
|
|
441
470
|
params.warn?.(`slack stream preview failed: ${formatSlackError(err)}`);
|
|
442
471
|
}
|
|
443
472
|
};
|
|
444
|
-
const loop =
|
|
473
|
+
const { loop, update, discardPending, seal } = createFinalizableDraftStreamControlsForState({
|
|
445
474
|
throttleMs,
|
|
446
|
-
|
|
447
|
-
sendOrEditStreamMessage
|
|
475
|
+
state: streamState,
|
|
476
|
+
sendOrEditStreamMessage,
|
|
477
|
+
emptyValue: "",
|
|
478
|
+
isEmpty: (value) => !normalizeUpdate(value).text.trim()
|
|
448
479
|
});
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
|
|
480
|
+
const stopTrackingConversationBoundary = () => {
|
|
481
|
+
untrackConversationBoundary?.();
|
|
482
|
+
untrackConversationBoundary = void 0;
|
|
452
483
|
};
|
|
453
|
-
const
|
|
454
|
-
stop();
|
|
455
|
-
await loop.waitForInFlight();
|
|
456
|
-
};
|
|
457
|
-
const clear = async () => {
|
|
458
|
-
await discardPending();
|
|
459
|
-
const channelId = streamChannelId;
|
|
460
|
-
const messageId = streamMessageId;
|
|
461
|
-
streamChannelId = void 0;
|
|
462
|
-
streamMessageId = void 0;
|
|
463
|
-
lastSentKey = "";
|
|
464
|
-
pendingUpdate = void 0;
|
|
465
|
-
if (!channelId || !messageId) return;
|
|
484
|
+
const removeMessage = async (channelId, messageId) => {
|
|
466
485
|
try {
|
|
467
486
|
await remove(channelId, messageId, {
|
|
468
487
|
token: params.token,
|
|
@@ -473,26 +492,76 @@ function createSlackDraftStream(params) {
|
|
|
473
492
|
params.warn?.(`slack stream preview cleanup failed: ${formatSlackError(err)}`);
|
|
474
493
|
}
|
|
475
494
|
};
|
|
495
|
+
const clear = async () => {
|
|
496
|
+
stopTrackingConversationBoundary();
|
|
497
|
+
await discardPending();
|
|
498
|
+
const channelId = streamChannelId;
|
|
499
|
+
const messageId = streamMessageId;
|
|
500
|
+
streamChannelId = void 0;
|
|
501
|
+
streamMessageId = void 0;
|
|
502
|
+
lastVisibleUpdate = void 0;
|
|
503
|
+
lastSentKey = "";
|
|
504
|
+
if (!channelId || !messageId) return;
|
|
505
|
+
await removeMessage(channelId, messageId);
|
|
506
|
+
};
|
|
476
507
|
const forceNewMessage = () => {
|
|
508
|
+
stopTrackingConversationBoundary();
|
|
509
|
+
streamState.stopped = false;
|
|
510
|
+
streamState.final = false;
|
|
511
|
+
if (streamChannelId && streamMessageId && !finalizedMessageIds.has(streamMessageId)) detachedMessages.push({
|
|
512
|
+
channelId: streamChannelId,
|
|
513
|
+
messageId: streamMessageId
|
|
514
|
+
});
|
|
477
515
|
streamMessageId = void 0;
|
|
478
516
|
streamChannelId = void 0;
|
|
517
|
+
lastVisibleUpdate = void 0;
|
|
479
518
|
lastSentKey = "";
|
|
480
|
-
pendingUpdate = void 0;
|
|
481
519
|
loop.resetPending();
|
|
482
520
|
};
|
|
521
|
+
const dropDetachedMessages = async () => {
|
|
522
|
+
let message;
|
|
523
|
+
while ((message = detachedMessages.shift()) !== void 0) {
|
|
524
|
+
const { channelId, messageId } = message;
|
|
525
|
+
await removeMessage(channelId, messageId);
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
const discardPendingAndStopTracking = async () => {
|
|
529
|
+
stopTrackingConversationBoundary();
|
|
530
|
+
await discardPending();
|
|
531
|
+
};
|
|
532
|
+
const finalizeMessage = async (messageId, editFinal) => {
|
|
533
|
+
const channelId = streamChannelId;
|
|
534
|
+
const previousUpdate = lastVisibleUpdate;
|
|
535
|
+
if (!channelId || streamMessageId !== messageId || !previousUpdate) return false;
|
|
536
|
+
await editFinal();
|
|
537
|
+
if (streamChannelId === channelId && streamMessageId === messageId) {
|
|
538
|
+
finalizedMessageIds.add(messageId);
|
|
539
|
+
stopTrackingConversationBoundary();
|
|
540
|
+
return true;
|
|
541
|
+
}
|
|
542
|
+
try {
|
|
543
|
+
await edit(channelId, messageId, previousUpdate.text, {
|
|
544
|
+
cfg: params.cfg,
|
|
545
|
+
token: params.token,
|
|
546
|
+
accountId: params.accountId,
|
|
547
|
+
...params.eventScope ? { client: params.eventScope.client } : {},
|
|
548
|
+
...previousUpdate.blocks ? { blocks: previousUpdate.blocks } : {}
|
|
549
|
+
});
|
|
550
|
+
} catch (err) {
|
|
551
|
+
params.warn?.(`slack stream preview restore failed: ${formatSlackError(err)}`);
|
|
552
|
+
}
|
|
553
|
+
return false;
|
|
554
|
+
};
|
|
483
555
|
params.log?.(`slack stream preview ready (maxChars=${maxChars}, throttleMs=${throttleMs})`);
|
|
484
556
|
return {
|
|
485
|
-
update
|
|
486
|
-
const normalized = normalizeUpdate(update);
|
|
487
|
-
pendingUpdate = update;
|
|
488
|
-
loop.update(normalized.text);
|
|
489
|
-
},
|
|
557
|
+
update,
|
|
490
558
|
flush: loop.flush,
|
|
491
559
|
clear,
|
|
492
|
-
discardPending,
|
|
493
|
-
seal
|
|
494
|
-
stop,
|
|
560
|
+
discardPending: discardPendingAndStopTracking,
|
|
561
|
+
seal,
|
|
495
562
|
forceNewMessage,
|
|
563
|
+
dropDetachedMessages,
|
|
564
|
+
finalizeMessage,
|
|
496
565
|
messageId: () => streamMessageId,
|
|
497
566
|
channelId: () => streamChannelId
|
|
498
567
|
};
|
|
@@ -505,6 +574,14 @@ const DEFAULT_SLACK_PROGRESS_TASK_DETAIL_MAX_CHARS = 48;
|
|
|
505
574
|
const SLACK_PROGRESS_CHUNK_TEXT_MAX = 256;
|
|
506
575
|
const SLACK_PROGRESS_TASK_TITLE_MAX = 120;
|
|
507
576
|
const SLACK_PROGRESS_PLAN_FALLBACK_TITLE = "Thinking";
|
|
577
|
+
const SLACK_PROGRESS_LINE_DELTA_RE = /(?:^|\s)\+(\d+)\s+[−-](\d+)(?=\s|$)/u;
|
|
578
|
+
function buildSessionSources(url) {
|
|
579
|
+
return [{
|
|
580
|
+
type: "url_source",
|
|
581
|
+
url,
|
|
582
|
+
text: "Open in OpenClaw"
|
|
583
|
+
}];
|
|
584
|
+
}
|
|
508
585
|
function field(text) {
|
|
509
586
|
return {
|
|
510
587
|
type: "mrkdwn",
|
|
@@ -535,18 +612,29 @@ function lineDetailParts(line) {
|
|
|
535
612
|
function legacyLineTitle(line) {
|
|
536
613
|
return `${line.icon ?? "•"} *${escapeSlackMrkdwn(line.label)}*`;
|
|
537
614
|
}
|
|
615
|
+
function isAuthoredProgressLine(line) {
|
|
616
|
+
return line.id === "reasoning" || line.id?.startsWith("commentary:") === true;
|
|
617
|
+
}
|
|
538
618
|
function legacyLineDetail(line, maxChars) {
|
|
539
619
|
const detail = lineDetailParts(line).join(" · ");
|
|
540
|
-
|
|
620
|
+
if (detail) return escapeSlackMrkdwn(compactDetail(detail, maxChars));
|
|
621
|
+
if (isAuthoredProgressLine(line)) return normalizeSlackOutboundText(compactDetail(line.text.replace(/^(?:🧠|💬)\s+/u, ""), maxChars));
|
|
622
|
+
return "—";
|
|
541
623
|
}
|
|
542
|
-
function lineTaskTitle(line
|
|
543
|
-
const label = line.label.replace(/\s+/g, " ").trim() || line.toolName || line.kind || "Update";
|
|
544
|
-
const detail = lineDetailParts(line).join(" · ") || line.status?.trim();
|
|
624
|
+
function lineTaskTitle(line) {
|
|
625
|
+
const label = (line.kind === "command-output" ? line.toolName : void 0) || line.label.replace(/\s+/g, " ").trim() || line.toolName || line.kind || "Update";
|
|
545
626
|
const fallback = line.text.replace(/\s+/g, " ").trim();
|
|
546
|
-
if (
|
|
547
|
-
if (fallback && fallback !== label) return compactTitle(fallback);
|
|
627
|
+
if (fallback && fallback !== label) return compactTitle(lineDetailParts(line).length > 0 || line.status ? label : fallback);
|
|
548
628
|
return compactTitle(label);
|
|
549
629
|
}
|
|
630
|
+
function lineTaskDetails(line, maxLineChars) {
|
|
631
|
+
const detail = (lineDetailParts(line).join(" · ") || line.status?.trim())?.replace(SLACK_PROGRESS_LINE_DELTA_RE, "").replace(/\s+·\s*$/u, "").trim();
|
|
632
|
+
return detail ? compactDetail(detail, maxLineChars) : void 0;
|
|
633
|
+
}
|
|
634
|
+
function lineTaskOutput(line) {
|
|
635
|
+
const match = SLACK_PROGRESS_LINE_DELTA_RE.exec(lineDetailParts(line).join(" · "));
|
|
636
|
+
return match ? `+${match[1]} −${match[2]}` : void 0;
|
|
637
|
+
}
|
|
550
638
|
function lineTaskStatus(line) {
|
|
551
639
|
const normalized = line.status?.replace(/\s+/g, " ").trim().toLowerCase();
|
|
552
640
|
if (!normalized) return "in_progress";
|
|
@@ -594,15 +682,20 @@ function buildPlanTasks(params) {
|
|
|
594
682
|
contentIdOccurrences.set(id, occurrence);
|
|
595
683
|
id = `${id}_${occurrence}`;
|
|
596
684
|
}
|
|
597
|
-
|
|
685
|
+
const details = lineTaskDetails(line, maxLineChars);
|
|
686
|
+
const output = lineTaskOutput(line);
|
|
687
|
+
const task = {
|
|
598
688
|
id,
|
|
599
|
-
title: lineTaskTitle(line
|
|
689
|
+
title: lineTaskTitle(line),
|
|
600
690
|
status: lineTaskStatus(line)
|
|
601
691
|
};
|
|
692
|
+
if (details) task.details = details;
|
|
693
|
+
if (output) task.output = output;
|
|
694
|
+
return task;
|
|
602
695
|
});
|
|
603
696
|
}
|
|
604
697
|
function resolvePlanTitle(params) {
|
|
605
|
-
return compactChunkText(params.title?.trim() || params.label?.trim() || params.tasks.at(-1)?.title || SLACK_PROGRESS_PLAN_FALLBACK_TITLE);
|
|
698
|
+
return compactChunkText(params.title?.trim() || params.label?.trim() || (params.tasks.at(-1)?.details ? `${params.tasks.at(-1)?.title} — ${params.tasks.at(-1)?.details}` : params.tasks.at(-1)?.title) || SLACK_PROGRESS_PLAN_FALLBACK_TITLE);
|
|
606
699
|
}
|
|
607
700
|
function buildSlackProgressStreamChunks(params) {
|
|
608
701
|
const tasks = buildPlanTasks({
|
|
@@ -612,38 +705,92 @@ function buildSlackProgressStreamChunks(params) {
|
|
|
612
705
|
});
|
|
613
706
|
if (tasks.length === 0) {
|
|
614
707
|
const title = params.title?.trim() || params.label?.trim();
|
|
615
|
-
|
|
708
|
+
if (!title) return;
|
|
709
|
+
if (!params.sessionUrl && !params.diffStat) return [{
|
|
710
|
+
type: "plan_update",
|
|
711
|
+
title: compactChunkText(title)
|
|
712
|
+
}];
|
|
713
|
+
return [{
|
|
616
714
|
type: "plan_update",
|
|
617
715
|
title: compactChunkText(title)
|
|
618
|
-
}
|
|
716
|
+
}, {
|
|
717
|
+
type: "task_update",
|
|
718
|
+
id: "openclaw_summary",
|
|
719
|
+
title: "Completed",
|
|
720
|
+
status: "complete",
|
|
721
|
+
...formatTaskDiffOutput(params.diffStat) ? { output: formatTaskDiffOutput(params.diffStat) } : {},
|
|
722
|
+
...params.sessionUrl ? { sources: buildSessionSources(params.sessionUrl) } : {}
|
|
723
|
+
}];
|
|
619
724
|
}
|
|
725
|
+
const title = resolvePlanTitle({
|
|
726
|
+
label: params.label,
|
|
727
|
+
title: params.title,
|
|
728
|
+
tasks
|
|
729
|
+
});
|
|
730
|
+
const finalTaskIndex = tasks.length - 1;
|
|
731
|
+
const diffOutput = formatTaskDiffOutput(params.diffStat);
|
|
732
|
+
const taskChunks = tasks.map((task, index) => {
|
|
733
|
+
const chunk = {
|
|
734
|
+
type: "task_update",
|
|
735
|
+
id: task.id,
|
|
736
|
+
title: task.title,
|
|
737
|
+
status: task.status === "in_progress" ? params.finalInProgressStatus ?? (params.completeInProgress ? "complete" : task.status) : task.status
|
|
738
|
+
};
|
|
739
|
+
if (task.details) chunk.details = task.details;
|
|
740
|
+
if (task.output) chunk.output = task.output;
|
|
741
|
+
if (index === finalTaskIndex && diffOutput) chunk.output = diffOutput;
|
|
742
|
+
if (index === finalTaskIndex && params.sessionUrl) chunk.sources = buildSessionSources(params.sessionUrl);
|
|
743
|
+
return chunk;
|
|
744
|
+
});
|
|
620
745
|
return [{
|
|
621
746
|
type: "plan_update",
|
|
622
|
-
title
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
747
|
+
title
|
|
748
|
+
}, ...taskChunks];
|
|
749
|
+
}
|
|
750
|
+
function formatDiffStat(diffStat) {
|
|
751
|
+
if (!diffStat || diffStat.files === 0 && diffStat.added === 0 && diffStat.removed === 0) return;
|
|
752
|
+
return [
|
|
753
|
+
`📝 ${diffStat.files} files`,
|
|
754
|
+
...diffStat.added > 0 ? [`+${diffStat.added}`] : [],
|
|
755
|
+
...diffStat.removed > 0 ? [`−${diffStat.removed}`] : []
|
|
756
|
+
].join(" ");
|
|
757
|
+
}
|
|
758
|
+
function formatTaskDiffOutput(diffStat) {
|
|
759
|
+
return diffStat && (diffStat.added > 0 || diffStat.removed > 0) ? `+${diffStat.added} −${diffStat.removed}` : void 0;
|
|
760
|
+
}
|
|
761
|
+
function buildActivityText(lines, maxLineChars) {
|
|
762
|
+
const rendered = [];
|
|
763
|
+
let length = 0;
|
|
764
|
+
for (const line of lines.slice(-50).toReversed()) {
|
|
765
|
+
const row = `${legacyLineTitle(line)} — ${legacyLineDetail(line, maxLineChars)}`;
|
|
766
|
+
const nextLength = length + row.length + (rendered.length > 0 ? 1 : 0);
|
|
767
|
+
if (nextLength > SLACK_PROGRESS_FIELD_MAX) break;
|
|
768
|
+
rendered.push(row);
|
|
769
|
+
length = nextLength;
|
|
770
|
+
}
|
|
771
|
+
return rendered.toReversed().join("\n");
|
|
772
|
+
}
|
|
773
|
+
function buildSlackProgressCardBlocks(params) {
|
|
636
774
|
const maxLineChars = resolveMaxLineChars(params.maxLineChars, DEFAULT_SLACK_PROGRESS_DETAIL_MAX_CHARS);
|
|
637
775
|
const planLines = formatPlanChecklistLines(params.plan ?? [], {
|
|
638
776
|
maxLines: 50,
|
|
639
777
|
maxLineChars
|
|
640
778
|
});
|
|
641
779
|
const narration = params.narration?.replace(/\s+/g, " ").trim();
|
|
642
|
-
const
|
|
643
|
-
|
|
780
|
+
const activityText = buildActivityText(params.lines, maxLineChars);
|
|
781
|
+
const diffStat = formatDiffStat(params.diffStat);
|
|
782
|
+
const workingFooter = [
|
|
783
|
+
...params.toolCalls && params.toolCalls > 0 ? [`🛠️ ${params.toolCalls} tools`] : [],
|
|
784
|
+
...diffStat ? [diffStat] : [],
|
|
785
|
+
...params.elapsedSeconds && params.elapsedSeconds > 0 ? [`⏱ ${params.elapsedSeconds}s`] : []
|
|
786
|
+
].join(" · ");
|
|
787
|
+
const terminalFooter = [params.receiptSummary?.trim(), diffStat].filter(Boolean).join(" · ");
|
|
788
|
+
const footer = params.state === "working" ? workingFooter : terminalFooter;
|
|
789
|
+
return [
|
|
790
|
+
{
|
|
644
791
|
type: "section",
|
|
645
|
-
text: field(
|
|
646
|
-
}
|
|
792
|
+
text: field(`${params.state === "working" ? "🔄" : params.state === "success" ? "✅" : "❌"} *${escapeSlackMrkdwn(params.title.trim() || "Working")}*`)
|
|
793
|
+
},
|
|
647
794
|
...narration ? [{
|
|
648
795
|
type: "section",
|
|
649
796
|
text: field(`_${escapeSlackMrkdwn(narration)}_`)
|
|
@@ -651,14 +798,28 @@ function buildSlackProgressDraftBlocks(params) {
|
|
|
651
798
|
...planLines.length > 0 ? [{
|
|
652
799
|
type: "section",
|
|
653
800
|
text: field(planLines.map((line) => escapeSlackMrkdwn(line)).join("\n"))
|
|
801
|
+
}] : [],
|
|
802
|
+
...activityText ? [{
|
|
803
|
+
type: "section",
|
|
804
|
+
text: field(activityText)
|
|
805
|
+
}] : [],
|
|
806
|
+
...footer ? [{
|
|
807
|
+
type: "context",
|
|
808
|
+
elements: [field(footer)]
|
|
809
|
+
}] : [],
|
|
810
|
+
...params.state !== "working" && params.sessionUrl ? [{
|
|
811
|
+
type: "actions",
|
|
812
|
+
elements: [{
|
|
813
|
+
type: "button",
|
|
814
|
+
action_id: SLACK_SESSION_LINK_ACTION_ID,
|
|
815
|
+
text: {
|
|
816
|
+
type: "plain_text",
|
|
817
|
+
text: "Open in OpenClaw"
|
|
818
|
+
},
|
|
819
|
+
url: params.sessionUrl
|
|
820
|
+
}]
|
|
654
821
|
}] : []
|
|
655
822
|
].slice(0, 50);
|
|
656
|
-
const lineBudget = Math.max(0, 50 - headBlocks.length);
|
|
657
|
-
const renderedBlocks = [...headBlocks, ...params.lines.slice(-lineBudget).map((line) => ({
|
|
658
|
-
type: "section",
|
|
659
|
-
fields: [field(legacyLineTitle(line)), field(legacyLineDetail(line, maxLineChars))]
|
|
660
|
-
}))];
|
|
661
|
-
return renderedBlocks.length ? renderedBlocks : void 0;
|
|
662
823
|
}
|
|
663
824
|
/**
|
|
664
825
|
* Slack native streams key task rows by persistent id with no removal chunk.
|
|
@@ -691,12 +852,6 @@ function reconcileSlackNativeTaskChunks(params) {
|
|
|
691
852
|
tasks: nextTasks
|
|
692
853
|
};
|
|
693
854
|
}
|
|
694
|
-
function buildSlackProgressStreamStartChunks(params) {
|
|
695
|
-
return buildSlackProgressStreamChunks(params);
|
|
696
|
-
}
|
|
697
|
-
function buildSlackProgressStreamUpdateChunks(params) {
|
|
698
|
-
return buildSlackProgressStreamChunks(params);
|
|
699
|
-
}
|
|
700
855
|
function buildSlackProgressStreamCompletionChunks(params) {
|
|
701
856
|
return buildSlackProgressStreamChunks({
|
|
702
857
|
...params,
|
|
@@ -706,11 +861,9 @@ function buildSlackProgressStreamCompletionChunks(params) {
|
|
|
706
861
|
//#endregion
|
|
707
862
|
//#region extensions/slack/src/stream-mode.ts
|
|
708
863
|
function resolveSlackStreamingConfig(params) {
|
|
709
|
-
const mode = resolveSlackStreamingMode(params);
|
|
710
864
|
return {
|
|
711
|
-
mode,
|
|
712
|
-
nativeStreaming: resolveSlackNativeStreaming(params)
|
|
713
|
-
draftMode: mapStreamingModeToSlackLegacyDraftStreamMode(mode)
|
|
865
|
+
mode: resolveSlackStreamingMode(params),
|
|
866
|
+
nativeStreaming: resolveSlackNativeStreaming(params)
|
|
714
867
|
};
|
|
715
868
|
}
|
|
716
869
|
function applyAppendOnlyStreamUpdate(params) {
|
|
@@ -730,11 +883,19 @@ function applyAppendOnlyStreamUpdate(params) {
|
|
|
730
883
|
source: params.source,
|
|
731
884
|
changed: false
|
|
732
885
|
};
|
|
733
|
-
if (incoming.startsWith(params.
|
|
886
|
+
if (incoming.startsWith(params.rendered)) return {
|
|
734
887
|
rendered: incoming,
|
|
735
888
|
source: incoming,
|
|
736
889
|
changed: incoming !== params.rendered
|
|
737
890
|
};
|
|
891
|
+
if (incoming.startsWith(params.source)) {
|
|
892
|
+
const delta = incoming.slice(params.source.length);
|
|
893
|
+
return {
|
|
894
|
+
rendered: `${params.rendered}${delta}`,
|
|
895
|
+
source: incoming,
|
|
896
|
+
changed: delta.length > 0
|
|
897
|
+
};
|
|
898
|
+
}
|
|
738
899
|
if (params.source.startsWith(incoming)) return {
|
|
739
900
|
rendered: params.rendered,
|
|
740
901
|
source: params.source,
|
|
@@ -748,6 +909,50 @@ function applyAppendOnlyStreamUpdate(params) {
|
|
|
748
909
|
};
|
|
749
910
|
}
|
|
750
911
|
//#endregion
|
|
912
|
+
//#region extensions/slack/src/monitor/message-handler/dispatch-progress-render.ts
|
|
913
|
+
function resolveStructuredProgressLines(lines) {
|
|
914
|
+
return lines.map((line) => {
|
|
915
|
+
if (typeof line !== "string") return line;
|
|
916
|
+
const reasoning = line.startsWith("🧠 ");
|
|
917
|
+
const text = line.replace(/^(?:🧠|💬)\s+/u, "").replace(/^_(.*)_$/su, "$1").trim();
|
|
918
|
+
return {
|
|
919
|
+
...reasoning ? { id: "reasoning" } : {},
|
|
920
|
+
kind: "item",
|
|
921
|
+
text,
|
|
922
|
+
label: reasoning ? "Reasoning" : "Update",
|
|
923
|
+
prefix: false
|
|
924
|
+
};
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
function resolveNativeProgressPlan(snapshot) {
|
|
928
|
+
return snapshot.plan?.length ? snapshot.plan : void 0;
|
|
929
|
+
}
|
|
930
|
+
function resolveNativeProgressLines(snapshot) {
|
|
931
|
+
const lines = resolveStructuredProgressLines(snapshot.lines).filter((line) => line.id !== "reasoning" && line.id?.startsWith("commentary:") !== true);
|
|
932
|
+
if (snapshot.plan?.length || !snapshot.planExplanation) return lines;
|
|
933
|
+
const explanationLine = buildChannelProgressDraftLine({
|
|
934
|
+
event: "plan",
|
|
935
|
+
phase: "update",
|
|
936
|
+
explanation: snapshot.planExplanation
|
|
937
|
+
});
|
|
938
|
+
return explanationLine ? [...lines, explanationLine] : lines;
|
|
939
|
+
}
|
|
940
|
+
function resolveNativeProgressNarration(snapshot) {
|
|
941
|
+
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);
|
|
942
|
+
return paragraphs.length > 0 ? paragraphs.join("\n\n") : void 0;
|
|
943
|
+
}
|
|
944
|
+
function combineProgressHeadlineAndExplanation(headline, explanation) {
|
|
945
|
+
return headline && explanation && headline !== explanation ? `${headline} — ${explanation}` : headline ?? explanation;
|
|
946
|
+
}
|
|
947
|
+
function buildNativeProgressChunks(params) {
|
|
948
|
+
return buildSlackProgressStreamChunks({
|
|
949
|
+
title: params.title,
|
|
950
|
+
lines: resolveNativeProgressLines(params.snapshot),
|
|
951
|
+
plan: resolveNativeProgressPlan(params.snapshot),
|
|
952
|
+
maxLineChars: params.maxLineChars
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
//#endregion
|
|
751
956
|
//#region extensions/slack/src/monitor/message-handler/preview-finalize.ts
|
|
752
957
|
function buildExpectedSlackEditText(params) {
|
|
753
958
|
const trimmedText = params.text.trim();
|
|
@@ -795,8 +1000,9 @@ async function readSlackMessageAfterEditError(params) {
|
|
|
795
1000
|
channel: params.channelId,
|
|
796
1001
|
ts: params.threadTs,
|
|
797
1002
|
latest: params.messageId,
|
|
1003
|
+
oldest: params.messageId,
|
|
798
1004
|
inclusive: true,
|
|
799
|
-
limit:
|
|
1005
|
+
limit: 1
|
|
800
1006
|
})).messages ?? []).find((message) => message?.ts === params.messageId) ?? null;
|
|
801
1007
|
const message = (await params.client.conversations.history({
|
|
802
1008
|
token: params.token,
|
|
@@ -826,7 +1032,7 @@ async function didSlackPreviewEditApplyAfterError(params) {
|
|
|
826
1032
|
}
|
|
827
1033
|
async function finalizeSlackPreviewEdit(params) {
|
|
828
1034
|
try {
|
|
829
|
-
await
|
|
1035
|
+
await editSlackRenderedMessage(params.channelId, params.messageId, params.text, {
|
|
830
1036
|
token: params.token,
|
|
831
1037
|
accountId: params.accountId,
|
|
832
1038
|
client: params.client,
|
|
@@ -853,74 +1059,199 @@ async function finalizeSlackPreviewEdit(params) {
|
|
|
853
1059
|
}
|
|
854
1060
|
}
|
|
855
1061
|
//#endregion
|
|
856
|
-
//#region extensions/slack/src/monitor/message-handler/dispatch-progress-
|
|
857
|
-
|
|
858
|
-
const { account, ctx, slackClient } = params.setup;
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
1062
|
+
//#region extensions/slack/src/monitor/message-handler/dispatch-progress-card.ts
|
|
1063
|
+
function createSlackDraftProgressCardRuntime(params) {
|
|
1064
|
+
const { account, cfg, ctx, prepared, slackClient } = params.setup;
|
|
1065
|
+
let latestFallbackText = "";
|
|
1066
|
+
let finalStatus;
|
|
1067
|
+
const resolveSessionUrl = () => {
|
|
1068
|
+
if (cfg.gateway?.controlUi?.enabled === false) return;
|
|
1069
|
+
const publicOrigin = resolveGatewayPublicOrigin(cfg);
|
|
1070
|
+
if (!publicOrigin) return;
|
|
1071
|
+
const url = new URL(publicOrigin);
|
|
1072
|
+
const path = buildControlUiSessionPath({
|
|
1073
|
+
namespace: "chat",
|
|
1074
|
+
sessionKey: prepared.route.sessionKey,
|
|
1075
|
+
fallbackAgentId: prepared.route.agentId,
|
|
1076
|
+
basePath: cfg.gateway?.controlUi?.basePath
|
|
865
1077
|
});
|
|
866
|
-
return
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
const reasoning = line.startsWith("🧠 ");
|
|
878
|
-
const text = line.replace(/^(?:🧠|💬)\s+/u, "").replace(/^_(.*)_$/su, "$1").trim();
|
|
879
|
-
return {
|
|
880
|
-
...reasoning ? { id: "reasoning" } : {},
|
|
881
|
-
kind: "item",
|
|
882
|
-
text,
|
|
883
|
-
label: reasoning ? "Reasoning" : "Update",
|
|
884
|
-
prefix: false
|
|
885
|
-
};
|
|
886
|
-
});
|
|
887
|
-
}
|
|
888
|
-
function resolveNativeProgressPlan(snapshot) {
|
|
889
|
-
return snapshot.plan?.length ? snapshot.plan : void 0;
|
|
890
|
-
}
|
|
891
|
-
function resolveNativeProgressLines(snapshot) {
|
|
892
|
-
const lines = resolveStructuredProgressLines(snapshot.lines);
|
|
893
|
-
if (snapshot.plan?.length || !snapshot.planExplanation) return lines;
|
|
894
|
-
const explanationLine = buildChannelProgressDraftLine({
|
|
895
|
-
event: "plan",
|
|
896
|
-
phase: "update",
|
|
897
|
-
explanation: snapshot.planExplanation
|
|
1078
|
+
if (!path) return;
|
|
1079
|
+
url.pathname = path;
|
|
1080
|
+
return url.toString();
|
|
1081
|
+
};
|
|
1082
|
+
const resolveText = (snapshot) => latestFallbackText || formatChannelProgressDraftText({
|
|
1083
|
+
entry: account.config,
|
|
1084
|
+
lines: [...snapshot.lines],
|
|
1085
|
+
seed: params.progressSeed,
|
|
1086
|
+
formatLine: formatSlackProgressDraftLine,
|
|
1087
|
+
narration: snapshot.statusHeadline,
|
|
1088
|
+
plan: snapshot.plan
|
|
898
1089
|
});
|
|
899
|
-
|
|
1090
|
+
const resolvePresentation = (snapshot, state) => {
|
|
1091
|
+
const title = params.explicitTitle ?? snapshot.statusHeadline ?? "Working";
|
|
1092
|
+
return buildSlackProgressCardBlocks({
|
|
1093
|
+
state,
|
|
1094
|
+
title,
|
|
1095
|
+
narration: params.explicitTitle ? combineProgressHeadlineAndExplanation(snapshot.statusHeadline, snapshot.planExplanation) : snapshot.planExplanation && snapshot.planExplanation !== title ? snapshot.planExplanation : void 0,
|
|
1096
|
+
plan: snapshot.plan,
|
|
1097
|
+
lines: resolveStructuredProgressLines(snapshot.lines),
|
|
1098
|
+
maxLineChars: params.maxLineChars,
|
|
1099
|
+
diffStat: snapshot.diffStat,
|
|
1100
|
+
...state === "working" ? {
|
|
1101
|
+
toolCalls: params.progressReceipt.toolCalls,
|
|
1102
|
+
elapsedSeconds: params.progressReceipt.elapsedSeconds
|
|
1103
|
+
} : {
|
|
1104
|
+
receiptSummary: params.progressReceipt.buildSummaryLine(),
|
|
1105
|
+
sessionUrl: resolveSessionUrl()
|
|
1106
|
+
}
|
|
1107
|
+
});
|
|
1108
|
+
};
|
|
1109
|
+
const finalize = async (status, snapshot = params.getSnapshot(), fallbackText = resolveText(snapshot)) => {
|
|
1110
|
+
if (!params.draftStream || !params.enabled) return false;
|
|
1111
|
+
await params.draftStream.dropDetachedMessages();
|
|
1112
|
+
const terminalStatus = finalStatus === "error" || status === "error" ? "error" : "success";
|
|
1113
|
+
if (finalStatus === terminalStatus) return true;
|
|
1114
|
+
await params.draftStream.flush();
|
|
1115
|
+
const channelId = params.draftStream.channelId();
|
|
1116
|
+
const messageId = params.draftStream.messageId();
|
|
1117
|
+
if (!channelId || !messageId) return false;
|
|
1118
|
+
await params.draftStream.seal();
|
|
1119
|
+
try {
|
|
1120
|
+
const finalized = await params.draftStream.finalizeMessage(messageId, async () => {
|
|
1121
|
+
await finalizeSlackPreviewEdit({
|
|
1122
|
+
client: slackClient,
|
|
1123
|
+
token: ctx.botToken,
|
|
1124
|
+
accountId: account.accountId,
|
|
1125
|
+
channelId,
|
|
1126
|
+
messageId,
|
|
1127
|
+
text: fallbackText,
|
|
1128
|
+
blocks: resolvePresentation(snapshot, terminalStatus),
|
|
1129
|
+
threadTs: params.getThreadTs()
|
|
1130
|
+
});
|
|
1131
|
+
});
|
|
1132
|
+
if (finalized) finalStatus = terminalStatus;
|
|
1133
|
+
return finalized;
|
|
1134
|
+
} catch (err) {
|
|
1135
|
+
logVerbose(`slack: progress card final edit failed (${formatSlackError(err)})`);
|
|
1136
|
+
return false;
|
|
1137
|
+
}
|
|
1138
|
+
};
|
|
1139
|
+
return {
|
|
1140
|
+
resolveSessionUrl,
|
|
1141
|
+
resolveText,
|
|
1142
|
+
resolvePresentation,
|
|
1143
|
+
finalize,
|
|
1144
|
+
get hasTerminalized() {
|
|
1145
|
+
return finalStatus !== void 0;
|
|
1146
|
+
},
|
|
1147
|
+
setFallbackText(text) {
|
|
1148
|
+
latestFallbackText = text;
|
|
1149
|
+
},
|
|
1150
|
+
reset() {
|
|
1151
|
+
latestFallbackText = "";
|
|
1152
|
+
finalStatus = void 0;
|
|
1153
|
+
}
|
|
1154
|
+
};
|
|
900
1155
|
}
|
|
901
|
-
function
|
|
902
|
-
|
|
1156
|
+
function formatSlackProgressDraftLine(line) {
|
|
1157
|
+
if (/^(?:🧠|💬)\s/u.test(line)) return line;
|
|
1158
|
+
const italicCommentary = /^_(.*)_$/su.exec(line);
|
|
1159
|
+
if (!italicCommentary) return escapeSlackMrkdwn(line);
|
|
1160
|
+
return `_${italicCommentary[1].split(/(`[^`\n]+`)/u).map((segment, index) => {
|
|
1161
|
+
if (index % 2 === 0) return escapeSlackMrkdwn(segment);
|
|
1162
|
+
return `\`${segment.slice(1, -1).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">")}\``;
|
|
1163
|
+
}).join("")}_`;
|
|
903
1164
|
}
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1165
|
+
//#endregion
|
|
1166
|
+
//#region extensions/slack/src/monitor/message-handler/dispatch-progress-native.ts
|
|
1167
|
+
function createSlackNativeProgressTransport(params) {
|
|
1168
|
+
const { ctx, message, replyPlan, slackClient, slackIdentity, slackStreamFallbackTeamId } = params.setup;
|
|
1169
|
+
const { delivery } = params;
|
|
1170
|
+
const markDelivered = (threadTs) => {
|
|
1171
|
+
if (!delivery.streamSession?.delivered) return false;
|
|
1172
|
+
delivery.observedReplyDelivery = true;
|
|
1173
|
+
if (threadTs) {
|
|
1174
|
+
delivery.usedReplyThreadTs ??= threadTs;
|
|
1175
|
+
delivery.rememberDeliveredThreadTs("block", threadTs);
|
|
1176
|
+
}
|
|
1177
|
+
return true;
|
|
1178
|
+
};
|
|
1179
|
+
const waitForStart = async () => {
|
|
1180
|
+
if (delivery.streamSession || !delivery.nativeProgressStreamStartPromise) return true;
|
|
1181
|
+
try {
|
|
1182
|
+
await delivery.nativeProgressStreamStartPromise;
|
|
1183
|
+
} catch {
|
|
1184
|
+
delivery.streamFailed = true;
|
|
1185
|
+
return false;
|
|
1186
|
+
}
|
|
1187
|
+
return !delivery.streamFailed;
|
|
1188
|
+
};
|
|
1189
|
+
const start = async (update) => {
|
|
1190
|
+
const streamThreadTs = replyPlan.nextThreadTs();
|
|
1191
|
+
if (!streamThreadTs) {
|
|
1192
|
+
logVerbose("slack-stream: no reply thread target for native progress stream start, falling back");
|
|
1193
|
+
delivery.streamFailed = true;
|
|
1194
|
+
return false;
|
|
1195
|
+
}
|
|
1196
|
+
delivery.nativeProgressStreamThreadTs = streamThreadTs;
|
|
1197
|
+
const startPromise = (async () => {
|
|
1198
|
+
const session = await startSlackStream({
|
|
1199
|
+
client: slackClient,
|
|
1200
|
+
channel: message.channel,
|
|
1201
|
+
threadTs: streamThreadTs,
|
|
1202
|
+
...update.text ? { text: update.text } : {},
|
|
1203
|
+
...update.chunks?.length ? { chunks: update.chunks } : {},
|
|
1204
|
+
taskDisplayMode: "plan",
|
|
1205
|
+
...slackIdentity ? { identity: slackIdentity } : {},
|
|
1206
|
+
teamId: await resolveSlackStreamRecipientTeamId({
|
|
1207
|
+
client: slackClient,
|
|
1208
|
+
token: ctx.botToken,
|
|
1209
|
+
userId: message.user,
|
|
1210
|
+
fallbackTeamId: slackStreamFallbackTeamId
|
|
1211
|
+
}),
|
|
1212
|
+
userId: message.user
|
|
1213
|
+
});
|
|
1214
|
+
delivery.streamSession = session;
|
|
1215
|
+
return session;
|
|
1216
|
+
})();
|
|
1217
|
+
delivery.nativeProgressStreamStartPromise = startPromise;
|
|
1218
|
+
try {
|
|
1219
|
+
if (!await startPromise) return false;
|
|
1220
|
+
const delivered = markDelivered(streamThreadTs);
|
|
1221
|
+
return update.chunks?.length ? delivered : true;
|
|
1222
|
+
} finally {
|
|
1223
|
+
if (delivery.nativeProgressStreamStartPromise === startPromise) delivery.nativeProgressStreamStartPromise = null;
|
|
1224
|
+
}
|
|
1225
|
+
};
|
|
1226
|
+
const append = async (update) => {
|
|
1227
|
+
const session = delivery.streamSession;
|
|
1228
|
+
if (!session) return false;
|
|
1229
|
+
await appendSlackStream({
|
|
1230
|
+
session,
|
|
1231
|
+
...update.text ? { text: update.text } : {},
|
|
1232
|
+
...update.chunks?.length ? { chunks: update.chunks } : {}
|
|
1233
|
+
});
|
|
1234
|
+
const delivered = markDelivered(delivery.nativeProgressStreamThreadTs);
|
|
1235
|
+
return update.chunks?.length ? delivered : true;
|
|
1236
|
+
};
|
|
1237
|
+
return {
|
|
1238
|
+
append,
|
|
1239
|
+
start,
|
|
1240
|
+
waitForStart
|
|
910
1241
|
};
|
|
911
|
-
return params.streamStarted ? buildSlackProgressStreamUpdateChunks(input) : buildSlackProgressStreamStartChunks(input);
|
|
912
1242
|
}
|
|
913
1243
|
//#endregion
|
|
914
1244
|
//#region extensions/slack/src/monitor/message-handler/dispatch-progress.ts
|
|
915
1245
|
function createSlackProgressRuntime(runtimeParams) {
|
|
916
1246
|
const { setup, delivery, resetPreviewDeliveryState } = runtimeParams;
|
|
917
|
-
const { account, cfg, ctx, hasSlackCustomIdentity, message, prepared, replyPlan, runtime, slackClient, slackIdentity, slackMessageMetadata, slackStreaming,
|
|
1247
|
+
const { account, cfg, ctx, hasSlackCustomIdentity, message, prepared, replyPlan, runtime, slackClient, slackIdentity, slackMessageMetadata, slackStreaming, shouldUseDraftStream, useStreaming, previewStreamingEnabled } = setup;
|
|
918
1248
|
const draftStream = shouldUseDraftStream ? createSlackDraftStream({
|
|
919
1249
|
target: prepared.replyTarget,
|
|
920
1250
|
cfg,
|
|
921
1251
|
token: ctx.botToken,
|
|
922
1252
|
accountId: account.accountId,
|
|
923
|
-
|
|
1253
|
+
conversationChannelId: message.channel,
|
|
1254
|
+
eventScope: prepared.eventScope,
|
|
924
1255
|
...!hasSlackCustomIdentity && slackIdentity ? { identity: slackIdentity } : {},
|
|
925
1256
|
...slackMessageMetadata ? { metadata: slackMessageMetadata } : {},
|
|
926
1257
|
maxChars: Math.min(ctx.textLimit, SLACK_TEXT_LIMIT),
|
|
@@ -933,40 +1264,61 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
933
1264
|
warn: logVerbose
|
|
934
1265
|
}) : void 0;
|
|
935
1266
|
let hasStreamedMessage = false;
|
|
936
|
-
const
|
|
1267
|
+
const isProgressMode = slackStreaming.mode === "progress";
|
|
937
1268
|
const useNativeProgressStreaming = useStreaming && slackStreaming.mode === "progress";
|
|
938
1269
|
const progressDraftActive = Boolean(draftStream) || useNativeProgressStreaming;
|
|
939
1270
|
const previewToolProgressEnabled = progressDraftActive && resolveChannelStreamingPreviewToolProgress(account.config, true, slackStreaming.mode);
|
|
940
1271
|
let shouldYieldDraftProgress = () => false;
|
|
941
1272
|
const suppressDefaultToolProgressMessages = resolveChannelStreamingSuppressDefaultToolProgressMessages(account.config, {
|
|
942
1273
|
draftStreamActive: Boolean(draftStream) || useNativeProgressStreaming,
|
|
1274
|
+
mode: slackStreaming.mode,
|
|
943
1275
|
previewToolProgressEnabled,
|
|
944
1276
|
previewStreamingEnabled
|
|
945
1277
|
});
|
|
946
1278
|
let previewToolProgressSuppressed = false;
|
|
947
|
-
let legacyPreviewToolProgressLines = [];
|
|
948
1279
|
let nativeTaskState = /* @__PURE__ */ new Map();
|
|
949
1280
|
let appendRenderedText = "";
|
|
950
1281
|
let appendSourceText = "";
|
|
951
1282
|
let nativeProgressCompletionSent = false;
|
|
952
1283
|
let nativeProgressTerminalStatus = "complete";
|
|
953
1284
|
let nativeProgressChunkKey;
|
|
1285
|
+
let nativeNarrationRenderedText = "";
|
|
1286
|
+
let nativeNarrationSourceText = "";
|
|
1287
|
+
let nativeStreamOrder = Promise.resolve();
|
|
1288
|
+
const withNativeStreamOrder = (task) => {
|
|
1289
|
+
const run = nativeStreamOrder.then(task, task);
|
|
1290
|
+
nativeStreamOrder = run.catch(() => void 0);
|
|
1291
|
+
return run;
|
|
1292
|
+
};
|
|
954
1293
|
const progressReceipt = createChannelProgressReceiptTracker();
|
|
955
|
-
let progressReceiptCollapsed = false;
|
|
956
|
-
let pendingNativeProgressReceipt;
|
|
957
1294
|
const progressSeed = `${account.accountId}:${message.channel}`;
|
|
958
|
-
const
|
|
1295
|
+
const useDraftProgressCard = Boolean(draftStream) && isProgressMode;
|
|
959
1296
|
const explicitProgressTitle = resolveExplicitSlackProgressTitle(account.config);
|
|
960
1297
|
const progressDraftMaxLineChars = resolveChannelProgressDraftMaxLineChars(account.config);
|
|
961
|
-
const
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
}
|
|
969
|
-
|
|
1298
|
+
const progressCard = createSlackDraftProgressCardRuntime({
|
|
1299
|
+
setup: {
|
|
1300
|
+
account,
|
|
1301
|
+
cfg,
|
|
1302
|
+
ctx,
|
|
1303
|
+
prepared,
|
|
1304
|
+
slackClient
|
|
1305
|
+
},
|
|
1306
|
+
draftStream,
|
|
1307
|
+
enabled: useDraftProgressCard,
|
|
1308
|
+
progressReceipt,
|
|
1309
|
+
progressSeed,
|
|
1310
|
+
explicitTitle: explicitProgressTitle,
|
|
1311
|
+
maxLineChars: progressDraftMaxLineChars,
|
|
1312
|
+
getSnapshot: () => progressDraft.getSnapshot(),
|
|
1313
|
+
getThreadTs: () => delivery.usedReplyThreadTs
|
|
1314
|
+
});
|
|
1315
|
+
const nativeTransport = createSlackNativeProgressTransport({
|
|
1316
|
+
setup,
|
|
1317
|
+
delivery
|
|
1318
|
+
});
|
|
1319
|
+
const dropDetachedProgressCards = async () => {
|
|
1320
|
+
if (!useDraftProgressCard) return;
|
|
1321
|
+
await draftStream?.dropDetachedMessages();
|
|
970
1322
|
};
|
|
971
1323
|
const appendNativeProgressCompletion = async (isError) => {
|
|
972
1324
|
const session = delivery.streamSession;
|
|
@@ -986,150 +1338,156 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
986
1338
|
runtime.error?.(danger(`slack-stream: native progress completion failed: ${formatSlackError(err)}`));
|
|
987
1339
|
}
|
|
988
1340
|
};
|
|
1341
|
+
const resolveNativeProgressTitle = (snapshot) => combineProgressHeadlineAndExplanation(explicitProgressTitle ?? snapshot.statusHeadline, snapshot.planExplanation);
|
|
989
1342
|
const buildNativeProgressChunks$1 = (snapshot) => buildNativeProgressChunks({
|
|
990
1343
|
snapshot,
|
|
991
|
-
|
|
992
|
-
title: combineProgressHeadlineAndExplanation(explicitProgressTitle ?? snapshot.statusHeadline, snapshot.planExplanation),
|
|
1344
|
+
title: resolveNativeProgressTitle(snapshot),
|
|
993
1345
|
maxLineChars: progressDraftMaxLineChars
|
|
994
1346
|
});
|
|
995
|
-
const
|
|
996
|
-
const
|
|
997
|
-
|
|
998
|
-
if (
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
}
|
|
1002
|
-
};
|
|
1003
|
-
const startNativeProgressStream = async (chunks, chunkKey) => {
|
|
1004
|
-
const streamThreadTs = replyPlan.nextThreadTs();
|
|
1005
|
-
if (!streamThreadTs) {
|
|
1006
|
-
logVerbose("slack-stream: no reply thread target for native progress stream start, falling back");
|
|
1007
|
-
delivery.streamFailed = true;
|
|
1008
|
-
return;
|
|
1009
|
-
}
|
|
1010
|
-
delivery.nativeProgressStreamThreadTs = streamThreadTs;
|
|
1011
|
-
const startPromise = (async () => {
|
|
1012
|
-
const session = await startSlackStream({
|
|
1013
|
-
client: slackClient,
|
|
1014
|
-
channel: message.channel,
|
|
1015
|
-
threadTs: streamThreadTs,
|
|
1016
|
-
chunks,
|
|
1017
|
-
taskDisplayMode: "plan",
|
|
1018
|
-
...slackIdentity ? { identity: slackIdentity } : {},
|
|
1019
|
-
teamId: await resolveSlackStreamRecipientTeamId({
|
|
1020
|
-
client: slackClient,
|
|
1021
|
-
token: ctx.botToken,
|
|
1022
|
-
userId: message.user,
|
|
1023
|
-
fallbackTeamId: slackStreamFallbackTeamId
|
|
1024
|
-
}),
|
|
1025
|
-
userId: message.user
|
|
1026
|
-
});
|
|
1027
|
-
delivery.streamSession = session;
|
|
1028
|
-
return session;
|
|
1029
|
-
})();
|
|
1030
|
-
delivery.nativeProgressStreamStartPromise = startPromise;
|
|
1031
|
-
let startedSession;
|
|
1032
|
-
try {
|
|
1033
|
-
startedSession = await startPromise;
|
|
1034
|
-
} finally {
|
|
1035
|
-
if (delivery.nativeProgressStreamStartPromise === startPromise) delivery.nativeProgressStreamStartPromise = null;
|
|
1036
|
-
}
|
|
1037
|
-
if (startedSession) markNativeProgressDelivered(startedSession, streamThreadTs);
|
|
1038
|
-
nativeProgressChunkKey = chunkKey;
|
|
1039
|
-
replyPlan.markSent();
|
|
1347
|
+
const normalizeProgressText = (text) => text?.replace(/\s+/gu, " ").trim() ?? "";
|
|
1348
|
+
const isRenderedAsProgressTitle = (text) => {
|
|
1349
|
+
const candidate = normalizeProgressText(text);
|
|
1350
|
+
if (!candidate) return false;
|
|
1351
|
+
const title = normalizeProgressText(resolveNativeProgressTitle(progressDraft.getSnapshot()));
|
|
1352
|
+
return title.length > 0 && title.includes(candidate);
|
|
1040
1353
|
};
|
|
1041
|
-
const
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1354
|
+
const resolveNarrationUpdate = (incoming) => {
|
|
1355
|
+
const next = applyAppendOnlyStreamUpdate({
|
|
1356
|
+
incoming: incoming ?? "",
|
|
1357
|
+
rendered: nativeNarrationRenderedText,
|
|
1358
|
+
source: nativeNarrationSourceText
|
|
1046
1359
|
});
|
|
1047
|
-
|
|
1048
|
-
|
|
1360
|
+
return {
|
|
1361
|
+
next,
|
|
1362
|
+
delta: next.changed ? next.rendered.slice(nativeNarrationRenderedText.length) : ""
|
|
1363
|
+
};
|
|
1049
1364
|
};
|
|
1050
|
-
const updateNativeProgressStream =
|
|
1365
|
+
const updateNativeProgressStream = () => withNativeStreamOrder(updateNativeProgressStreamNow);
|
|
1366
|
+
const updateNativeProgressStreamNow = async () => {
|
|
1051
1367
|
const snapshot = progressDraft.getSnapshot();
|
|
1052
1368
|
const progressLines = resolveNativeProgressLines(snapshot);
|
|
1369
|
+
const narrationUpdate = resolveNarrationUpdate(resolveNativeProgressNarration(snapshot));
|
|
1053
1370
|
const hasRetirableNativeTasks = [...nativeTaskState.values()].some((task) => task.status !== "complete" && task.status !== "error");
|
|
1054
|
-
if (!useNativeProgressStreaming || delivery.streamFailed || progressLines.length === 0 && !snapshot.plan?.length && !snapshot.statusHeadline && !explicitProgressTitle && !hasRetirableNativeTasks) return;
|
|
1055
|
-
if (!await
|
|
1371
|
+
if (!useNativeProgressStreaming || delivery.streamFailed || progressLines.length === 0 && !snapshot.plan?.length && !snapshot.statusHeadline && !explicitProgressTitle && !hasRetirableNativeTasks && !narrationUpdate.delta) return false;
|
|
1372
|
+
if (!await nativeTransport.waitForStart()) return false;
|
|
1056
1373
|
const reconciled = reconcileSlackNativeTaskChunks({
|
|
1057
1374
|
previousTasks: nativeTaskState,
|
|
1058
1375
|
chunks: buildNativeProgressChunks$1(snapshot)
|
|
1059
1376
|
});
|
|
1060
|
-
const
|
|
1061
|
-
|
|
1062
|
-
const
|
|
1063
|
-
if (
|
|
1377
|
+
const chunkKey = JSON.stringify(reconciled.chunks ?? []);
|
|
1378
|
+
const taskChunksChanged = chunkKey !== nativeProgressChunkKey;
|
|
1379
|
+
const chunks = taskChunksChanged ? reconciled.chunks : void 0;
|
|
1380
|
+
if (!chunks?.length && !narrationUpdate.delta) return false;
|
|
1064
1381
|
try {
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1382
|
+
const hadSession = Boolean(delivery.streamSession);
|
|
1383
|
+
const streamUpdate = {
|
|
1384
|
+
...narrationUpdate.delta ? { text: narrationUpdate.delta } : {},
|
|
1385
|
+
...chunks?.length ? { chunks } : {}
|
|
1386
|
+
};
|
|
1387
|
+
if (!(hadSession ? await nativeTransport.append(streamUpdate) : await nativeTransport.start(streamUpdate))) return false;
|
|
1388
|
+
if (!hadSession) replyPlan.markSent();
|
|
1389
|
+
if (narrationUpdate.next.changed) {
|
|
1390
|
+
nativeNarrationRenderedText = narrationUpdate.next.rendered;
|
|
1391
|
+
nativeNarrationSourceText = narrationUpdate.next.source;
|
|
1392
|
+
}
|
|
1393
|
+
if (taskChunksChanged) {
|
|
1394
|
+
nativeProgressChunkKey = chunkKey;
|
|
1395
|
+
nativeTaskState = reconciled.tasks;
|
|
1396
|
+
}
|
|
1397
|
+
return true;
|
|
1068
1398
|
} catch (err) {
|
|
1069
1399
|
runtime.error?.(danger(`slack-stream: native progress stream failed: ${formatSlackError(err)}, falling back`));
|
|
1070
1400
|
delivery.streamFailed = true;
|
|
1401
|
+
return false;
|
|
1402
|
+
}
|
|
1403
|
+
};
|
|
1404
|
+
const appendNativeNarration = (payload, kind) => withNativeStreamOrder(() => appendNativeNarrationNow(payload, kind));
|
|
1405
|
+
const appendNativeNarrationNow = async (payload, kind) => {
|
|
1406
|
+
if (isRenderedAsProgressTitle(payload.text)) return false;
|
|
1407
|
+
const narrationUpdate = resolveNarrationUpdate(payload.text?.trimEnd());
|
|
1408
|
+
if (!narrationUpdate.delta) return false;
|
|
1409
|
+
await delivery.deliverWithStreaming({
|
|
1410
|
+
payload,
|
|
1411
|
+
kind,
|
|
1412
|
+
streamText: narrationUpdate.delta,
|
|
1413
|
+
appendSeparator: false,
|
|
1414
|
+
taskDisplayMode: "plan"
|
|
1415
|
+
});
|
|
1416
|
+
if (!delivery.streamFailed) {
|
|
1417
|
+
nativeNarrationRenderedText = narrationUpdate.next.rendered;
|
|
1418
|
+
nativeNarrationSourceText = narrationUpdate.next.source;
|
|
1071
1419
|
}
|
|
1420
|
+
return true;
|
|
1072
1421
|
};
|
|
1073
1422
|
const resetProgressTurnState = () => {
|
|
1074
1423
|
progressReceipt.reset();
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
};
|
|
1078
|
-
const collapseProgressReceipt = async (params) => {
|
|
1079
|
-
progressReceiptCollapsed = await collapseSlackProgressReceipt({
|
|
1080
|
-
setup,
|
|
1081
|
-
edit: params
|
|
1082
|
-
});
|
|
1424
|
+
nativeNarrationRenderedText = "";
|
|
1425
|
+
nativeNarrationSourceText = "";
|
|
1083
1426
|
};
|
|
1084
1427
|
const progressDraft = createChannelProgressDraftCompositor({
|
|
1085
1428
|
entry: account.config,
|
|
1086
1429
|
mode: slackStreaming.mode,
|
|
1087
|
-
active: progressDraftActive
|
|
1430
|
+
active: progressDraftActive,
|
|
1088
1431
|
seed: progressSeed,
|
|
1089
|
-
formatLine:
|
|
1432
|
+
formatLine: formatSlackProgressDraftLine,
|
|
1090
1433
|
reasoningLinePrefix: "🧠 ",
|
|
1091
|
-
commentaryLinePrefix: "
|
|
1434
|
+
commentaryLinePrefix: "",
|
|
1092
1435
|
reasoningGate: previewToolProgressEnabled,
|
|
1093
|
-
commentaryItalics:
|
|
1094
|
-
|
|
1436
|
+
commentaryItalics: true,
|
|
1437
|
+
buildProgressEventLine: (input, options) => input.event === "tool" || input.event === "item" || input.event === "command-output" ? buildChannelProgressDraftLineForEntry(account.config, input, options) : buildChannelProgressDraftLine(input, options),
|
|
1438
|
+
updateOnLineChange: useNativeProgressStreaming || useDraftProgressCard,
|
|
1095
1439
|
update: async (previewText, options) => {
|
|
1096
|
-
if (useNativeProgressStreaming)
|
|
1097
|
-
|
|
1098
|
-
return;
|
|
1099
|
-
}
|
|
1100
|
-
if (!draftStream) return;
|
|
1440
|
+
if (useNativeProgressStreaming) return await updateNativeProgressStream();
|
|
1441
|
+
if (!draftStream) return false;
|
|
1101
1442
|
const snapshot = progressDraft.getSnapshot();
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
const richProgressBlocks = useRichProgressDraft ? buildSlackProgressDraftBlocks({
|
|
1105
|
-
title: explicitProgressTitle,
|
|
1106
|
-
lines: structuredLines,
|
|
1107
|
-
plan: snapshot.plan,
|
|
1108
|
-
narration: richNarration,
|
|
1109
|
-
maxLineChars: progressDraftMaxLineChars
|
|
1110
|
-
}) : void 0;
|
|
1111
|
-
draftStream.update(useRichProgressDraft && richProgressBlocks ? {
|
|
1443
|
+
progressCard.setFallbackText(previewText);
|
|
1444
|
+
draftStream.update(useDraftProgressCard ? {
|
|
1112
1445
|
text: previewText,
|
|
1113
|
-
blocks:
|
|
1446
|
+
blocks: progressCard.resolvePresentation(snapshot, "working")
|
|
1114
1447
|
} : previewText);
|
|
1115
1448
|
hasStreamedMessage = true;
|
|
1116
1449
|
if (options?.flush) await draftStream.flush();
|
|
1450
|
+
return Boolean(draftStream.messageId() && draftStream.channelId());
|
|
1117
1451
|
}
|
|
1118
1452
|
});
|
|
1119
1453
|
const commentaryProgressEnabled = progressDraft.commentaryProgressEnabled;
|
|
1454
|
+
const deliverNativeFinal = (payload, kind) => withNativeStreamOrder(() => deliverNativeFinalNow(payload, kind));
|
|
1455
|
+
const deliverNativeFinalNow = async (payload, kind) => {
|
|
1456
|
+
progressDraft.markFinalReplyStarted();
|
|
1457
|
+
const streamReady = await nativeTransport.waitForStart();
|
|
1458
|
+
const finalThreadTs = delivery.streamSession?.threadTs ?? delivery.nativeProgressStreamThreadTs;
|
|
1459
|
+
if (payload.isError !== true && streamReady && Boolean(delivery.streamSession) && delivery.isStreamingEligible(payload, { maxTextBytes: 4e3 })) {
|
|
1460
|
+
await appendNativeProgressCompletion(false);
|
|
1461
|
+
await delivery.deliverWithStreaming({
|
|
1462
|
+
payload,
|
|
1463
|
+
kind
|
|
1464
|
+
});
|
|
1465
|
+
} else {
|
|
1466
|
+
await delivery.deliverNormally({
|
|
1467
|
+
payload,
|
|
1468
|
+
kind,
|
|
1469
|
+
forcedThreadTs: finalThreadTs
|
|
1470
|
+
});
|
|
1471
|
+
await appendNativeProgressCompletion(payload.isError === true);
|
|
1472
|
+
}
|
|
1473
|
+
progressDraft.markFinalReplyDelivered();
|
|
1474
|
+
};
|
|
1120
1475
|
const buildNativeProgressCompletionChunks = (finalInProgressStatus) => {
|
|
1121
1476
|
const snapshot = progressDraft.getSnapshot();
|
|
1122
1477
|
const lines = resolveNativeProgressLines(snapshot);
|
|
1478
|
+
const sessionUrl = progressCard.resolveSessionUrl();
|
|
1123
1479
|
const hasRetirableNativeTasks = [...nativeTaskState.values()].some((task) => task.status !== "complete" && task.status !== "error");
|
|
1124
|
-
if (lines.length === 0 && !snapshot.plan?.length && !hasRetirableNativeTasks) return;
|
|
1480
|
+
if (lines.length === 0 && !snapshot.plan?.length && !hasRetirableNativeTasks && !snapshot.diffStat && !sessionUrl) return;
|
|
1125
1481
|
return reconcileSlackNativeTaskChunks({
|
|
1126
1482
|
previousTasks: nativeTaskState,
|
|
1127
1483
|
chunks: buildSlackProgressStreamCompletionChunks({
|
|
1128
|
-
title: resolveNativeProgressTitle(snapshot),
|
|
1484
|
+
title: resolveNativeProgressTitle(snapshot) ?? (lines.length === 0 && !snapshot.plan?.length ? "Working" : void 0),
|
|
1129
1485
|
lines,
|
|
1130
1486
|
plan: resolveNativeProgressPlan(snapshot),
|
|
1131
1487
|
maxLineChars: progressDraftMaxLineChars,
|
|
1132
|
-
finalInProgressStatus
|
|
1488
|
+
finalInProgressStatus,
|
|
1489
|
+
diffStat: snapshot.diffStat,
|
|
1490
|
+
sessionUrl
|
|
1133
1491
|
})
|
|
1134
1492
|
}).chunks;
|
|
1135
1493
|
};
|
|
@@ -1144,12 +1502,6 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1144
1502
|
...slackMessageMetadata ? { metadata: slackMessageMetadata } : {}
|
|
1145
1503
|
});
|
|
1146
1504
|
delivery.acknowledgeStoppedStreamedDeliveries(session, stopResult?.messageId);
|
|
1147
|
-
if (pendingNativeProgressReceipt && stopResult?.messageId) await collapseProgressReceipt({
|
|
1148
|
-
channelId: session.channel,
|
|
1149
|
-
messageId: stopResult.messageId,
|
|
1150
|
-
text: pendingNativeProgressReceipt,
|
|
1151
|
-
threadTs: session.threadTs
|
|
1152
|
-
});
|
|
1153
1505
|
} catch (err) {
|
|
1154
1506
|
const error = formatSlackError(err);
|
|
1155
1507
|
delivery.emitAcknowledgedStreamedDeliveries();
|
|
@@ -1162,16 +1514,13 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1162
1514
|
delivery.streamFailed = false;
|
|
1163
1515
|
};
|
|
1164
1516
|
const pushPlanProgress = async (steps, explanation) => {
|
|
1165
|
-
if (
|
|
1166
|
-
|
|
1167
|
-
return;
|
|
1168
|
-
}
|
|
1169
|
-
if (previewToolProgressSuppressed || !draftStream) return;
|
|
1517
|
+
if (isProgressMode) return await progressDraft.pushPlanProgress(steps, { explanation });
|
|
1518
|
+
if (previewToolProgressSuppressed || !draftStream) return false;
|
|
1170
1519
|
const text = formatChannelProgressDraftText({
|
|
1171
1520
|
entry: account.config,
|
|
1172
|
-
lines:
|
|
1521
|
+
lines: [...progressDraft.getSnapshot().lines],
|
|
1173
1522
|
seed: progressSeed,
|
|
1174
|
-
formatLine:
|
|
1523
|
+
formatLine: formatSlackProgressDraftLine,
|
|
1175
1524
|
narration: explanation,
|
|
1176
1525
|
plan: steps
|
|
1177
1526
|
});
|
|
@@ -1179,37 +1528,25 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1179
1528
|
draftStream.update(text);
|
|
1180
1529
|
hasStreamedMessage = true;
|
|
1181
1530
|
}
|
|
1531
|
+
return false;
|
|
1182
1532
|
};
|
|
1183
1533
|
const pushPreviewProgress = async (line, options) => {
|
|
1184
|
-
if (!draftStream && !useNativeProgressStreaming) return;
|
|
1185
|
-
if (options?.toolName !== void 0 && !isChannelProgressDraftWorkToolName(options.toolName)) return;
|
|
1534
|
+
if (!draftStream && !useNativeProgressStreaming) return false;
|
|
1535
|
+
if (options?.toolName !== void 0 && !isChannelProgressDraftWorkToolName(options.toolName)) return false;
|
|
1186
1536
|
const normalized = line?.text.replace(/\s+/g, " ").trim();
|
|
1187
|
-
if (
|
|
1188
|
-
if (!line || !normalized)
|
|
1189
|
-
|
|
1190
|
-
return;
|
|
1191
|
-
}
|
|
1192
|
-
await progressDraft.pushToolProgress(line, options);
|
|
1193
|
-
return;
|
|
1537
|
+
if (isProgressMode) {
|
|
1538
|
+
if (!line || !normalized) return await progressDraft.noteActivity();
|
|
1539
|
+
return await progressDraft.pushToolProgress(line, options);
|
|
1194
1540
|
}
|
|
1195
|
-
if (!line || !normalized || !draftStream || !previewToolProgressEnabled
|
|
1196
|
-
|
|
1197
|
-
if (nextLines === legacyPreviewToolProgressLines) return;
|
|
1198
|
-
legacyPreviewToolProgressLines = nextLines;
|
|
1199
|
-
draftStream.update(formatChannelProgressDraftText({
|
|
1200
|
-
entry: account.config,
|
|
1201
|
-
lines: legacyPreviewToolProgressLines,
|
|
1202
|
-
seed: progressSeed,
|
|
1203
|
-
formatLine: escapeSlackMrkdwn
|
|
1204
|
-
}));
|
|
1205
|
-
hasStreamedMessage = true;
|
|
1541
|
+
if (!line || !normalized || !draftStream || !previewToolProgressEnabled) return false;
|
|
1542
|
+
return await progressDraft.pushToolProgress(line, options);
|
|
1206
1543
|
};
|
|
1207
1544
|
const updateDraftFromPartial = (text) => {
|
|
1208
1545
|
const trimmed = text?.trimEnd();
|
|
1209
|
-
if (!trimmed) return;
|
|
1210
|
-
if (
|
|
1546
|
+
if (!trimmed) return false;
|
|
1547
|
+
if (slackStreaming.mode === "block") {
|
|
1211
1548
|
previewToolProgressSuppressed = true;
|
|
1212
|
-
|
|
1549
|
+
progressDraft.suppress();
|
|
1213
1550
|
const next = applyAppendOnlyStreamUpdate({
|
|
1214
1551
|
incoming: trimmed,
|
|
1215
1552
|
rendered: appendRenderedText,
|
|
@@ -1217,32 +1554,34 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1217
1554
|
});
|
|
1218
1555
|
appendRenderedText = next.rendered;
|
|
1219
1556
|
appendSourceText = next.source;
|
|
1220
|
-
if (!next.changed) return;
|
|
1557
|
+
if (!next.changed) return false;
|
|
1221
1558
|
draftStream?.update(next.rendered);
|
|
1222
1559
|
hasStreamedMessage = true;
|
|
1223
|
-
return;
|
|
1560
|
+
return false;
|
|
1224
1561
|
}
|
|
1225
|
-
if (
|
|
1562
|
+
if (isProgressMode) return false;
|
|
1226
1563
|
previewToolProgressSuppressed = true;
|
|
1227
|
-
|
|
1564
|
+
progressDraft.suppress();
|
|
1228
1565
|
draftStream?.update(trimmed);
|
|
1229
1566
|
hasStreamedMessage = true;
|
|
1567
|
+
return false;
|
|
1230
1568
|
};
|
|
1231
1569
|
const pushReasoningProgress = async (payload) => {
|
|
1232
|
-
if (!payload?.text) return;
|
|
1233
|
-
if (
|
|
1570
|
+
if (!payload?.text) return false;
|
|
1571
|
+
if (!isProgressMode) {
|
|
1234
1572
|
const normalized = progressDraft.mergeReasoningProgress(payload.text, { snapshot: payload.isReasoningSnapshot === true }).replace(/^_(.*)_$/su, "$1").trim();
|
|
1235
|
-
if (!normalized) return;
|
|
1236
|
-
await pushPreviewProgress({
|
|
1573
|
+
if (!normalized) return false;
|
|
1574
|
+
const visible = await pushPreviewProgress({
|
|
1237
1575
|
id: "reasoning",
|
|
1238
1576
|
kind: "item",
|
|
1239
1577
|
text: normalized,
|
|
1240
1578
|
label: "Reasoning"
|
|
1241
1579
|
});
|
|
1242
|
-
|
|
1580
|
+
progressDraft.mergeReasoningProgress(normalized, { snapshot: true });
|
|
1581
|
+
return visible;
|
|
1243
1582
|
}
|
|
1244
1583
|
progressReceipt.noteReasoning();
|
|
1245
|
-
await progressDraft.pushReasoningProgress(payload.text, { snapshot: payload.isReasoningSnapshot === true });
|
|
1584
|
+
return await progressDraft.pushReasoningProgress(payload.text, { snapshot: payload.isReasoningSnapshot === true });
|
|
1246
1585
|
};
|
|
1247
1586
|
const resetDraftDeliveryState = () => {
|
|
1248
1587
|
hasStreamedMessage = false;
|
|
@@ -1250,28 +1589,56 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1250
1589
|
appendSourceText = "";
|
|
1251
1590
|
};
|
|
1252
1591
|
const resetDraftProgressState = () => {
|
|
1253
|
-
progressDraft.resetReasoningProgress();
|
|
1254
1592
|
previewToolProgressSuppressed = false;
|
|
1255
|
-
|
|
1593
|
+
progressDraft.reset();
|
|
1256
1594
|
};
|
|
1257
1595
|
const beginNewProgressTurn = async (options) => {
|
|
1596
|
+
const priorSnapshot = progressDraft.getSnapshot();
|
|
1597
|
+
const priorFallbackText = progressCard.resolveText(priorSnapshot);
|
|
1258
1598
|
const completionChunks = useNativeProgressStreaming && !nativeProgressCompletionSent ? buildNativeProgressCompletionChunks(nativeProgressTerminalStatus) : void 0;
|
|
1259
1599
|
if (!progressDraft.beginNewTurn(options)) return false;
|
|
1260
1600
|
if (useNativeProgressStreaming) await finishNativeProgressTurn(completionChunks);
|
|
1261
|
-
else
|
|
1601
|
+
else {
|
|
1602
|
+
await progressCard.finalize("success", priorSnapshot, priorFallbackText);
|
|
1603
|
+
draftStream?.forceNewMessage();
|
|
1604
|
+
await dropDetachedProgressCards();
|
|
1605
|
+
}
|
|
1262
1606
|
resetProgressTurnState();
|
|
1263
1607
|
nativeTaskState = /* @__PURE__ */ new Map();
|
|
1264
1608
|
nativeProgressCompletionSent = false;
|
|
1265
1609
|
nativeProgressTerminalStatus = "complete";
|
|
1266
1610
|
nativeProgressChunkKey = void 0;
|
|
1611
|
+
progressCard.reset();
|
|
1267
1612
|
resetPreviewDeliveryState();
|
|
1268
1613
|
delivery.resetDeliveryTracker();
|
|
1269
|
-
progressReceiptCollapsed = false;
|
|
1270
1614
|
return true;
|
|
1271
1615
|
};
|
|
1616
|
+
const onDraftBoundary = !shouldUseDraftStream && !useNativeProgressStreaming ? void 0 : async () => {
|
|
1617
|
+
if (isProgressMode) {
|
|
1618
|
+
await beginNewProgressTurn();
|
|
1619
|
+
return;
|
|
1620
|
+
}
|
|
1621
|
+
if (hasStreamedMessage) draftStream?.forceNewMessage();
|
|
1622
|
+
resetDraftDeliveryState();
|
|
1623
|
+
resetDraftProgressState();
|
|
1624
|
+
};
|
|
1625
|
+
const onQueuedFollowupAdmitted = !shouldUseDraftStream && !useNativeProgressStreaming ? void 0 : async () => {
|
|
1626
|
+
await draftStream?.flush();
|
|
1627
|
+
resetPreviewDeliveryState();
|
|
1628
|
+
if (isProgressMode) await beginNewProgressTurn({ force: true });
|
|
1629
|
+
else draftStream?.forceNewMessage();
|
|
1630
|
+
delivery.resetDeliveryTracker();
|
|
1631
|
+
resetDraftDeliveryState();
|
|
1632
|
+
resetDraftProgressState();
|
|
1633
|
+
};
|
|
1634
|
+
const onQueuedFollowupSettled = !useDraftProgressCard ? void 0 : async () => {
|
|
1635
|
+
if (!progressCard.hasTerminalized) await draftStream?.clear();
|
|
1636
|
+
await dropDetachedProgressCards();
|
|
1637
|
+
};
|
|
1272
1638
|
return {
|
|
1273
1639
|
draftStream,
|
|
1274
|
-
|
|
1640
|
+
isProgressMode,
|
|
1641
|
+
useDraftProgressCard,
|
|
1275
1642
|
useNativeProgressStreaming,
|
|
1276
1643
|
progressDraftActive,
|
|
1277
1644
|
previewToolProgressEnabled,
|
|
@@ -1279,15 +1646,6 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1279
1646
|
progressDraft,
|
|
1280
1647
|
commentaryProgressEnabled,
|
|
1281
1648
|
progressReceipt,
|
|
1282
|
-
get progressReceiptCollapsed() {
|
|
1283
|
-
return progressReceiptCollapsed;
|
|
1284
|
-
},
|
|
1285
|
-
get pendingNativeProgressReceipt() {
|
|
1286
|
-
return pendingNativeProgressReceipt;
|
|
1287
|
-
},
|
|
1288
|
-
set pendingNativeProgressReceipt(value) {
|
|
1289
|
-
pendingNativeProgressReceipt = value;
|
|
1290
|
-
},
|
|
1291
1649
|
get nativeProgressCompletionSent() {
|
|
1292
1650
|
return nativeProgressCompletionSent;
|
|
1293
1651
|
},
|
|
@@ -1297,33 +1655,17 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1297
1655
|
get nativeProgressTerminalStatus() {
|
|
1298
1656
|
return nativeProgressTerminalStatus;
|
|
1299
1657
|
},
|
|
1300
|
-
|
|
1301
|
-
beginNewProgressTurn,
|
|
1658
|
+
appendNativeNarration,
|
|
1302
1659
|
buildNativeProgressCompletionChunks,
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
if (hasStreamedMessage) draftStream?.forceNewMessage();
|
|
1310
|
-
resetDraftDeliveryState();
|
|
1311
|
-
resetDraftProgressState();
|
|
1312
|
-
},
|
|
1313
|
-
onQueuedFollowupAdmitted: !shouldUseDraftStream && !useNativeProgressStreaming ? void 0 : async () => {
|
|
1314
|
-
await draftStream?.flush();
|
|
1315
|
-
resetPreviewDeliveryState();
|
|
1316
|
-
if (streamMode === "status_final") await beginNewProgressTurn({ force: true });
|
|
1317
|
-
else draftStream?.forceNewMessage();
|
|
1318
|
-
delivery.resetDeliveryTracker();
|
|
1319
|
-
resetDraftDeliveryState();
|
|
1320
|
-
resetDraftProgressState();
|
|
1321
|
-
},
|
|
1660
|
+
deliverNativeFinal,
|
|
1661
|
+
dropDetachedProgressCards,
|
|
1662
|
+
finalizeDraftProgressCard: progressCard.finalize,
|
|
1663
|
+
onDraftBoundary,
|
|
1664
|
+
onQueuedFollowupAdmitted,
|
|
1665
|
+
onQueuedFollowupSettled,
|
|
1322
1666
|
pushPlanProgress,
|
|
1323
|
-
pushPreviewProgress,
|
|
1324
1667
|
pushReasoningProgress,
|
|
1325
1668
|
updateDraftFromPartial,
|
|
1326
|
-
waitForNativeProgressStreamStart,
|
|
1327
1669
|
setShouldYieldDraftProgress: (value) => {
|
|
1328
1670
|
shouldYieldDraftProgress = value;
|
|
1329
1671
|
},
|
|
@@ -1398,7 +1740,7 @@ async function createSlackDispatchSetup(prepared) {
|
|
|
1398
1740
|
sessionKey: inboundLastRouteSessionKey,
|
|
1399
1741
|
deliveryContext: {
|
|
1400
1742
|
channel: "slack",
|
|
1401
|
-
to: `user:${message.user}`,
|
|
1743
|
+
to: prepared.ctxPayload.OriginatingTo ?? prepared.ctxPayload.To ?? `user:${message.user}`,
|
|
1402
1744
|
accountId: route.accountId,
|
|
1403
1745
|
threadId: prepared.ctxPayload.MessageThreadId ?? prepared.ctxPayload.TransportThreadId
|
|
1404
1746
|
},
|
|
@@ -1434,6 +1776,7 @@ async function createSlackDispatchSetup(prepared) {
|
|
|
1434
1776
|
const messageTs = message.ts ?? message.event_ts;
|
|
1435
1777
|
const incomingThreadTs = message.thread_ts;
|
|
1436
1778
|
let didSetStatus = false;
|
|
1779
|
+
let didAddTypingReaction = false;
|
|
1437
1780
|
const statusReactionsEnabled = prepared.ctxPayload.InboundEventKind !== "room_event" && Boolean(prepared.ackReactionPromise) && Boolean(reactionMessageTs) && cfg.messages?.statusReactions?.enabled === true;
|
|
1438
1781
|
const statusReactions = createStatusReactionController({
|
|
1439
1782
|
enabled: statusReactionsEnabled,
|
|
@@ -1480,6 +1823,7 @@ async function createSlackDispatchSetup(prepared) {
|
|
|
1480
1823
|
});
|
|
1481
1824
|
const typingTarget = statusThreadTs ? `${message.channel}/${statusThreadTs}` : message.channel;
|
|
1482
1825
|
const typingReaction = ctx.typingReaction;
|
|
1826
|
+
const threadStatusGate = { hasVisibleOutput: () => false };
|
|
1483
1827
|
const { onModelSelected, ...replyPipeline } = createChannelMessageReplyPipeline({
|
|
1484
1828
|
cfg,
|
|
1485
1829
|
agentId: route.agentId,
|
|
@@ -1491,35 +1835,44 @@ async function createSlackDispatchSetup(prepared) {
|
|
|
1491
1835
|
},
|
|
1492
1836
|
typing: {
|
|
1493
1837
|
start: async () => {
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1838
|
+
if (!threadStatusGate.hasVisibleOutput()) {
|
|
1839
|
+
didSetStatus = true;
|
|
1840
|
+
await ctx.setSlackThreadStatus({
|
|
1841
|
+
channelId: message.channel,
|
|
1842
|
+
threadTs: statusThreadTs,
|
|
1843
|
+
status: "is typing...",
|
|
1844
|
+
eventScope: prepared.eventScope
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1847
|
+
if (typingReaction && message.ts) {
|
|
1848
|
+
didAddTypingReaction = true;
|
|
1849
|
+
await reactSlackMessage(message.channel, message.ts, typingReaction, {
|
|
1850
|
+
token: ctx.botToken,
|
|
1851
|
+
client: slackClient
|
|
1852
|
+
}).catch((err) => {
|
|
1853
|
+
logVerbose(`slack send: typing reaction failed: ${formatSlackError(err)}`);
|
|
1854
|
+
});
|
|
1855
|
+
}
|
|
1507
1856
|
},
|
|
1508
1857
|
stop: async () => {
|
|
1509
|
-
if (
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1858
|
+
if (didSetStatus) {
|
|
1859
|
+
didSetStatus = false;
|
|
1860
|
+
await ctx.setSlackThreadStatus({
|
|
1861
|
+
channelId: message.channel,
|
|
1862
|
+
threadTs: statusThreadTs,
|
|
1863
|
+
status: "",
|
|
1864
|
+
eventScope: prepared.eventScope
|
|
1865
|
+
});
|
|
1866
|
+
}
|
|
1867
|
+
if (didAddTypingReaction && typingReaction && message.ts) {
|
|
1868
|
+
didAddTypingReaction = false;
|
|
1869
|
+
await removeSlackReaction(message.channel, message.ts, typingReaction, {
|
|
1870
|
+
token: ctx.botToken,
|
|
1871
|
+
client: slackClient
|
|
1872
|
+
}).catch((err) => {
|
|
1873
|
+
logVerbose(`slack send: typing reaction removal failed: ${formatSlackError(err)}`);
|
|
1874
|
+
});
|
|
1875
|
+
}
|
|
1523
1876
|
},
|
|
1524
1877
|
onStartError: (err) => {
|
|
1525
1878
|
logTypingFailure({
|
|
@@ -1541,10 +1894,7 @@ async function createSlackDispatchSetup(prepared) {
|
|
|
1541
1894
|
}
|
|
1542
1895
|
}
|
|
1543
1896
|
});
|
|
1544
|
-
const slackStreaming = resolveSlackStreamingConfig({
|
|
1545
|
-
streaming: account.config.streaming,
|
|
1546
|
-
nativeStreaming: resolveChannelStreamingNativeTransport(account.config)
|
|
1547
|
-
});
|
|
1897
|
+
const slackStreaming = resolveSlackStreamingConfig({ streaming: account.config.streaming });
|
|
1548
1898
|
const streamThreadHint = forcedReplyThreadTs ?? resolveSlackStreamingThreadHint({
|
|
1549
1899
|
replyToMode: replyDeliveryMode,
|
|
1550
1900
|
incomingThreadTs,
|
|
@@ -1553,7 +1903,7 @@ async function createSlackDispatchSetup(prepared) {
|
|
|
1553
1903
|
});
|
|
1554
1904
|
const hookRunner = getGlobalHookRunner();
|
|
1555
1905
|
const allowPreHookProviderStreaming = !((hookRunner?.hasHooks("reply_payload_sending") ?? false) || (hookRunner?.hasHooks("message_sending") ?? false));
|
|
1556
|
-
const previewStreamingEnabled = allowPreHookProviderStreaming && !sourceRepliesAreToolOnly &&
|
|
1906
|
+
const previewStreamingEnabled = allowPreHookProviderStreaming && !sourceRepliesAreToolOnly && slackStreaming.mode !== "off";
|
|
1557
1907
|
const hasSlackCustomIdentity = Boolean(slackIdentity?.username || slackIdentity?.iconUrl || slackIdentity?.iconEmoji);
|
|
1558
1908
|
const useStreaming = shouldUseStreaming({
|
|
1559
1909
|
streamingEnabled: !sourceRepliesAreToolOnly && (allowPreHookProviderStreaming || slackStreaming.mode !== "progress") && isSlackStreamingEnabled({
|
|
@@ -1563,10 +1913,7 @@ async function createSlackDispatchSetup(prepared) {
|
|
|
1563
1913
|
}),
|
|
1564
1914
|
threadTs: streamThreadHint
|
|
1565
1915
|
});
|
|
1566
|
-
const shouldUseDraftStream =
|
|
1567
|
-
previewStreamingEnabled,
|
|
1568
|
-
useStreaming
|
|
1569
|
-
});
|
|
1916
|
+
const shouldUseDraftStream = previewStreamingEnabled && !useStreaming;
|
|
1570
1917
|
const blockStreamingEnabled = resolveChannelStreamingBlockEnabled(account.config);
|
|
1571
1918
|
const disableBlockStreaming = sourceRepliesAreToolOnly ? true : resolveSlackDisableBlockStreaming({
|
|
1572
1919
|
useStreaming,
|
|
@@ -1604,6 +1951,7 @@ async function createSlackDispatchSetup(prepared) {
|
|
|
1604
1951
|
statusReactionsEnabled,
|
|
1605
1952
|
statusReactions,
|
|
1606
1953
|
hasRepliedRef,
|
|
1954
|
+
threadStatusGate,
|
|
1607
1955
|
replyPlan,
|
|
1608
1956
|
onModelSelected,
|
|
1609
1957
|
replyPipeline,
|
|
@@ -1752,7 +2100,7 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
1752
2100
|
...slackMessageMetadata ? { metadata: slackMessageMetadata } : {},
|
|
1753
2101
|
...messageSentDeliveryHookContext,
|
|
1754
2102
|
deferMessageSentHooks: true,
|
|
1755
|
-
|
|
2103
|
+
eventScope: prepared.eventScope
|
|
1756
2104
|
});
|
|
1757
2105
|
markSlackStreamFallbackDelivered(session);
|
|
1758
2106
|
if (!session.stopped) try {
|
|
@@ -1800,7 +2148,7 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
1800
2148
|
...slackIdentity ? { identity: slackIdentity } : {},
|
|
1801
2149
|
...slackMessageMetadata ? { metadata: slackMessageMetadata } : {},
|
|
1802
2150
|
...messageSentDeliveryHookContext,
|
|
1803
|
-
|
|
2151
|
+
eventScope: prepared.eventScope
|
|
1804
2152
|
});
|
|
1805
2153
|
state.observedReplyDelivery = true;
|
|
1806
2154
|
if (params.kind === "final") state.observedFinalReplyDelivery = true;
|
|
@@ -1834,12 +2182,16 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
1834
2182
|
rememberDeliveredThreadTs(params.kind, params.session.threadTs);
|
|
1835
2183
|
return true;
|
|
1836
2184
|
};
|
|
2185
|
+
const isStreamingEligible = (payload, options) => {
|
|
2186
|
+
const reply = resolveSendableOutboundReplyParts(payload);
|
|
2187
|
+
const renderPlan = resolveSlackReplyRenderPlan(payload);
|
|
2188
|
+
const plannedBlocks = renderPlan.mode === "single" ? renderPlan.blocks : renderPlan.blockPart?.blocks;
|
|
2189
|
+
return !state.streamFailed && !reply.hasMedia && renderPlan.mode !== "split" && !plannedBlocks?.length && !readSlackReplyBlocks(payload)?.length && reply.hasText && (!options?.maxTextBytes || countSlackTextUtf8Bytes(reply.trimmedText) <= options.maxTextBytes);
|
|
2190
|
+
};
|
|
1837
2191
|
const deliverWithStreaming = async (params) => {
|
|
1838
2192
|
if (params.payload.isReasoning === true) return;
|
|
1839
2193
|
const reply = resolveSendableOutboundReplyParts(params.payload);
|
|
1840
|
-
|
|
1841
|
-
const plannedBlocks = renderPlan.mode === "single" ? renderPlan.blocks : renderPlan.blockPart?.blocks;
|
|
1842
|
-
if (state.streamFailed || reply.hasMedia || renderPlan.mode === "split" || Boolean(plannedBlocks?.length) || readSlackReplyBlocks(params.payload)?.length || !reply.hasText) {
|
|
2194
|
+
if (!isStreamingEligible(params.payload)) {
|
|
1843
2195
|
await deliverNormally({
|
|
1844
2196
|
payload: params.payload,
|
|
1845
2197
|
kind: params.kind,
|
|
@@ -1847,7 +2199,8 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
1847
2199
|
});
|
|
1848
2200
|
return;
|
|
1849
2201
|
}
|
|
1850
|
-
const text = reply.trimmedText;
|
|
2202
|
+
const text = params.streamText ?? reply.trimmedText;
|
|
2203
|
+
const hookContent = reply.trimmedText;
|
|
1851
2204
|
let plannedThreadTs;
|
|
1852
2205
|
try {
|
|
1853
2206
|
if (!state.streamSession && state.nativeProgressStreamStartPromise) await state.nativeProgressStreamStartPromise;
|
|
@@ -1885,6 +2238,7 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
1885
2238
|
channel: message.channel,
|
|
1886
2239
|
threadTs: streamThreadTs,
|
|
1887
2240
|
text,
|
|
2241
|
+
...params.taskDisplayMode ? { taskDisplayMode: params.taskDisplayMode } : {},
|
|
1888
2242
|
...slackIdentity ? { identity: slackIdentity } : {},
|
|
1889
2243
|
teamId: await resolveSlackStreamRecipientTeamId({
|
|
1890
2244
|
client: slackClient,
|
|
@@ -1899,7 +2253,7 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
1899
2253
|
state.observedReplyDelivery = true;
|
|
1900
2254
|
if (params.kind === "final") state.observedFinalReplyDelivery = true;
|
|
1901
2255
|
}
|
|
1902
|
-
if (
|
|
2256
|
+
if (hookContent) rememberStreamedDelivery(params.kind, hookContent, state.streamSession);
|
|
1903
2257
|
rememberDeliveredThreadTs(params.kind, streamThreadTs);
|
|
1904
2258
|
replyPlan.markSent();
|
|
1905
2259
|
deliveryTracker.markDelivered({
|
|
@@ -1919,13 +2273,13 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
1919
2273
|
logVerbose("slack-stream: suppressed duplicate append payload");
|
|
1920
2274
|
return;
|
|
1921
2275
|
}
|
|
1922
|
-
if (
|
|
2276
|
+
if (hookContent) recordStreamedDelivery(params.kind, hookContent);
|
|
1923
2277
|
await appendSlackStream({
|
|
1924
2278
|
session: state.streamSession,
|
|
1925
|
-
text: "\n"
|
|
2279
|
+
text: `${params.appendSeparator === false ? "" : "\n"}${text}`
|
|
1926
2280
|
});
|
|
1927
2281
|
refreshStreamedAcknowledgements(state.streamSession);
|
|
1928
|
-
if (state.streamSession.delivered) {
|
|
2282
|
+
if (state.streamSession.delivered && state.streamSession.pendingText.length === 0) {
|
|
1929
2283
|
state.observedReplyDelivery = true;
|
|
1930
2284
|
if (params.kind === "final") state.observedFinalReplyDelivery = true;
|
|
1931
2285
|
}
|
|
@@ -2000,6 +2354,7 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
2000
2354
|
emitAcknowledgedStreamedDeliveries,
|
|
2001
2355
|
emitFailedPendingStreamedDeliveries,
|
|
2002
2356
|
hasDelivered: (params) => deliveryTracker.hasDelivered(params),
|
|
2357
|
+
isStreamingEligible,
|
|
2003
2358
|
markPreviewPayloadDelivered,
|
|
2004
2359
|
rememberDeliveredThreadTs,
|
|
2005
2360
|
reconcileCounts,
|
|
@@ -2024,47 +2379,66 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2024
2379
|
}
|
|
2025
2380
|
});
|
|
2026
2381
|
const draftStream = progress.draftStream;
|
|
2382
|
+
setup.threadStatusGate.hasVisibleOutput = () => delivery.observedReplyDelivery || draftPreviewCommitted.value || Boolean(draftStream?.messageId());
|
|
2383
|
+
const failureNoticeThreadTs = message.thread_ts;
|
|
2384
|
+
const failureNoticeTeamId = prepared.eventScope?.teamId;
|
|
2385
|
+
let sawTerminalFailurePayload = false;
|
|
2386
|
+
let pendingFailureNotice;
|
|
2387
|
+
const filterPassiveThreadFailure = (payload) => {
|
|
2388
|
+
if (payload.isError !== true || prepared.ctxPayload.ChatType !== "channel" || isReplyPayloadNonTerminalToolErrorWarning(payload)) return payload;
|
|
2389
|
+
sawTerminalFailurePayload = true;
|
|
2390
|
+
if (delivery.observedReplyDelivery || draftPreviewCommitted.value) return payload;
|
|
2391
|
+
const explicitlyAddressed = prepared.ctxPayload.ExplicitlyMentionedBot === true || prepared.ctxPayload.MentionSource === "explicit_bot" || prepared.ctxPayload.MentionSource === "subteam" || prepared.ctxPayload.MentionSource === "mention_pattern" || prepared.ctxPayload.MentionSource === "command_bypass" || prepared.ctxPayload.CommandTurn?.kind !== void 0 && prepared.ctxPayload.CommandTurn.kind !== "normal" && prepared.ctxPayload.CommandTurn.authorized;
|
|
2392
|
+
const noticeThreadTs = failureNoticeThreadTs ?? (explicitlyAddressed ? statusThreadTs : void 0);
|
|
2393
|
+
const notice = {
|
|
2394
|
+
accountId: account.accountId,
|
|
2395
|
+
channelId: message.channel,
|
|
2396
|
+
...noticeThreadTs ? { threadTs: noticeThreadTs } : {},
|
|
2397
|
+
failureText: payload.text ?? "",
|
|
2398
|
+
...failureNoticeTeamId ? { teamId: failureNoticeTeamId } : {}
|
|
2399
|
+
};
|
|
2400
|
+
if (failureNoticeThreadTs && !explicitlyAddressed && prepared.ctxPayload.MentionSource !== "implicit_thread" && !hasSlackThreadParticipation(notice.accountId, notice.channelId, failureNoticeThreadTs, failureNoticeTeamId)) {
|
|
2401
|
+
logVerbose("slack: suppressed passive failure before thread participation");
|
|
2402
|
+
return null;
|
|
2403
|
+
}
|
|
2404
|
+
if (!explicitlyAddressed && hasSlackThreadFailureNotice(notice)) {
|
|
2405
|
+
logVerbose("slack: suppressed repeated passive channel or thread failure");
|
|
2406
|
+
return null;
|
|
2407
|
+
}
|
|
2408
|
+
pendingFailureNotice = notice;
|
|
2409
|
+
return payload;
|
|
2410
|
+
};
|
|
2027
2411
|
const deliverSlackPayload = async (payload, info) => {
|
|
2028
2412
|
if (payload.isReasoning === true) return { visibleReplySent: false };
|
|
2029
|
-
if (info.kind === "final" && slackStreaming.mode === "progress" && progress.
|
|
2030
|
-
const hadProgressDraft = progress.progressDraft.hasStarted;
|
|
2031
|
-
progress.progressDraft.markFinalReplyStarted();
|
|
2413
|
+
if (info.kind === "final" && slackStreaming.mode === "progress" && progress.isProgressMode) {
|
|
2032
2414
|
if (progress.useNativeProgressStreaming) {
|
|
2033
|
-
await progress.
|
|
2034
|
-
|
|
2415
|
+
await progress.deliverNativeFinal(payload, info.kind);
|
|
2416
|
+
return;
|
|
2417
|
+
}
|
|
2418
|
+
progress.progressDraft.markFinalReplyStarted();
|
|
2419
|
+
if (progress.useDraftProgressCard) {
|
|
2035
2420
|
await delivery.deliverNormally({
|
|
2036
2421
|
payload,
|
|
2037
2422
|
kind: info.kind,
|
|
2038
|
-
forcedThreadTs:
|
|
2423
|
+
forcedThreadTs: delivery.usedReplyThreadTs
|
|
2039
2424
|
});
|
|
2040
|
-
await progress.
|
|
2425
|
+
if (!await progress.finalizeDraftProgressCard(payload.isError === true ? "error" : "success")) await draftStream?.clear();
|
|
2041
2426
|
progress.progressDraft.markFinalReplyDelivered();
|
|
2042
|
-
if (!payload.isError && hadProgressDraft && delivery.streamSession) progress.pendingNativeProgressReceipt = progress.progressReceipt.buildSummaryLine();
|
|
2043
2427
|
return;
|
|
2044
2428
|
}
|
|
2045
|
-
if (hadProgressDraft) try {
|
|
2046
|
-
await draftStream?.flush();
|
|
2047
|
-
} catch (err) {
|
|
2048
|
-
logVerbose(`slack: progress draft flush before final failed (${formatSlackError(err)})`);
|
|
2049
|
-
}
|
|
2050
|
-
const receiptChannelId = hadProgressDraft ? draftStream?.channelId() : void 0;
|
|
2051
|
-
const receiptMessageId = hadProgressDraft ? draftStream?.messageId() : void 0;
|
|
2052
|
-
const draftThreadTs = hadProgressDraft ? delivery.usedReplyThreadTs ?? statusThreadTs : void 0;
|
|
2053
|
-
await delivery.deliverNormally({
|
|
2054
|
-
payload,
|
|
2055
|
-
kind: info.kind,
|
|
2056
|
-
...draftThreadTs ? { forcedThreadTs: draftThreadTs } : {}
|
|
2057
|
-
});
|
|
2058
|
-
progress.progressDraft.markFinalReplyDelivered();
|
|
2059
|
-
if (!payload.isError && receiptChannelId && receiptMessageId && !progress.progressReceiptCollapsed) await progress.collapseProgressReceipt({
|
|
2060
|
-
channelId: receiptChannelId,
|
|
2061
|
-
messageId: receiptMessageId,
|
|
2062
|
-
text: progress.progressReceipt.buildSummaryLine(),
|
|
2063
|
-
threadTs: delivery.usedReplyThreadTs ?? statusThreadTs
|
|
2064
|
-
});
|
|
2065
|
-
return;
|
|
2066
2429
|
}
|
|
2067
2430
|
if (progress.useNativeProgressStreaming) {
|
|
2431
|
+
if (info.kind !== "final" && payload.isError !== true) {
|
|
2432
|
+
if (!delivery.isStreamingEligible(payload)) {
|
|
2433
|
+
await delivery.deliverNormally({
|
|
2434
|
+
payload,
|
|
2435
|
+
kind: info.kind,
|
|
2436
|
+
forcedThreadTs: delivery.streamSession?.threadTs ?? delivery.nativeProgressStreamThreadTs
|
|
2437
|
+
});
|
|
2438
|
+
return;
|
|
2439
|
+
}
|
|
2440
|
+
return await progress.appendNativeNarration(payload, info.kind) ? void 0 : { visibleReplySent: false };
|
|
2441
|
+
}
|
|
2068
2442
|
await delivery.deliverNormally({
|
|
2069
2443
|
payload,
|
|
2070
2444
|
kind: info.kind,
|
|
@@ -2087,25 +2461,28 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2087
2461
|
const requiresSeparateFallbackDelivery = replyRenderPlan.mode === "split";
|
|
2088
2462
|
const trimmedFinalText = replyRenderPlan.mode === "single" ? replyRenderPlan.text.trim() : replyRenderPlan.fallbackText.trim();
|
|
2089
2463
|
const previewFinalText = replyRenderPlan.mode === "single" && replyRenderPlan.textIsSlackMrkdwn ? trimmedFinalText : normalizeSlackOutboundText((replySourceText ?? "").trim());
|
|
2464
|
+
const previewFinalTextFitsEdit = countSlackTextUtf8Bytes(previewFinalText) <= SLACK_EDIT_TEXT_MAX_BYTES;
|
|
2090
2465
|
const shouldRestoreTtsSupplementTextForPreviewFallback = Boolean(ttsSupplement) && ttsSupplement?.visibleTextAlreadyDelivered !== true && Boolean(draftStream) && !draftPreviewCommitted.value && !delivery.observedFinalReplyDelivery && previewStreamingEnabled && !payload.text?.trim();
|
|
2091
|
-
if (info.kind === "final" && ttsSupplement && draftStream && !hasSlackCustomIdentity && !draftPreviewCommitted.value && !delivery.observedFinalReplyDelivery && previewStreamingEnabled && !payload.isError && !requiresSeparateFallbackDelivery && trimmedFinalText.length > 0) {
|
|
2466
|
+
if (info.kind === "final" && ttsSupplement && draftStream && !hasSlackCustomIdentity && !draftPreviewCommitted.value && !delivery.observedFinalReplyDelivery && previewStreamingEnabled && !payload.isError && !requiresSeparateFallbackDelivery && previewFinalTextFitsEdit && trimmedFinalText.length > 0) {
|
|
2467
|
+
await draftStream.flush();
|
|
2092
2468
|
const channelId = draftStream.channelId();
|
|
2093
2469
|
const messageId = draftStream.messageId();
|
|
2094
2470
|
if (channelId && messageId) {
|
|
2095
2471
|
const finalThreadTs = delivery.usedReplyThreadTs ?? statusThreadTs;
|
|
2096
|
-
await draftStream.flush();
|
|
2097
2472
|
await draftStream.seal();
|
|
2098
2473
|
try {
|
|
2099
|
-
await
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2474
|
+
if (!await draftStream.finalizeMessage(messageId, async () => {
|
|
2475
|
+
await finalizeSlackPreviewEdit({
|
|
2476
|
+
client: slackClient,
|
|
2477
|
+
token: ctx.botToken,
|
|
2478
|
+
accountId: account.accountId,
|
|
2479
|
+
channelId,
|
|
2480
|
+
messageId,
|
|
2481
|
+
text: previewFinalText,
|
|
2482
|
+
...slackBlocks?.length ? { blocks: slackBlocks } : {},
|
|
2483
|
+
threadTs: finalThreadTs
|
|
2484
|
+
});
|
|
2485
|
+
})) throw new Error("Slack preview moved below a newer conversation message");
|
|
2109
2486
|
} catch (err) {
|
|
2110
2487
|
logVerbose(`slack: preview final edit failed; falling back to standard send (${formatSlackError(err)})`);
|
|
2111
2488
|
await draftStream.discardPending();
|
|
@@ -2139,6 +2516,7 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2139
2516
|
payload,
|
|
2140
2517
|
threadTs: finalThreadTs
|
|
2141
2518
|
});
|
|
2519
|
+
progress.progressDraft.markFinalReplyDelivered();
|
|
2142
2520
|
return;
|
|
2143
2521
|
}
|
|
2144
2522
|
}
|
|
@@ -2161,7 +2539,7 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2161
2539
|
}
|
|
2162
2540
|
} : void 0,
|
|
2163
2541
|
buildFinalEdit: () => {
|
|
2164
|
-
if (hasSlackCustomIdentity || !previewStreamingEnabled || reply.hasMedia && !ttsSupplement || payload.isError || requiresSeparateFallbackDelivery || trimmedFinalText.length === 0 && !slackBlocks?.length) return;
|
|
2542
|
+
if (hasSlackCustomIdentity || !previewStreamingEnabled || reply.hasMedia && !ttsSupplement || payload.isError || requiresSeparateFallbackDelivery || !previewFinalTextFitsEdit || trimmedFinalText.length === 0 && !slackBlocks?.length) return;
|
|
2165
2543
|
return {
|
|
2166
2544
|
text: previewFinalText,
|
|
2167
2545
|
blocks: slackBlocks,
|
|
@@ -2174,16 +2552,18 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2174
2552
|
payload,
|
|
2175
2553
|
threadTs: edit.threadTs
|
|
2176
2554
|
})) return;
|
|
2177
|
-
await
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2555
|
+
if (!await draftStream?.finalizeMessage(preview.messageId, async () => {
|
|
2556
|
+
await finalizeSlackPreviewEdit({
|
|
2557
|
+
client: slackClient,
|
|
2558
|
+
token: ctx.botToken,
|
|
2559
|
+
accountId: account.accountId,
|
|
2560
|
+
channelId: preview.channelId,
|
|
2561
|
+
messageId: preview.messageId,
|
|
2562
|
+
text: edit.text,
|
|
2563
|
+
...edit.blocks?.length ? { blocks: edit.blocks } : {},
|
|
2564
|
+
threadTs: edit.threadTs
|
|
2565
|
+
});
|
|
2566
|
+
})) throw new Error("Slack preview moved below a newer conversation message");
|
|
2187
2567
|
if (!ttsSupplement) emitSlackMessageSentHooks({
|
|
2188
2568
|
...messageSentHookContext,
|
|
2189
2569
|
to: messageSentHookTarget,
|
|
@@ -2235,8 +2615,10 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2235
2615
|
});
|
|
2236
2616
|
}
|
|
2237
2617
|
});
|
|
2618
|
+
if (info.kind === "final") progress.progressDraft.markFinalReplyDelivered();
|
|
2238
2619
|
};
|
|
2239
2620
|
let dispatchError;
|
|
2621
|
+
let agentRunFailed = false;
|
|
2240
2622
|
let queuedFinal = false;
|
|
2241
2623
|
let counts = {};
|
|
2242
2624
|
try {
|
|
@@ -2251,6 +2633,10 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2251
2633
|
ctxPayload: prepared.ctxPayload,
|
|
2252
2634
|
dispatcherOptions: {
|
|
2253
2635
|
...replyPipeline,
|
|
2636
|
+
transformReplyPayload: (payload) => {
|
|
2637
|
+
const transformed = replyPipeline.transformReplyPayload ? replyPipeline.transformReplyPayload(payload) : payload;
|
|
2638
|
+
return transformed ? filterPassiveThreadFailure(transformed) : null;
|
|
2639
|
+
},
|
|
2254
2640
|
humanDelay: resolveHumanDelayConfig(cfg, route.agentId)
|
|
2255
2641
|
},
|
|
2256
2642
|
delivery: {
|
|
@@ -2272,102 +2658,62 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2272
2658
|
commentaryProgressEnabled: progress.commentaryProgressEnabled ? true : void 0,
|
|
2273
2659
|
progressPreambleEnabled: progress.progressDraftActive && slackStreaming.mode === "progress" ? true : void 0,
|
|
2274
2660
|
commentaryPayloadsEnabled: progress.commentaryProgressEnabled ? true : void 0,
|
|
2661
|
+
shouldDeliverCommentaryPayloads: progress.commentaryProgressEnabled ? progress.shouldYieldDraftProgress : void 0,
|
|
2275
2662
|
onVerboseProgressVisibility: progress.commentaryProgressEnabled ? (isActive) => {
|
|
2276
2663
|
progress.setShouldYieldDraftProgress(isActive);
|
|
2277
2664
|
} : void 0,
|
|
2278
2665
|
allowProgressCallbacksWhenSourceDeliverySuppressed: sourceReplyDeliveryMode === "message_tool_only" && statusReactionsEnabled ? true : void 0,
|
|
2279
2666
|
allowToolLifecycleWhenProgressHidden: statusReactionsEnabled ? true : void 0,
|
|
2280
2667
|
onPartialReply: useStreaming ? void 0 : !previewStreamingEnabled ? void 0 : async (payload) => {
|
|
2281
|
-
progress.updateDraftFromPartial(payload.text);
|
|
2668
|
+
return progress.updateDraftFromPartial(payload.text);
|
|
2282
2669
|
},
|
|
2283
|
-
onAssistantMessageStart: progress.onDraftBoundary
|
|
2670
|
+
onAssistantMessageStart: progress.onDraftBoundary ? async () => {
|
|
2671
|
+
await progress.onDraftBoundary?.();
|
|
2672
|
+
return false;
|
|
2673
|
+
} : void 0,
|
|
2284
2674
|
onReasoningEnd: async () => {
|
|
2285
2675
|
progress.progressReceipt.closeReasoning();
|
|
2286
2676
|
await progress.onDraftBoundary?.();
|
|
2677
|
+
return false;
|
|
2287
2678
|
},
|
|
2288
2679
|
onQueuedFollowupAdmitted: progress.onQueuedFollowupAdmitted,
|
|
2680
|
+
onQueuedFollowupSettled: progress.onQueuedFollowupSettled,
|
|
2289
2681
|
onReasoningStream: statusReactionsEnabled || progress.previewToolProgressEnabled ? async (payload) => {
|
|
2290
|
-
await progress.pushReasoningProgress(payload);
|
|
2291
|
-
if (!statusReactionsEnabled) return;
|
|
2682
|
+
const visible = await progress.pushReasoningProgress(payload);
|
|
2683
|
+
if (!statusReactionsEnabled) return visible;
|
|
2292
2684
|
await statusReactions.setThinking();
|
|
2685
|
+
return visible;
|
|
2293
2686
|
} : void 0,
|
|
2294
2687
|
onToolStart: async (payload) => {
|
|
2295
2688
|
if (statusReactionsEnabled) await statusReactions.setTool(payload.name);
|
|
2296
2689
|
if (payload.phase === "start") progress.progressReceipt.noteToolCall(payload.name);
|
|
2297
|
-
await progress.
|
|
2298
|
-
event: "tool",
|
|
2299
|
-
itemId: payload.itemId,
|
|
2300
|
-
toolCallId: payload.toolCallId,
|
|
2301
|
-
name: payload.name,
|
|
2302
|
-
phase: payload.phase,
|
|
2303
|
-
args: payload.args
|
|
2304
|
-
}, payload.detailMode ? { detailMode: payload.detailMode } : void 0), { toolName: payload.name });
|
|
2690
|
+
return await progress.progressDraft.pushToolEvent(payload);
|
|
2305
2691
|
},
|
|
2306
2692
|
onItemEvent: async (payload) => {
|
|
2307
|
-
if (progress.
|
|
2308
|
-
if (progress.shouldYieldDraftProgress()) return;
|
|
2309
|
-
await progress.progressDraft.pushPreambleHeadline(payload.progressText, { itemId: payload.itemId });
|
|
2693
|
+
if (progress.isProgressMode && payload.kind === "preamble") {
|
|
2694
|
+
if (progress.shouldYieldDraftProgress()) return false;
|
|
2695
|
+
const headlineVisible = await progress.progressDraft.pushPreambleHeadline(payload.progressText, { itemId: payload.itemId });
|
|
2310
2696
|
if (progress.commentaryProgressEnabled) {
|
|
2311
|
-
|
|
2697
|
+
const accepted = await progress.progressDraft.pushCommentaryProgress(payload.progressText, { itemId: payload.itemId });
|
|
2698
|
+
if (accepted) progress.progressReceipt.noteCommentary(payload.itemId, payload.progressText);
|
|
2699
|
+
return accepted || headlineVisible;
|
|
2312
2700
|
}
|
|
2313
|
-
return;
|
|
2701
|
+
return headlineVisible;
|
|
2314
2702
|
}
|
|
2315
|
-
await progress.
|
|
2316
|
-
event: "item",
|
|
2317
|
-
itemId: payload.itemId,
|
|
2318
|
-
toolCallId: payload.toolCallId,
|
|
2319
|
-
itemKind: payload.kind,
|
|
2320
|
-
title: payload.title,
|
|
2321
|
-
name: payload.name,
|
|
2322
|
-
phase: payload.phase,
|
|
2323
|
-
status: payload.status,
|
|
2324
|
-
summary: payload.summary,
|
|
2325
|
-
progressText: payload.progressText,
|
|
2326
|
-
meta: payload.meta
|
|
2327
|
-
}));
|
|
2703
|
+
return await progress.progressDraft.pushItemEvent(payload);
|
|
2328
2704
|
},
|
|
2329
2705
|
onPlanUpdate: async (payload) => {
|
|
2330
|
-
if (payload.phase !== "update") return;
|
|
2331
|
-
await progress.pushPlanProgress(payload.steps, payload.explanation);
|
|
2706
|
+
if (payload.phase !== "update") return false;
|
|
2707
|
+
return await progress.pushPlanProgress(payload.steps, payload.explanation);
|
|
2332
2708
|
},
|
|
2333
2709
|
onApprovalEvent: async (payload) => {
|
|
2334
|
-
|
|
2335
|
-
await progress.pushPreviewProgress(buildChannelProgressDraftLine({
|
|
2336
|
-
event: "approval",
|
|
2337
|
-
phase: payload.phase,
|
|
2338
|
-
title: payload.title,
|
|
2339
|
-
command: payload.command,
|
|
2340
|
-
reason: payload.reason,
|
|
2341
|
-
message: payload.message
|
|
2342
|
-
}));
|
|
2710
|
+
return await progress.progressDraft.pushApprovalEvent(payload);
|
|
2343
2711
|
},
|
|
2344
2712
|
onCommandOutput: async (payload) => {
|
|
2345
|
-
|
|
2346
|
-
await progress.pushPreviewProgress(buildChannelProgressDraftLine({
|
|
2347
|
-
event: "command-output",
|
|
2348
|
-
itemId: payload.itemId,
|
|
2349
|
-
toolCallId: payload.toolCallId,
|
|
2350
|
-
phase: payload.phase,
|
|
2351
|
-
title: payload.title,
|
|
2352
|
-
name: payload.name,
|
|
2353
|
-
status: payload.status,
|
|
2354
|
-
exitCode: payload.exitCode
|
|
2355
|
-
}));
|
|
2713
|
+
return await progress.progressDraft.pushCommandOutputEvent(payload);
|
|
2356
2714
|
},
|
|
2357
2715
|
onPatchSummary: async (payload) => {
|
|
2358
|
-
|
|
2359
|
-
await progress.pushPreviewProgress(buildChannelProgressDraftLine({
|
|
2360
|
-
event: "patch",
|
|
2361
|
-
itemId: payload.itemId,
|
|
2362
|
-
toolCallId: payload.toolCallId,
|
|
2363
|
-
phase: payload.phase,
|
|
2364
|
-
title: payload.title,
|
|
2365
|
-
name: payload.name,
|
|
2366
|
-
added: payload.added,
|
|
2367
|
-
modified: payload.modified,
|
|
2368
|
-
deleted: payload.deleted,
|
|
2369
|
-
summary: payload.summary
|
|
2370
|
-
}));
|
|
2716
|
+
return await progress.progressDraft.pushPatchEvent(payload);
|
|
2371
2717
|
}
|
|
2372
2718
|
}
|
|
2373
2719
|
});
|
|
@@ -2375,17 +2721,25 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2375
2721
|
const result = turnResult.dispatchResult;
|
|
2376
2722
|
queuedFinal = result.queuedFinal;
|
|
2377
2723
|
counts = result.counts;
|
|
2724
|
+
const agentRunOutcome = readAgentRunTerminalOutcome(result);
|
|
2725
|
+
agentRunFailed = agentRunOutcome === "failed";
|
|
2726
|
+
if (agentRunOutcome === "completed" && !sawTerminalFailurePayload && prepared.ctxPayload.ChatType === "channel") clearSlackThreadFailureNotice({
|
|
2727
|
+
accountId: account.accountId,
|
|
2728
|
+
channelId: message.channel,
|
|
2729
|
+
...failureNoticeThreadTs ? { threadTs: failureNoticeThreadTs } : {},
|
|
2730
|
+
...failureNoticeTeamId ? { teamId: failureNoticeTeamId } : {}
|
|
2731
|
+
});
|
|
2378
2732
|
}
|
|
2379
2733
|
} catch (err) {
|
|
2380
2734
|
dispatchError = err;
|
|
2381
2735
|
} finally {
|
|
2382
2736
|
progress.progressDraft.cancel();
|
|
2383
|
-
await draftStream?.discardPending();
|
|
2737
|
+
if (!progress.useDraftProgressCard) await draftStream?.discardPending();
|
|
2384
2738
|
}
|
|
2385
2739
|
let streamFallbackDelivered = false;
|
|
2386
2740
|
const finalStream = delivery.streamSession;
|
|
2387
2741
|
if (finalStream && !finalStream.stopped) try {
|
|
2388
|
-
const completionChunks = progress.useNativeProgressStreaming && !progress.nativeProgressCompletionSent ? progress.buildNativeProgressCompletionChunks(dispatchError ? "error" : progress.nativeProgressTerminalStatus) : void 0;
|
|
2742
|
+
const completionChunks = progress.useNativeProgressStreaming && !progress.nativeProgressCompletionSent ? progress.buildNativeProgressCompletionChunks(dispatchError || agentRunFailed ? "error" : progress.nativeProgressTerminalStatus) : void 0;
|
|
2389
2743
|
if (completionChunks?.length) progress.nativeProgressCompletionSent = true;
|
|
2390
2744
|
const stopResult = await stopSlackStream({
|
|
2391
2745
|
session: finalStream,
|
|
@@ -2393,12 +2747,6 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2393
2747
|
...slackMessageMetadata ? { metadata: slackMessageMetadata } : {}
|
|
2394
2748
|
});
|
|
2395
2749
|
delivery.acknowledgeStoppedStreamedDeliveries(finalStream, stopResult?.messageId);
|
|
2396
|
-
if (progress.pendingNativeProgressReceipt && stopResult?.messageId && !progress.progressReceiptCollapsed) await progress.collapseProgressReceipt({
|
|
2397
|
-
channelId: finalStream.channel,
|
|
2398
|
-
messageId: stopResult.messageId,
|
|
2399
|
-
text: progress.pendingNativeProgressReceipt,
|
|
2400
|
-
threadTs: finalStream.threadTs
|
|
2401
|
-
});
|
|
2402
2750
|
} catch (err) {
|
|
2403
2751
|
if (err instanceof SlackStreamNotDeliveredError) {
|
|
2404
2752
|
streamFallbackDelivered = await delivery.deliverPendingStreamFallback(finalStream, err);
|
|
@@ -2420,19 +2768,25 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2420
2768
|
observedReplyDelivery: delivery.observedReplyDelivery,
|
|
2421
2769
|
fallbackDelivered: streamFallbackDelivered
|
|
2422
2770
|
});
|
|
2423
|
-
if (
|
|
2424
|
-
|
|
2771
|
+
if (pendingFailureNotice && anyReplyDelivered) recordSlackThreadFailureNotice(pendingFailureNotice);
|
|
2772
|
+
if (dispatchError || agentRunFailed) await progress.finalizeDraftProgressCard("error");
|
|
2773
|
+
await progress.dropDetachedProgressCards();
|
|
2774
|
+
if (statusReactionsEnabled) if (dispatchError || agentRunFailed) {
|
|
2775
|
+
await statusReactions.setError();
|
|
2776
|
+
statusReactions.restoreInitial();
|
|
2777
|
+
} else if (anyReplyDelivered) {
|
|
2425
2778
|
await statusReactions.setDone();
|
|
2426
2779
|
statusReactions.restoreInitial();
|
|
2427
2780
|
} else await statusReactions.restoreInitial();
|
|
2428
2781
|
const participationThreadTs = delivery.usedReplyThreadTs ?? statusThreadTs;
|
|
2429
2782
|
if (anyReplyDelivered && participationThreadTs) recordSlackThreadParticipation(account.accountId, message.channel, participationThreadTs, {
|
|
2430
2783
|
agentId: route.agentId,
|
|
2431
|
-
|
|
2784
|
+
teamId: prepared.eventScope?.teamId
|
|
2432
2785
|
});
|
|
2433
2786
|
if (dispatchError) throw toErrorObject(dispatchError, "Slack dispatch failed");
|
|
2434
|
-
if (!anyReplyDelivered && !draftPreviewCommitted.value) {
|
|
2787
|
+
if (!anyReplyDelivered && !draftPreviewCommitted.value && !(agentRunFailed && progress.useDraftProgressCard)) {
|
|
2435
2788
|
await draftStream?.clear();
|
|
2789
|
+
await progress.dropDetachedProgressCards();
|
|
2436
2790
|
return;
|
|
2437
2791
|
}
|
|
2438
2792
|
if (shouldLogVerbose()) {
|
|
@@ -2442,19 +2796,13 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2442
2796
|
}
|
|
2443
2797
|
//#endregion
|
|
2444
2798
|
//#region extensions/slack/src/agent-context.ts
|
|
2445
|
-
function asRecord(value) {
|
|
2446
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
2447
|
-
}
|
|
2448
|
-
function trimmedString(value) {
|
|
2449
|
-
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
2450
|
-
}
|
|
2451
2799
|
function normalizeEntity(value) {
|
|
2452
|
-
const entity =
|
|
2453
|
-
const type =
|
|
2800
|
+
const entity = asOptionalRecord(value);
|
|
2801
|
+
const type = normalizeOptionalString(entity?.type);
|
|
2454
2802
|
if (!entity || !type) return;
|
|
2455
|
-
const teamId =
|
|
2803
|
+
const teamId = normalizeOptionalString(entity.team_id);
|
|
2456
2804
|
if (type === "slack#/types/channel_id" || type === "slack#/types/canvas_id" || type === "slack#/types/list_id") {
|
|
2457
|
-
const entityValue =
|
|
2805
|
+
const entityValue = normalizeOptionalString(entity.value);
|
|
2458
2806
|
return entityValue ? {
|
|
2459
2807
|
type,
|
|
2460
2808
|
value: entityValue,
|
|
@@ -2462,9 +2810,9 @@ function normalizeEntity(value) {
|
|
|
2462
2810
|
} : void 0;
|
|
2463
2811
|
}
|
|
2464
2812
|
if (type !== "slack#/types/message_context") return;
|
|
2465
|
-
const message =
|
|
2466
|
-
const channelId =
|
|
2467
|
-
const messageTs =
|
|
2813
|
+
const message = asOptionalRecord(entity.value);
|
|
2814
|
+
const channelId = normalizeOptionalString(message?.channel_id);
|
|
2815
|
+
const messageTs = normalizeOptionalString(message?.message_ts);
|
|
2468
2816
|
return channelId && messageTs ? {
|
|
2469
2817
|
type,
|
|
2470
2818
|
value: {
|
|
@@ -2475,10 +2823,10 @@ function normalizeEntity(value) {
|
|
|
2475
2823
|
} : void 0;
|
|
2476
2824
|
}
|
|
2477
2825
|
function isSlackAppContext(value) {
|
|
2478
|
-
return Boolean(
|
|
2826
|
+
return Boolean(asOptionalRecord(value));
|
|
2479
2827
|
}
|
|
2480
2828
|
function normalizeSlackAppContextEntities(value) {
|
|
2481
|
-
const context =
|
|
2829
|
+
const context = asOptionalRecord(value);
|
|
2482
2830
|
if (!Array.isArray(context?.entities)) return [];
|
|
2483
2831
|
return context.entities.flatMap((entity) => {
|
|
2484
2832
|
const normalized = normalizeEntity(entity);
|
|
@@ -2487,13 +2835,15 @@ function normalizeSlackAppContextEntities(value) {
|
|
|
2487
2835
|
}
|
|
2488
2836
|
//#endregion
|
|
2489
2837
|
//#region extensions/slack/src/monitor/message-handler/preflight-audio.ts
|
|
2490
|
-
const SLACK_DEFAULT_ECHO_TRANSCRIPT_FORMAT = "📝 \"{transcript}\"";
|
|
2491
|
-
const loadSlackPreflightAudioRuntime = createLazyRuntimeModule(() => import("./preflight-audio.runtime-D5Bgags0.js"));
|
|
2492
2838
|
function isSlackAudioFile(file) {
|
|
2493
2839
|
if (file.subtype === "slack_audio") return true;
|
|
2494
2840
|
if ((file.mimetype?.split(";")[0]?.trim().toLowerCase())?.startsWith("audio/")) return true;
|
|
2495
2841
|
return Boolean(mimeTypeFromFilePath(file.name)?.startsWith("audio/"));
|
|
2496
2842
|
}
|
|
2843
|
+
const slackPreflightAudio = createChannelPreflightAudio({
|
|
2844
|
+
channel: "slack",
|
|
2845
|
+
isAudio: isSlackAudioFile
|
|
2846
|
+
});
|
|
2497
2847
|
function findCaptionlessSlackAudioFile(message) {
|
|
2498
2848
|
if (message.text?.trim()) return;
|
|
2499
2849
|
return message.files?.slice(0, 8).find(isSlackAudioFile);
|
|
@@ -2501,74 +2851,30 @@ function findCaptionlessSlackAudioFile(message) {
|
|
|
2501
2851
|
function formatSlackAudioTranscriptForAgent(params) {
|
|
2502
2852
|
return [`[Audio transcript (machine-generated, untrusted)]: ${JSON.stringify(params.transcript)}`, params.rawBody].filter(Boolean).join("\n");
|
|
2503
2853
|
}
|
|
2504
|
-
function suppressSlackPreflightAudioEcho(cfg) {
|
|
2505
|
-
const audio = cfg.tools?.media?.audio;
|
|
2506
|
-
if (!audio?.echoTranscript) return cfg;
|
|
2507
|
-
return {
|
|
2508
|
-
...cfg,
|
|
2509
|
-
tools: {
|
|
2510
|
-
...cfg.tools,
|
|
2511
|
-
media: {
|
|
2512
|
-
...cfg.tools?.media,
|
|
2513
|
-
audio: {
|
|
2514
|
-
...audio,
|
|
2515
|
-
echoTranscript: false
|
|
2516
|
-
}
|
|
2517
|
-
}
|
|
2518
|
-
}
|
|
2519
|
-
};
|
|
2520
|
-
}
|
|
2521
2854
|
async function resolveSlackPreflightAudioTranscript(params) {
|
|
2522
2855
|
const mediaIndex = params.media.findIndex((entry) => entry.contentType?.toLowerCase().startsWith("audio/"));
|
|
2523
2856
|
if (mediaIndex < 0) return null;
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
mediaIndex
|
|
2543
|
-
} : null;
|
|
2544
|
-
} catch (err) {
|
|
2545
|
-
logVerbose(`slack: audio preflight transcription failed: ${String(err)}`);
|
|
2546
|
-
return null;
|
|
2547
|
-
}
|
|
2548
|
-
}
|
|
2549
|
-
function formatSlackAudioTranscriptEcho(transcript, format) {
|
|
2550
|
-
return format.replace("{transcript}", () => transcript);
|
|
2857
|
+
const transcript = await slackPreflightAudio.resolve({ request: {
|
|
2858
|
+
ctx: {
|
|
2859
|
+
media: [...params.media],
|
|
2860
|
+
Provider: "slack",
|
|
2861
|
+
Surface: "slack",
|
|
2862
|
+
OriginatingChannel: "slack",
|
|
2863
|
+
OriginatingTo: params.originatingTo,
|
|
2864
|
+
AccountId: params.accountId,
|
|
2865
|
+
MessageThreadId: params.messageThreadId,
|
|
2866
|
+
ChatType: "channel",
|
|
2867
|
+
SessionKey: params.sessionKey
|
|
2868
|
+
},
|
|
2869
|
+
cfg: params.cfg
|
|
2870
|
+
} });
|
|
2871
|
+
return transcript ? {
|
|
2872
|
+
transcript,
|
|
2873
|
+
mediaIndex
|
|
2874
|
+
} : null;
|
|
2551
2875
|
}
|
|
2552
2876
|
async function sendSlackPreflightAudioTranscriptEcho(params) {
|
|
2553
|
-
|
|
2554
|
-
if (!audio?.echoTranscript) return;
|
|
2555
|
-
const text = formatSlackAudioTranscriptEcho(params.transcript, audio.echoFormat ?? SLACK_DEFAULT_ECHO_TRANSCRIPT_FORMAT);
|
|
2556
|
-
try {
|
|
2557
|
-
const { sendDurableMessageBatch } = await loadSlackPreflightAudioRuntime();
|
|
2558
|
-
const send = await sendDurableMessageBatch({
|
|
2559
|
-
cfg: params.cfg,
|
|
2560
|
-
channel: "slack",
|
|
2561
|
-
to: params.originatingTo,
|
|
2562
|
-
accountId: params.accountId,
|
|
2563
|
-
threadId: params.messageThreadId,
|
|
2564
|
-
payloads: [{ text }],
|
|
2565
|
-
bestEffort: true,
|
|
2566
|
-
durability: "best_effort"
|
|
2567
|
-
});
|
|
2568
|
-
if (send.status === "failed") throw send.error;
|
|
2569
|
-
} catch (err) {
|
|
2570
|
-
logVerbose(`slack: audio transcript echo failed: ${String(err)}`);
|
|
2571
|
-
}
|
|
2877
|
+
await slackPreflightAudio.send(params);
|
|
2572
2878
|
}
|
|
2573
2879
|
async function discardSlackPreflightMedia(media) {
|
|
2574
2880
|
await Promise.allSettled((media ?? []).map((entry) => fs.rm(entry.path, { force: true })));
|
|
@@ -2578,7 +2884,7 @@ async function discardSlackPreflightMedia(media) {
|
|
|
2578
2884
|
const SLACK_MENTION_RESOLUTION_CONCURRENCY = 4;
|
|
2579
2885
|
const SLACK_MENTION_RESOLUTION_MAX_LOOKUPS_PER_MESSAGE = 20;
|
|
2580
2886
|
const SLACK_USER_MENTION_RE$1 = /<@([A-Z0-9]+)(?:\|[^>]+)?>/gi;
|
|
2581
|
-
const loadSlackMediaModule$1 = createLazyRuntimeModule(() => import("./actions-
|
|
2887
|
+
const loadSlackMediaModule$1 = createLazyRuntimeModule(() => import("./actions-BTDXwwE3.js").then((n) => n._));
|
|
2582
2888
|
function collectUniqueSlackMentionIds$1(texts) {
|
|
2583
2889
|
const seen = /* @__PURE__ */ new Set();
|
|
2584
2890
|
const mentionIds = [];
|
|
@@ -2639,7 +2945,14 @@ async function resolveSlackMessageContent(params) {
|
|
|
2639
2945
|
const mergedMedia = [...media ?? [], ...attachmentContent?.media ?? []];
|
|
2640
2946
|
const effectiveDirectMedia = mergedMedia.length > 0 ? mergedMedia : null;
|
|
2641
2947
|
const mediaPlaceholder = effectiveDirectMedia ? effectiveDirectMedia.map((item) => item.placeholder).join(" ") : void 0;
|
|
2642
|
-
const
|
|
2948
|
+
const fallbackFileIds = /* @__PURE__ */ new Set();
|
|
2949
|
+
const fallbackFiles = [...ownFiles ?? [], ...attachmentContent?.files ?? []].filter((file) => {
|
|
2950
|
+
const fileId = normalizeOptionalString(file.id);
|
|
2951
|
+
if (!fileId) return true;
|
|
2952
|
+
if (fallbackFileIds.has(fileId)) return false;
|
|
2953
|
+
fallbackFileIds.add(fileId);
|
|
2954
|
+
return true;
|
|
2955
|
+
});
|
|
2643
2956
|
const fileOnlyFallback = !mediaPlaceholder && fallbackFiles.length > 0 ? fallbackFiles.slice(0, 8).map((file) => formatSlackFileReference(file)).join(", ") : void 0;
|
|
2644
2957
|
const fileOnlyPlaceholder = fileOnlyFallback ? `[Slack file: ${fileOnlyFallback}]` : void 0;
|
|
2645
2958
|
let botAttachmentText;
|
|
@@ -2678,13 +2991,18 @@ async function resolveSlackMessageContent(params) {
|
|
|
2678
2991
|
renderedMentions.set(result.userId, result.rendered);
|
|
2679
2992
|
}
|
|
2680
2993
|
}
|
|
2681
|
-
|
|
2994
|
+
let rawBody = [
|
|
2682
2995
|
renderSlackUserMentions(textParts[0], renderedMentions),
|
|
2683
2996
|
renderSlackUserMentions(textParts[1], renderedMentions),
|
|
2684
2997
|
renderSlackUserMentions(textParts[2], renderedMentions),
|
|
2685
2998
|
mediaPlaceholder,
|
|
2686
2999
|
fileOnlyPlaceholder
|
|
2687
3000
|
].filter(Boolean).join("\n") || "";
|
|
3001
|
+
const unavailableImageCount = attachmentContent?.unavailableImageCount ?? 0;
|
|
3002
|
+
if (unavailableImageCount > 0) rawBody = formatInboundMediaUnavailableText({
|
|
3003
|
+
body: rawBody,
|
|
3004
|
+
notice: `[slack ${unavailableImageCount > 1 ? `${unavailableImageCount} forwarded images` : "forwarded image"} unavailable]`
|
|
3005
|
+
});
|
|
2688
3006
|
if (!rawBody) return null;
|
|
2689
3007
|
return {
|
|
2690
3008
|
rawBody,
|
|
@@ -2783,10 +3101,11 @@ function normalizeSlackRouteBindingPeer(peer) {
|
|
|
2783
3101
|
return;
|
|
2784
3102
|
}
|
|
2785
3103
|
})();
|
|
2786
|
-
if (!target || !slackTargetKindMatchesPeer(peer.kind, target.kind)
|
|
2787
|
-
|
|
3104
|
+
if (!target || !slackTargetKindMatchesPeer(peer.kind, target.kind)) return peer;
|
|
3105
|
+
const normalizedId = target.teamId ? `team:${target.teamId}:${target.kind}:${target.id}` : target.id;
|
|
3106
|
+
return normalizedId === peer.id ? peer : {
|
|
2788
3107
|
...peer,
|
|
2789
|
-
id:
|
|
3108
|
+
id: normalizedId
|
|
2790
3109
|
};
|
|
2791
3110
|
}
|
|
2792
3111
|
function normalizeSlackRouteBindingConfig(cfg) {
|
|
@@ -2821,12 +3140,7 @@ function normalizeSlackRouteBindingConfig(cfg) {
|
|
|
2821
3140
|
return normalizedCfg;
|
|
2822
3141
|
}
|
|
2823
3142
|
function resolveSlackBaseConversationId(params) {
|
|
2824
|
-
|
|
2825
|
-
return params.eventScope ? `team:${encodeURIComponent(params.eventScope.teamId)}:${raw}` : raw;
|
|
2826
|
-
}
|
|
2827
|
-
function qualifySlackPeerId(params) {
|
|
2828
|
-
if (!params.eventScope) return params.id;
|
|
2829
|
-
return `team:${encodeURIComponent(params.eventScope.teamId)}:${params.kind}:${encodeURIComponent(params.id)}`;
|
|
3143
|
+
return qualifySlackConversationId(params.isDirectMessage ? `user:${params.message.user ?? "unknown"}` : params.message.channel, params.eventScope);
|
|
2830
3144
|
}
|
|
2831
3145
|
function resolveSlackInitialAgentRoute(params) {
|
|
2832
3146
|
const route = resolveAgentRoute({
|
|
@@ -2836,7 +3150,7 @@ function resolveSlackInitialAgentRoute(params) {
|
|
|
2836
3150
|
teamId: params.eventScope?.teamId || params.ctx.teamId || void 0,
|
|
2837
3151
|
peer: {
|
|
2838
3152
|
kind: params.isDirectMessage ? "direct" : params.isRoom ? "channel" : "group",
|
|
2839
|
-
id:
|
|
3153
|
+
id: qualifySlackRoutePeerId({
|
|
2840
3154
|
id: params.isDirectMessage ? params.message.user ?? "unknown" : params.message.channel,
|
|
2841
3155
|
kind: params.isDirectMessage ? "user" : "channel",
|
|
2842
3156
|
eventScope: params.eventScope
|
|
@@ -2844,8 +3158,11 @@ function resolveSlackInitialAgentRoute(params) {
|
|
|
2844
3158
|
}
|
|
2845
3159
|
});
|
|
2846
3160
|
if (!params.eventScope || !params.isDirectMessage || route.dmScope !== "main") return route;
|
|
2847
|
-
const
|
|
2848
|
-
|
|
3161
|
+
const sessionKey = resolveSlackEnterpriseMainDmSessionKey({
|
|
3162
|
+
baseSessionKey: route.sessionKey,
|
|
3163
|
+
accountId: params.account.accountId,
|
|
3164
|
+
eventScope: params.eventScope
|
|
3165
|
+
});
|
|
2849
3166
|
return {
|
|
2850
3167
|
...route,
|
|
2851
3168
|
sessionKey,
|
|
@@ -2953,8 +3270,12 @@ function isSlackThreadAuthorCurrentBot(params) {
|
|
|
2953
3270
|
return false;
|
|
2954
3271
|
}
|
|
2955
3272
|
function resolveSlackThreadHistoryFilterPolicy(params) {
|
|
2956
|
-
if (
|
|
2957
|
-
|
|
3273
|
+
if (params.retainCurrentBotHistory) return { currentBot: "all" };
|
|
3274
|
+
if (!params.includeBotStarterAsRootContext || !params.starterTs) return { currentBot: "omit" };
|
|
3275
|
+
return {
|
|
3276
|
+
currentBot: "root-only",
|
|
3277
|
+
rootTs: params.starterTs
|
|
3278
|
+
};
|
|
2958
3279
|
}
|
|
2959
3280
|
function applySlackThreadHistoryFilterPolicy(params) {
|
|
2960
3281
|
const kept = [];
|
|
@@ -2967,7 +3288,7 @@ function applySlackThreadHistoryFilterPolicy(params) {
|
|
|
2967
3288
|
kept.push(entry);
|
|
2968
3289
|
continue;
|
|
2969
3290
|
}
|
|
2970
|
-
if (params.policy.
|
|
3291
|
+
if (params.policy.currentBot === "all" || params.policy.currentBot === "root-only" && entry.ts === params.policy.rootTs) kept.push(entry);
|
|
2971
3292
|
else omittedCurrentBot += 1;
|
|
2972
3293
|
}
|
|
2973
3294
|
return {
|
|
@@ -2996,11 +3317,11 @@ function formatSlackThreadLabelSnippet(text) {
|
|
|
2996
3317
|
}
|
|
2997
3318
|
//#endregion
|
|
2998
3319
|
//#region extensions/slack/src/monitor/message-handler/prepare-thread-context.ts
|
|
2999
|
-
const loadSlackMediaModule = createLazyRuntimeModule(() => import("./actions-
|
|
3320
|
+
const loadSlackMediaModule = createLazyRuntimeModule(() => import("./actions-BTDXwwE3.js").then((n) => n._));
|
|
3000
3321
|
const SLACK_THREAD_CONTEXT_USER_LOOKUP_CONCURRENCY = 4;
|
|
3001
3322
|
function resolveSlackThreadSessionFreshness(params) {
|
|
3002
3323
|
return params.ctx.channelRuntime?.session?.resolveEntryResetFreshness?.({
|
|
3003
|
-
|
|
3324
|
+
agentId: params.agentId,
|
|
3004
3325
|
storePath: params.storePath,
|
|
3005
3326
|
sessionKey: params.sessionKey,
|
|
3006
3327
|
sessionCfg: params.ctx.cfg.session,
|
|
@@ -3066,6 +3387,7 @@ async function resolveSlackThreadContextData(params) {
|
|
|
3066
3387
|
let threadStarterMedia = null;
|
|
3067
3388
|
const threadSessionFreshness = params.isThreadReply && params.threadTs ? resolveSlackThreadSessionFreshness({
|
|
3068
3389
|
ctx: params.ctx,
|
|
3390
|
+
agentId: params.agentId,
|
|
3069
3391
|
storePath: params.storePath,
|
|
3070
3392
|
sessionKey: params.sessionKey
|
|
3071
3393
|
}) : void 0;
|
|
@@ -3073,7 +3395,9 @@ async function resolveSlackThreadContextData(params) {
|
|
|
3073
3395
|
storePath: params.storePath,
|
|
3074
3396
|
sessionKey: params.sessionKey
|
|
3075
3397
|
}) : void 0;
|
|
3076
|
-
const
|
|
3398
|
+
const isMissingThreadSession = threadSessionFreshness ? threadSessionFreshness.state === "missing" : threadSessionPreviousTimestamp === void 0;
|
|
3399
|
+
const isOutboundOnlyThreadSession = threadSessionFreshness !== void 0 && threadSessionFreshness.state !== "missing" && threadSessionFreshness.entry.lastInteractionAt === void 0 && threadSessionFreshness.entry.updatedAt !== 0;
|
|
3400
|
+
const shouldSeedInitialThreadContext = Boolean(params.isThreadReply && params.threadTs && (threadSessionFreshness ? threadSessionFreshness.state !== "fresh" || isOutboundOnlyThreadSession : threadSessionPreviousTimestamp === void 0));
|
|
3077
3401
|
const shouldLoadInitialThreadHistory = shouldSeedInitialThreadContext || params.forceInitialHistory === true;
|
|
3078
3402
|
if (!params.isThreadReply || !params.threadTs) return {
|
|
3079
3403
|
threadStarterBody,
|
|
@@ -3151,7 +3475,8 @@ async function resolveSlackThreadContextData(params) {
|
|
|
3151
3475
|
history: threadHistoryWithBotRoot,
|
|
3152
3476
|
policy: resolveSlackThreadHistoryFilterPolicy({
|
|
3153
3477
|
includeBotStarterAsRootContext,
|
|
3154
|
-
starterTs: currentBotRootTs
|
|
3478
|
+
starterTs: currentBotRootTs,
|
|
3479
|
+
retainCurrentBotHistory: params.isGroupDm && (isMissingThreadSession || isOutboundOnlyThreadSession)
|
|
3155
3480
|
}),
|
|
3156
3481
|
identity: botIdentity
|
|
3157
3482
|
});
|
|
@@ -3298,6 +3623,13 @@ const SLACK_HISTORY_MEDIA_MAX_ATTACHMENTS = 4;
|
|
|
3298
3623
|
const SLACK_HISTORY_MEDIA_MAX_BYTES = 10 * 1024 * 1024;
|
|
3299
3624
|
const SLACK_HISTORY_MEDIA_IDLE_TIMEOUT_MS = 1e3;
|
|
3300
3625
|
const SLACK_HISTORY_MEDIA_TOTAL_TIMEOUT_MS = 3e3;
|
|
3626
|
+
const SLACK_CHANNEL_ACCESS_DOCS_URL = "https://docs.openclaw.ai/channels/slack#access-control-and-routing";
|
|
3627
|
+
function resolveSlackGroupSessionSubject(params) {
|
|
3628
|
+
const channelName = normalizeOptionalString(params.channelName);
|
|
3629
|
+
const workspaceName = normalizeOptionalString(params.workspaceName);
|
|
3630
|
+
if (channelName && workspaceName) return `${workspaceName} #${channelName}`;
|
|
3631
|
+
return `Slack Channel (Workspace ID: ${params.workspaceId}, Channel ID: ${params.channelId})`;
|
|
3632
|
+
}
|
|
3301
3633
|
function recordString(record, key) {
|
|
3302
3634
|
return normalizeOptionalString(record?.[key]);
|
|
3303
3635
|
}
|
|
@@ -3504,6 +3836,8 @@ async function resolveSlackConversationContext(params) {
|
|
|
3504
3836
|
const isRoom = resolvedChannelType === "channel" || resolvedChannelType === "group";
|
|
3505
3837
|
const isRoomish = isRoom || isGroupDm;
|
|
3506
3838
|
const channelConfig = isRoom ? resolveSlackChannelConfig({
|
|
3839
|
+
teamId: params.eventScope?.teamId ?? ctx.teamId,
|
|
3840
|
+
allowUnscoped: ctx.installationIdentity?.kind !== "enterprise",
|
|
3507
3841
|
channelId: message.channel,
|
|
3508
3842
|
channelName,
|
|
3509
3843
|
channels: ctx.channelsConfig,
|
|
@@ -3545,14 +3879,38 @@ async function authorizeSlackInboundMessage(params) {
|
|
|
3545
3879
|
return null;
|
|
3546
3880
|
}
|
|
3547
3881
|
if (!ctx.isChannelAllowed({
|
|
3882
|
+
teamId: params.eventScope?.teamId ?? ctx.teamId,
|
|
3548
3883
|
channelId: message.channel,
|
|
3549
3884
|
channelName,
|
|
3550
3885
|
channelType: resolvedChannelType
|
|
3551
3886
|
})) {
|
|
3887
|
+
if (conversation.isRoom && ctx.groupPolicy === "allowlist" && params.explicitBotMention && !isBotMessage && message.user) {
|
|
3888
|
+
let subject = "This OpenClaw bot";
|
|
3889
|
+
if (ctx.botUserId) try {
|
|
3890
|
+
const botName = normalizeOptionalString((await ctx.resolveUserName(ctx.botUserId, params.eventScope))?.name);
|
|
3891
|
+
if (botName) subject = escapeSlackMrkdwn(botName);
|
|
3892
|
+
} catch (error) {
|
|
3893
|
+
logVerbose(`slack allowlist denial bot-name lookup failed: ${formatSlackError(error)}`);
|
|
3894
|
+
}
|
|
3895
|
+
try {
|
|
3896
|
+
await (params.eventScope?.client ?? ctx.app.client).chat.postEphemeral({
|
|
3897
|
+
token: ctx.botToken,
|
|
3898
|
+
channel: message.channel,
|
|
3899
|
+
user: message.user,
|
|
3900
|
+
text: `${subject} can’t reply here because this channel isn’t in its OpenClaw channel allowlist. Ask the OpenClaw owner to allow this channel. <${SLACK_CHANNEL_ACCESS_DOCS_URL}|Learn how to configure Slack channel access.>`
|
|
3901
|
+
});
|
|
3902
|
+
params.onVisibleDrop?.();
|
|
3903
|
+
} catch (error) {
|
|
3904
|
+
ctx.runtime.error?.(`slack allowlist denial notice failed for channel ${message.channel}: ${formatSlackError(error)}`);
|
|
3905
|
+
}
|
|
3906
|
+
}
|
|
3552
3907
|
logVerbose("slack: drop message (channel not allowed)");
|
|
3553
3908
|
return null;
|
|
3554
3909
|
}
|
|
3555
|
-
const allowFromLower = await resolveSlackEffectiveAllowFrom(ctx, {
|
|
3910
|
+
const allowFromLower = await resolveSlackEffectiveAllowFrom(ctx, {
|
|
3911
|
+
includePairingStore: isDirectMessage,
|
|
3912
|
+
eventScope: params.eventScope
|
|
3913
|
+
});
|
|
3556
3914
|
if (isDirectMessage) {
|
|
3557
3915
|
const directUserId = message.user;
|
|
3558
3916
|
if (!directUserId) {
|
|
@@ -3563,6 +3921,7 @@ async function authorizeSlackInboundMessage(params) {
|
|
|
3563
3921
|
ctx,
|
|
3564
3922
|
accountId: account.accountId,
|
|
3565
3923
|
senderId: directUserId,
|
|
3924
|
+
eventScope: params.eventScope,
|
|
3566
3925
|
allowFromLower,
|
|
3567
3926
|
resolveSenderName: (userId) => ctx.resolveUserName(userId, params.eventScope),
|
|
3568
3927
|
sendPairingReply: async (text) => {
|
|
@@ -3570,7 +3929,8 @@ async function authorizeSlackInboundMessage(params) {
|
|
|
3570
3929
|
cfg: ctx.cfg,
|
|
3571
3930
|
token: ctx.botToken,
|
|
3572
3931
|
client: params.eventScope?.client ?? ctx.app.client,
|
|
3573
|
-
accountId: account.accountId
|
|
3932
|
+
accountId: account.accountId,
|
|
3933
|
+
eventScope: params.eventScope
|
|
3574
3934
|
});
|
|
3575
3935
|
},
|
|
3576
3936
|
onDisabled: () => {
|
|
@@ -3590,10 +3950,10 @@ async function authorizeSlackInboundMessage(params) {
|
|
|
3590
3950
|
async function prepareSlackMessage(params) {
|
|
3591
3951
|
const { ctx, account, message, opts } = params;
|
|
3592
3952
|
const slackClient = opts.eventScope?.client ?? ctx.app.client;
|
|
3593
|
-
const threadStarterWorkspaceScope =
|
|
3953
|
+
const threadStarterWorkspaceScope = {
|
|
3594
3954
|
accountId: account.accountId,
|
|
3595
|
-
teamId: opts.eventScope.teamId
|
|
3596
|
-
}
|
|
3955
|
+
teamId: opts.eventScope?.teamId ?? ctx.teamId
|
|
3956
|
+
};
|
|
3597
3957
|
const cfg = ctx.cfg;
|
|
3598
3958
|
const conversation = await resolveSlackConversationContext({
|
|
3599
3959
|
ctx,
|
|
@@ -3602,16 +3962,20 @@ async function prepareSlackMessage(params) {
|
|
|
3602
3962
|
eventScope: opts.eventScope
|
|
3603
3963
|
});
|
|
3604
3964
|
const { channelInfo, channelName, isDirectMessage, isGroupDm, isRoom, isRoomish, channelConfig, allowBotsMode, isBotMessage } = conversation;
|
|
3965
|
+
const messageText = message.text ?? "";
|
|
3966
|
+
const mentionMetadata = collectSlackMentionMetadata(messageText);
|
|
3967
|
+
const normalizedBotUserId = normalizeSlackId(ctx.botUserId);
|
|
3605
3968
|
const authorization = await authorizeSlackInboundMessage({
|
|
3606
3969
|
ctx,
|
|
3607
3970
|
account,
|
|
3608
3971
|
message,
|
|
3609
3972
|
conversation,
|
|
3610
|
-
|
|
3973
|
+
explicitBotMention: opts.source === "app_mention" || Boolean(normalizedBotUserId && mentionMetadata.mentionedUserIds.includes(normalizedBotUserId)),
|
|
3974
|
+
eventScope: opts.eventScope,
|
|
3975
|
+
onVisibleDrop: opts.onVisibleDrop
|
|
3611
3976
|
});
|
|
3612
3977
|
if (!authorization) return null;
|
|
3613
3978
|
const { senderId, allowFromLower } = authorization;
|
|
3614
|
-
const messageText = message.text ?? "";
|
|
3615
3979
|
let resolvedSenderName = normalizeOptionalString(message.username);
|
|
3616
3980
|
const resolveSenderName = async () => {
|
|
3617
3981
|
if (resolvedSenderName) return resolvedSenderName;
|
|
@@ -3625,7 +3989,6 @@ async function prepareSlackMessage(params) {
|
|
|
3625
3989
|
resolvedSenderName = message.user ?? message.bot_id ?? "unknown";
|
|
3626
3990
|
return resolvedSenderName;
|
|
3627
3991
|
};
|
|
3628
|
-
const mentionMetadata = collectSlackMentionMetadata(messageText);
|
|
3629
3992
|
const { mentionedUserIds, mentionedSubteamIds, hasAnyMention } = mentionMetadata;
|
|
3630
3993
|
const messageAssistantThreadContext = resolveSlackMessageAssistantThreadContext(message);
|
|
3631
3994
|
const assistantContextLookupChannelId = messageAssistantThreadContext?.assistantChannelId ?? message.channel;
|
|
@@ -3688,7 +4051,11 @@ async function prepareSlackMessage(params) {
|
|
|
3688
4051
|
}));
|
|
3689
4052
|
const buildPolicyMentionRegexes = (agentId) => resolveCachedMentionRegexes(ctx, agentId, {
|
|
3690
4053
|
provider: "slack",
|
|
3691
|
-
conversationId:
|
|
4054
|
+
conversationId: formatSlackTarget({
|
|
4055
|
+
teamId: opts.eventScope?.teamId,
|
|
4056
|
+
kind: "channel",
|
|
4057
|
+
id: message.channel
|
|
4058
|
+
}),
|
|
3692
4059
|
providerPolicy: account.config.mentionPatterns
|
|
3693
4060
|
});
|
|
3694
4061
|
let mentionRegexes = buildPolicyMentionRegexes(routing.route.agentId);
|
|
@@ -3729,13 +4096,13 @@ async function prepareSlackMessage(params) {
|
|
|
3729
4096
|
name: void 0
|
|
3730
4097
|
});
|
|
3731
4098
|
let implicitMentionKinds = [];
|
|
3732
|
-
if (!isDirectMessage &&
|
|
3733
|
-
const replyToBotKinds = implicitMentionKindWhen("reply_to_bot", message.parent_user_id === ctx.botUserId);
|
|
4099
|
+
if (!isDirectMessage && message.thread_ts && !wasMentioned) {
|
|
4100
|
+
const replyToBotKinds = implicitMentionKindWhen("reply_to_bot", Boolean(ctx.botUserId && message.parent_user_id === ctx.botUserId));
|
|
3734
4101
|
implicitMentionKinds = replyToBotKinds.length > 0 ? replyToBotKinds : implicitMentionKindWhen("bot_thread_participant", await hasSlackThreadParticipationWithPersistence({
|
|
3735
4102
|
accountId: account.accountId,
|
|
3736
4103
|
channelId: message.channel,
|
|
3737
4104
|
threadTs: message.thread_ts,
|
|
3738
|
-
|
|
4105
|
+
teamId: opts.eventScope?.teamId
|
|
3739
4106
|
}));
|
|
3740
4107
|
}
|
|
3741
4108
|
const recordDroppedHistory = async (reason) => {
|
|
@@ -3843,12 +4210,14 @@ async function prepareSlackMessage(params) {
|
|
|
3843
4210
|
const hasControlCommandInMessage = hasControlCommand(textForCommandDetection, cfg);
|
|
3844
4211
|
const hasAbortRequest = isAbortRequestText(textForCommandDetection);
|
|
3845
4212
|
const channelUsersAllowlistConfigured = isRoom && Array.isArray(channelConfig?.users) && channelConfig.users.length > 0;
|
|
3846
|
-
const
|
|
4213
|
+
const resolveMessageIngress = async (contextBinding, threadId) => await resolveSlackCommandIngress({
|
|
3847
4214
|
ctx,
|
|
4215
|
+
teamId: opts.eventScope?.teamId ?? ctx.teamId,
|
|
3848
4216
|
senderId,
|
|
3849
4217
|
senderName: senderNameForAuth,
|
|
3850
4218
|
channelType: conversation.resolvedChannelType ?? "channel",
|
|
3851
4219
|
channelId: message.channel,
|
|
4220
|
+
threadId,
|
|
3852
4221
|
ownerAllowFromLower: allowFromLower,
|
|
3853
4222
|
channelUsers: isRoom ? channelConfig?.users : void 0,
|
|
3854
4223
|
allowTextCommands,
|
|
@@ -3863,8 +4232,10 @@ async function prepareSlackMessage(params) {
|
|
|
3863
4232
|
requireMention: shouldRequireMention,
|
|
3864
4233
|
allowTextCommands,
|
|
3865
4234
|
implicitMentions
|
|
3866
|
-
}
|
|
4235
|
+
},
|
|
4236
|
+
contextBinding
|
|
3867
4237
|
});
|
|
4238
|
+
let messageIngress = await resolveMessageIngress();
|
|
3868
4239
|
const senderGate = messageIngress.senderAccess.gate;
|
|
3869
4240
|
if (isRoomish && senderGate?.allowed === false) {
|
|
3870
4241
|
logVerbose(`Blocked unauthorized slack sender ${senderId} (not in sender allowlist)`);
|
|
@@ -3885,6 +4256,18 @@ async function prepareSlackMessage(params) {
|
|
|
3885
4256
|
channel: "slack",
|
|
3886
4257
|
accountId: account.accountId
|
|
3887
4258
|
});
|
|
4259
|
+
const preflightChannelTarget = formatSlackTarget({
|
|
4260
|
+
teamId: opts.eventScope?.teamId,
|
|
4261
|
+
kind: "channel",
|
|
4262
|
+
id: message.channel,
|
|
4263
|
+
explicitKind: true
|
|
4264
|
+
});
|
|
4265
|
+
const replyRouteTarget = formatSlackTarget({
|
|
4266
|
+
teamId: opts.eventScope?.teamId,
|
|
4267
|
+
kind: isDirectMessage ? "user" : "channel",
|
|
4268
|
+
id: isDirectMessage ? senderId : message.channel,
|
|
4269
|
+
explicitKind: true
|
|
4270
|
+
});
|
|
3888
4271
|
const commandAuthorized = messageIngress.commandAccess.authorized;
|
|
3889
4272
|
if (isRoomish && messageIngress.commandAccess.shouldBlockControlCommand) {
|
|
3890
4273
|
logInboundDrop({
|
|
@@ -3931,7 +4314,7 @@ async function prepareSlackMessage(params) {
|
|
|
3931
4314
|
media: preflightMedia,
|
|
3932
4315
|
cfg,
|
|
3933
4316
|
accountId: account.accountId,
|
|
3934
|
-
originatingTo:
|
|
4317
|
+
originatingTo: preflightChannelTarget,
|
|
3935
4318
|
sessionKey: preflightRouting.sessionKey,
|
|
3936
4319
|
messageThreadId: preflightRouting.threadContext.messageThreadId
|
|
3937
4320
|
}) : null;
|
|
@@ -4076,14 +4459,22 @@ async function prepareSlackMessage(params) {
|
|
|
4076
4459
|
return false;
|
|
4077
4460
|
}) : statusReactionsWillHandle ? Promise.resolve(true) : null;
|
|
4078
4461
|
const roomLabel = channelName ? `#${channelName}` : `#${message.channel}`;
|
|
4462
|
+
const workspaceId = opts.eventScope?.teamId || ctx.teamId;
|
|
4463
|
+
const workspaceName = ctx.installationIdentity?.kind === "workspace" && ctx.installationIdentity.teamId === workspaceId ? ctx.installationIdentity.teamName : void 0;
|
|
4464
|
+
const groupSessionSubject = isRoomish ? resolveSlackGroupSessionSubject({
|
|
4465
|
+
channelId: message.channel,
|
|
4466
|
+
channelName,
|
|
4467
|
+
workspaceId,
|
|
4468
|
+
workspaceName
|
|
4469
|
+
}) : void 0;
|
|
4079
4470
|
const senderName = await resolveSenderName();
|
|
4080
4471
|
const preview = truncateUtf16Safe(bodyForAgent.replace(/\s+/g, " "), 160);
|
|
4081
4472
|
const inboundLabel = isDirectMessage ? `Slack DM from ${senderName}` : `Slack message in ${roomLabel} from ${senderName}`;
|
|
4082
4473
|
const slackFrom = isDirectMessage ? `slack:${message.user}` : isRoom ? `slack:channel:${message.channel}` : `slack:group:${message.channel}`;
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
});
|
|
4474
|
+
enqueueRoutedSystemEvent(inboundLabel, {
|
|
4475
|
+
...route,
|
|
4476
|
+
sessionKey
|
|
4477
|
+
}, { contextKey: `slack:message:${message.channel}:${message.ts ?? "unknown"}` });
|
|
4087
4478
|
const envelopeFrom = resolveConversationLabel$1({
|
|
4088
4479
|
ChatType: chatType,
|
|
4089
4480
|
SenderName: senderName,
|
|
@@ -4147,7 +4538,6 @@ async function prepareSlackMessage(params) {
|
|
|
4147
4538
|
envelope: envelopeOptions
|
|
4148
4539
|
})
|
|
4149
4540
|
});
|
|
4150
|
-
const slackTo = isDirectMessage ? `user:${message.user}` : `channel:${message.channel}`;
|
|
4151
4541
|
const { channelMetadata, groupSystemPrompt } = resolveSlackRoomContextHints({
|
|
4152
4542
|
isRoomish,
|
|
4153
4543
|
channelInfo,
|
|
@@ -4155,8 +4545,10 @@ async function prepareSlackMessage(params) {
|
|
|
4155
4545
|
});
|
|
4156
4546
|
const { threadStarterBody, threadHistoryBody, shouldSeedInitialThreadContext, threadLabel, threadStarterMedia } = await resolveSlackThreadContextData({
|
|
4157
4547
|
ctx,
|
|
4548
|
+
agentId: route.agentId,
|
|
4158
4549
|
account,
|
|
4159
4550
|
message,
|
|
4551
|
+
isGroupDm,
|
|
4160
4552
|
isThreadReply,
|
|
4161
4553
|
threadTs,
|
|
4162
4554
|
threadStarter,
|
|
@@ -4180,8 +4572,20 @@ async function prepareSlackMessage(params) {
|
|
|
4180
4572
|
const commandBody = textForCommandDetection.trim();
|
|
4181
4573
|
const supplementalThreadHistoryBody = directThreadRoutedToDmSession && !threadHistoryBody ? threadStarterBody : threadHistoryBody;
|
|
4182
4574
|
const effectiveMessageThreadId = assistantThreadContext?.threadTs ?? agentViewThreadTs ?? threadContext.messageThreadId;
|
|
4575
|
+
const boundMessageThreadId = directThreadRoutedToDmSession ? void 0 : effectiveMessageThreadId;
|
|
4576
|
+
messageIngress = await resolveMessageIngress({
|
|
4577
|
+
agentId: route.agentId,
|
|
4578
|
+
sessionKey,
|
|
4579
|
+
messageId: threadContext.messageTs,
|
|
4580
|
+
inboundEventKind
|
|
4581
|
+
}, boundMessageThreadId);
|
|
4582
|
+
if (messageIngress.ingress.admission !== "dispatch") {
|
|
4583
|
+
logVerbose(`Blocked slack sender ${senderId} after final route binding`);
|
|
4584
|
+
return null;
|
|
4585
|
+
}
|
|
4183
4586
|
const agentContextEntities = isAgentViewMessage ? normalizeSlackAppContextEntities(message.app_context) : [];
|
|
4184
|
-
const ctxPayload = buildChannelInboundEventContext({
|
|
4587
|
+
const ctxPayload = (ctx.buildContext ?? buildChannelInboundEventContext)({
|
|
4588
|
+
channelIngress: messageIngress,
|
|
4185
4589
|
channel: "slack",
|
|
4186
4590
|
accountId: route.accountId,
|
|
4187
4591
|
messageId: threadContext.messageTs,
|
|
@@ -4198,20 +4602,21 @@ async function prepareSlackMessage(params) {
|
|
|
4198
4602
|
id: message.channel,
|
|
4199
4603
|
label: envelopeFrom,
|
|
4200
4604
|
spaceId: opts.eventScope?.teamId || ctx.teamId || void 0,
|
|
4201
|
-
threadId:
|
|
4605
|
+
threadId: boundMessageThreadId,
|
|
4202
4606
|
nativeChannelId: message.channel
|
|
4203
4607
|
},
|
|
4204
4608
|
route: {
|
|
4205
4609
|
agentId: route.agentId,
|
|
4206
4610
|
dmScope: route.dmScope,
|
|
4207
4611
|
accountId: route.accountId,
|
|
4208
|
-
routeSessionKey: sessionKey,
|
|
4612
|
+
routeSessionKey: route.sessionKey,
|
|
4613
|
+
dispatchSessionKey: sessionKey,
|
|
4209
4614
|
parentSessionKey: threadKeys.parentSessionKey
|
|
4210
4615
|
},
|
|
4211
4616
|
reply: {
|
|
4212
|
-
to:
|
|
4617
|
+
to: replyRouteTarget,
|
|
4213
4618
|
replyToId: threadContext.replyToId,
|
|
4214
|
-
messageThreadId:
|
|
4619
|
+
messageThreadId: boundMessageThreadId,
|
|
4215
4620
|
nativeChannelId: message.channel
|
|
4216
4621
|
},
|
|
4217
4622
|
message: {
|
|
@@ -4244,7 +4649,7 @@ async function prepareSlackMessage(params) {
|
|
|
4244
4649
|
groupSystemPrompt
|
|
4245
4650
|
},
|
|
4246
4651
|
extra: {
|
|
4247
|
-
GroupSubject:
|
|
4652
|
+
GroupSubject: groupSessionSubject,
|
|
4248
4653
|
ChannelPromptContext: channelMetadata ? [channelMetadata] : void 0,
|
|
4249
4654
|
ChannelStructuredContext: agentContextEntities.length > 0 ? [{
|
|
4250
4655
|
label: "Slack active context",
|
|
@@ -4284,16 +4689,16 @@ async function prepareSlackMessage(params) {
|
|
|
4284
4689
|
});
|
|
4285
4690
|
const pinnedMainDmOwner = isDirectMessage ? resolvePinnedMainDmOwnerFromAllowlist({
|
|
4286
4691
|
dmScope: cfg.session?.dmScope,
|
|
4287
|
-
allowFrom:
|
|
4692
|
+
allowFrom: allowFromLower,
|
|
4288
4693
|
normalizeEntry: normalizeSlackAllowOwnerEntry
|
|
4289
4694
|
}) : null;
|
|
4290
|
-
const replyTarget =
|
|
4695
|
+
const replyTarget = `channel:${message.channel}`;
|
|
4291
4696
|
if (!replyTarget) return null;
|
|
4292
4697
|
if (preflightAudioTranscript) await sendSlackPreflightAudioTranscriptEcho({
|
|
4293
4698
|
transcript: preflightAudioTranscript,
|
|
4294
4699
|
cfg,
|
|
4295
4700
|
accountId: account.accountId,
|
|
4296
|
-
originatingTo:
|
|
4701
|
+
originatingTo: preflightChannelTarget,
|
|
4297
4702
|
messageThreadId: threadContext.messageThreadId
|
|
4298
4703
|
});
|
|
4299
4704
|
if (shouldLogVerbose()) logVerbose(`slack inbound: account=${route.accountId} agent=${route.agentId} channel=${message.channel} message_ts=${message.ts ?? "unknown"} thread_ts=${effectiveMessageThreadId ?? "none"} from=${slackFrom} chat=${chatType} chars=${rawBody.length}`);
|
|
@@ -4306,7 +4711,7 @@ async function prepareSlackMessage(params) {
|
|
|
4306
4711
|
account,
|
|
4307
4712
|
message,
|
|
4308
4713
|
...opts.relayIdentity ? { relayIdentity: opts.relayIdentity } : {},
|
|
4309
|
-
|
|
4714
|
+
eventScope: opts.eventScope,
|
|
4310
4715
|
route,
|
|
4311
4716
|
channelConfig,
|
|
4312
4717
|
replyTarget,
|
|
@@ -4314,13 +4719,13 @@ async function prepareSlackMessage(params) {
|
|
|
4314
4719
|
turn: {
|
|
4315
4720
|
storePath,
|
|
4316
4721
|
record: {
|
|
4317
|
-
updateLastRoute: isDirectMessage ? {
|
|
4722
|
+
updateLastRoute: isDirectMessage || opts.eventScope ? {
|
|
4318
4723
|
sessionKey: updateLastRouteSessionKey,
|
|
4319
4724
|
channel: "slack",
|
|
4320
|
-
to:
|
|
4725
|
+
to: replyRouteTarget,
|
|
4321
4726
|
accountId: route.accountId,
|
|
4322
4727
|
threadId: effectiveMessageThreadId,
|
|
4323
|
-
mainDmOwnerPin: updateLastRouteSessionKey === route.mainSessionKey && pinnedMainDmOwner && message.user ? {
|
|
4728
|
+
mainDmOwnerPin: isDirectMessage && updateLastRouteSessionKey === route.mainSessionKey && pinnedMainDmOwner && message.user ? {
|
|
4324
4729
|
ownerRecipient: pinnedMainDmOwner,
|
|
4325
4730
|
senderRecipient: normalizeLowercaseStringOrEmpty(message.user),
|
|
4326
4731
|
onSkip: ({ ownerRecipient, senderRecipient }) => {
|