@openclaw/slack 2026.7.2-beta.7 → 2026.8.1-beta.3
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-YJvFBbCR.js} +1 -1
- package/dist/account-inspect-api.js +1 -1
- package/dist/{accounts-CHAeNZOX.js → accounts-BlzxuYle.js} +7 -3
- package/dist/{accounts.runtime-BHD4Ma2R.js → accounts.runtime-CbabBL8x.js} +1 -1
- package/dist/{action-runtime-DfuYoMjG.js → action-runtime-D6jg2CQd.js} +193 -125
- package/dist/action-runtime.runtime-JtoOK9l5.js +2 -0
- package/dist/{actions-D3NuJvva.js → actions-DFk0ANVB.js} +116 -235
- package/dist/actions.runtime-CYLsYRai.js +2 -0
- package/dist/api.js +17 -17
- package/dist/{approval-handler.runtime-BA05P0QT.js → approval-handler.runtime-BNvAoKdK.js} +53 -14
- package/dist/{channel-B9fVdW3s.js → channel-CA5w7qDF.js} +209 -110
- package/dist/channel-config-api.js +1 -1
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel-type-DXyos1d2.js → channel-type-CZWv55Wu.js} +9 -12
- package/dist/{config-adapter-CxBQH8fq.js → channel.setup-CpyYsEXH.js} +83 -25
- package/dist/{config-schema-SgZT6nXV.js → config-schema-CN3XZRX5.js} +27 -17
- package/dist/conversation-binding-route-DFVLUdYF.js +115 -0
- package/dist/{directory-config-CnCIAktf.js → directory-config-BGYyuzWh.js} +3 -3
- package/dist/directory-contract-api.js +1 -1
- package/dist/{directory-live-DKunWh3A.js → directory-live-DjUJQLbq.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-DyfB_ZIE.js} +282 -183
- package/dist/index.js +4 -1
- package/dist/{message-tool-api-DkhFpMoy.js → message-tool-api-CqMr5Rmc.js} +14 -3
- package/dist/message-tool-api.js +1 -1
- package/dist/{monitor-CmKz0wtI.js → monitor-Chrecrb9.js} +3 -3
- package/dist/{outbound-adapter-BkuMp2Vs.js → outbound-adapter-DuRqAQXK.js} +35 -23
- package/dist/{pipeline.runtime-DgtmuYCP.js → pipeline.runtime-Bbfd5VFW.js} +1386 -929
- package/dist/{policy-PRysPn23.js → policy-BKXSxrIS.js} +18 -14
- package/dist/{probe-CgDsSops.js → probe-BVA65leh.js} +38 -18
- package/dist/{provider-DmRizMZt.js → provider-CUUiwsg2.js} +3454 -2601
- package/dist/{relay-source-Blxj8xb4.js → relay-source-mhpMMRG-.js} +11 -21
- package/dist/{replies-4s_Vq_z9.js → replies-SnWyXcsL.js} +84 -27
- package/dist/{resolve-channels-yEqmM-3I.js → resolve-channels-BSpqC8jU.js} +39 -12
- package/dist/{resolve-users-BOrt5K-5.js → resolve-users-Dvz9pDe0.js} +28 -5
- package/dist/{runtime-api-QMb-Q5Ko.js → runtime-api-DBi7xc_g.js} +3 -3
- package/dist/runtime-api.js +11 -11
- package/dist/{scopes-BJSrkAp6.js → scopes-BSk4GZvZ.js} +1 -1
- package/dist/security-contract-api.js +1 -1
- package/dist/{send-DsLeZpST.js → send-CtnOxKCc.js} +249 -163
- package/dist/{send.runtime-gUdK6V3h.js → send.runtime-kYctMHSd.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-BPFfp6So.js} +6 -6
- package/dist/{shared-DgIN3Dcr.js → shared-DJBaslN7.js} +45 -45
- package/dist/{block-text-DkHaoGem.js → slack-client-kind-BXyOOICI.js} +17 -2
- package/dist/{slash-dispatch.runtime-zPkyl7dB.js → slash-dispatch.runtime-Cf2FTrnf.js} +2 -2
- 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 +280 -33
- package/package.json +9 -11
- package/skills/slack/SKILL.md +10 -68
- package/dist/account-reply-mode-DrUXogvO.js +0 -14
- 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
- package/node_modules/p-map/index.d.ts +0 -155
- package/node_modules/p-map/index.js +0 -285
- package/node_modules/p-map/license +0 -9
- package/node_modules/p-map/package.json +0 -57
- package/node_modules/p-map/readme.md +0 -190
- package/dist/{security-audit-DR097212.js → security-audit-B_yKr7MW.js} +1 -1
|
@@ -1,41 +1,44 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import "./
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { c as
|
|
10
|
-
import {
|
|
11
|
-
import { n as
|
|
12
|
-
import { a as resolveDeliveredSlackReplyThreadTs, i as readSlackReplyBlocks, n as deliverReplies, o as resolveSlackThreadTs, s as emitSlackMessageSentHooks, t as createSlackReplyDeliveryPlan } from "./replies-4s_Vq_z9.js";
|
|
1
|
+
import { n as resolveSlackConversationBindingRoute, r as resolveSlackReplyToMode, t as normalizeSlackRouteBindingConfig } from "./conversation-binding-route-DFVLUdYF.js";
|
|
2
|
+
import { n as formatSlackTarget } from "./target-parsing-BnMD2ZqZ.js";
|
|
3
|
+
import { B as SLACK_EDIT_TEXT_MAX_BYTES, F as normalizeSlackOutboundText, I as buildSlackBlocksFallbackText, J as truncateSlackText, O as hasSlackNativeDataBlock, S as resolveSlackReplyRenderPlan, U as SLACK_TEXT_LIMIT, Y as truncateSlackTextByUtf8Bytes, a as qualifySlackConversationId, ct as escapeSlackMrkdwn, d as normalizeSlackAllowOwnerEntry, et as SLACK_SESSION_LINK_ACTION_ID, o as qualifySlackRoutePeerId, p as resolveSlackAllowListMatch, q as countSlackTextUtf8Bytes, s as resolveSlackEnterpriseMainDmSessionKey, u as normalizeAllowListLower } from "./group-policy-DyfB_ZIE.js";
|
|
4
|
+
import { r as formatSlackError } from "./probe-BVA65leh.js";
|
|
5
|
+
import { n as resolveSlackStreamingMode, t as resolveSlackNativeStreaming } from "./streaming-compat-B-e0mqM0.js";
|
|
6
|
+
import { b as formatSlackFileReferenceList, i as editSlackRenderedMessage, p as removeSlackReaction, r as editSlackMessage, t as deleteSlackMessage, u as reactSlackMessage, v as logVerbose$1, x as buildSlackEditTextPayload, y as formatSlackFileReference } from "./actions-DFk0ANVB.js";
|
|
7
|
+
import { a as hasSlackTableBlock, c as resolveSlackMessageText$1, o as isSlackUnfurlAttachment, s as resolveSlackBlocksText } from "./slack-client-kind-BXyOOICI.js";
|
|
8
|
+
import { c as hasSlackThreadFailureNotice, d as recordSlackThreadFailureNotice, f as recordSlackThreadParticipation, l as hasSlackThreadParticipation, m as buildSlackNativeDataDeliveryPlan, o as clearSlackThreadFailureNotice, r as sendMessageSlack, u as hasSlackThreadParticipationWithPersistence } from "./send-CtnOxKCc.js";
|
|
9
|
+
import { S as updateLastRoute, _ as parseSlackAssistantThreadMetadata, b as resolveChannelResetConfig, c as resolveConversationLabel$1, d as resolveSlackEffectiveAllowFrom, g as buildSlackAssistantThreadMetadata, h as resolveSlackChatType, l as authorizeSlackBotRoomMessage, m as normalizeSlackChannelType, o as authorizeSlackDirectMessage, r as resolveSlackRoomContextHints, s as trackSlackDraftMessage, u as resolveSlackCommandIngress, v as readSessionUpdatedAt, w as stripSlackMentionsForCommandDetection, x as resolveStorePath, y as resolveChannelContextVisibilityMode } from "./provider-CUUiwsg2.js";
|
|
10
|
+
import { n as resolveSlackChannelConfig } from "./policy-BKXSxrIS.js";
|
|
11
|
+
import { a as resolveDeliveredSlackReplyThreadTs, i as readSlackReplyBlocks, n as deliverReplies, o as resolveSlackThreadTs, s as emitSlackMessageSentHooks, t as createSlackReplyDeliveryPlan } from "./replies-SnWyXcsL.js";
|
|
13
12
|
import { asOptionalRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
14
13
|
import { resolveAgentRoute, resolveInboundLastRouteSessionKey, resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
|
|
15
|
-
import { buildChannelProgressDraftLine, buildChannelProgressDraftLineForEntry, createChannelMessageReplyPipeline, createChannelProgressDraftCompositor,
|
|
14
|
+
import { buildChannelProgressDraftLine, buildChannelProgressDraftLineForEntry, createChannelMessageReplyPipeline, createChannelProgressDraftCompositor, createChannelProgressWorkCounter, createFinalizableDraftStreamControlsForState, defineFinalizableLivePreviewAdapter, deliverWithFinalizableLivePreviewAdapter, formatChannelProgressDraftText, formatPlanChecklistLines, isChannelProgressDraftWorkToolName, resolveAgentOutboundIdentity, resolveChannelMessageSourceReplyDeliveryMode, resolveChannelProgressDraftConfig, resolveChannelProgressDraftMaxLineChars, resolveChannelStreamingBlockEnabled, resolveChannelStreamingPreviewToolProgress, resolveChannelStreamingSuppressDefaultToolProgressMessages } from "openclaw/plugin-sdk/channel-outbound";
|
|
16
15
|
import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
|
17
16
|
import { danger, logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env";
|
|
18
17
|
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
19
18
|
import { createHash } from "node:crypto";
|
|
19
|
+
import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
|
|
20
|
+
import { ensureConfiguredBindingRouteReady } from "openclaw/plugin-sdk/conversation-runtime";
|
|
20
21
|
import fs from "node:fs/promises";
|
|
22
|
+
import { runTasksWithConcurrency } from "openclaw/plugin-sdk/concurrency-runtime";
|
|
21
23
|
import { formatErrorMessage, toErrorObject } from "openclaw/plugin-sdk/error-runtime";
|
|
22
|
-
import {
|
|
23
|
-
import { buildTtsSupplementMediaPayload, getReplyPayloadTtsSupplement, resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
|
|
24
|
+
import { buildTtsSupplementMediaPayload, getReplyPayloadTtsSupplement, isReplyPayloadNonTerminalToolErrorWarning, resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
|
|
24
25
|
import { filterSupplementalContextItems, resolvePinnedMainDmOwnerFromAllowlist, shouldIncludeSupplementalContext } from "openclaw/plugin-sdk/security-runtime";
|
|
25
|
-
import {
|
|
26
|
+
import { mimeTypeFromFilePath } from "openclaw/plugin-sdk/media-mime";
|
|
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 { asDateTimestampMs, asFiniteNumberInRange, parseStrictFiniteNumber, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
|
|
32
|
+
import { getGlobalHookRunner } from "openclaw/plugin-sdk/plugin-runtime";
|
|
33
|
+
import { buildChannelInboundEventContext, buildMentionRegexes, classifyChannelInboundEvent, dispatchChannelInboundTurn, formatInboundEnvelope, formatInboundMediaUnavailableText, hasVisibleInboundReplyDispatch, implicitMentionKindWhen, logInboundDrop, matchesMentionWithExplicit, readAgentRunTerminalOutcome, recordDroppedChannelInboundHistory, resolveEnvelopeFormatOptions, resolveInboundMentionDecision, resolveInboundReplyDispatchCounts, resolveInboundSupplementalSenderAllowed, resolveUnmentionedGroupInboundPolicy, toInboundMediaFactsWithMetadata } from "openclaw/plugin-sdk/channel-inbound";
|
|
34
|
+
import { resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
|
|
35
|
+
import { resolveGatewayPublicOrigin } from "openclaw/plugin-sdk/config-contracts";
|
|
36
|
+
import { buildControlUiSessionPath } from "openclaw/plugin-sdk/session-discussion";
|
|
33
37
|
import { DEFAULT_TIMING, createStatusReactionController, logAckFailure, logTypingFailure, resolveAckReaction, shouldAckReaction } from "openclaw/plugin-sdk/channel-feedback";
|
|
34
38
|
import { hasControlCommand } from "openclaw/plugin-sdk/command-detection";
|
|
35
39
|
import { isAbortRequestText } from "openclaw/plugin-sdk/command-primitives-runtime";
|
|
36
40
|
import { shouldHandleTextCommands } from "openclaw/plugin-sdk/command-surface";
|
|
37
|
-
import {
|
|
38
|
-
import { runTasksWithConcurrency } from "openclaw/plugin-sdk/concurrency-runtime";
|
|
41
|
+
import { createChannelPreflightAudio, formatAudioTranscriptForAgent } from "openclaw/plugin-sdk/media-understanding-runtime";
|
|
39
42
|
//#region extensions/slack/src/streaming.ts
|
|
40
43
|
/**
|
|
41
44
|
* Thrown when Slack definitively rejects a stream flush/finalize while text
|
|
@@ -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;
|
|
@@ -272,11 +267,16 @@ function resolveExplicitSlackProgressTitle(entry) {
|
|
|
272
267
|
const trimmed = label.trim();
|
|
273
268
|
return trimmed && trimmed.toLowerCase() !== "auto" ? trimmed : void 0;
|
|
274
269
|
}
|
|
270
|
+
function resolveSlackProgressStyle(entry) {
|
|
271
|
+
return entry?.streaming?.progress?.style === "compact" ? "compact" : "card";
|
|
272
|
+
}
|
|
275
273
|
function resolveSlackNativeProgressTaskCards(entry) {
|
|
274
|
+
if (resolveSlackProgressStyle(entry) === "compact") return false;
|
|
276
275
|
const streaming = entry?.streaming;
|
|
277
|
-
if (!streaming || typeof streaming !== "object" || Array.isArray(streaming)) return
|
|
276
|
+
if (!streaming || typeof streaming !== "object" || Array.isArray(streaming)) return true;
|
|
278
277
|
const progressConfig = streaming.progress;
|
|
279
|
-
|
|
278
|
+
if (!progressConfig || typeof progressConfig !== "object" || Array.isArray(progressConfig)) return true;
|
|
279
|
+
return progressConfig.nativeTaskCards !== false;
|
|
280
280
|
}
|
|
281
281
|
function resolveSlackStreamingThreadHint(params) {
|
|
282
282
|
return resolveSlackThreadTs({
|
|
@@ -386,21 +386,27 @@ function createSlackDraftStream(params) {
|
|
|
386
386
|
const remove = params.remove ?? deleteSlackMessage;
|
|
387
387
|
let streamMessageId;
|
|
388
388
|
let streamChannelId;
|
|
389
|
+
let untrackConversationBoundary;
|
|
390
|
+
let lastVisibleUpdate;
|
|
389
391
|
let lastSentKey = "";
|
|
390
|
-
|
|
391
|
-
|
|
392
|
+
const detachedMessages = [];
|
|
393
|
+
const finalizedMessageIds = /* @__PURE__ */ new Set();
|
|
394
|
+
const streamState = {
|
|
395
|
+
stopped: false,
|
|
396
|
+
final: false
|
|
397
|
+
};
|
|
392
398
|
const normalizeUpdate = (update) => typeof update === "string" ? { text: update } : update;
|
|
393
|
-
const sendOrEditStreamMessage = async (
|
|
394
|
-
if (stopped) return;
|
|
395
|
-
const
|
|
399
|
+
const sendOrEditStreamMessage = async (pending) => {
|
|
400
|
+
if (streamState.stopped) return;
|
|
401
|
+
const update = normalizeUpdate(pending);
|
|
402
|
+
const trimmed = update.text.trimEnd();
|
|
396
403
|
if (!trimmed) return;
|
|
397
404
|
if (trimmed.length > maxChars) {
|
|
398
|
-
stopped = true;
|
|
405
|
+
streamState.stopped = true;
|
|
399
406
|
params.warn?.(`slack stream preview stopped (text length ${trimmed.length} > ${maxChars})`);
|
|
400
407
|
return;
|
|
401
408
|
}
|
|
402
|
-
const
|
|
403
|
-
const blocks = update.text === text ? update.blocks : void 0;
|
|
409
|
+
const blocks = update.blocks;
|
|
404
410
|
const sentKey = `${trimmed}\n${blocks ? JSON.stringify(blocks) : ""}`;
|
|
405
411
|
if (sentKey === lastSentKey) return;
|
|
406
412
|
lastSentKey = sentKey;
|
|
@@ -413,56 +419,73 @@ function createSlackDraftStream(params) {
|
|
|
413
419
|
...params.eventScope ? { client: params.eventScope.client } : {},
|
|
414
420
|
...blocks ? { blocks } : {}
|
|
415
421
|
});
|
|
422
|
+
lastVisibleUpdate = {
|
|
423
|
+
text: trimmed,
|
|
424
|
+
...blocks ? { blocks } : {}
|
|
425
|
+
};
|
|
416
426
|
return;
|
|
417
427
|
}
|
|
428
|
+
const threadTs = params.resolveThreadTs?.();
|
|
429
|
+
const pendingBoundary = params.conversationChannelId ? trackSlackDraftMessage({
|
|
430
|
+
accountId: params.accountId,
|
|
431
|
+
teamId: params.eventScope?.teamId,
|
|
432
|
+
channelId: params.conversationChannelId,
|
|
433
|
+
threadTs,
|
|
434
|
+
onInterveningMessage: forceNewMessage
|
|
435
|
+
}) : void 0;
|
|
436
|
+
untrackConversationBoundary = pendingBoundary?.stop;
|
|
418
437
|
const sent = await send(params.target, trimmed, {
|
|
419
438
|
cfg: params.cfg,
|
|
420
439
|
token: params.token,
|
|
421
440
|
accountId: params.accountId,
|
|
422
|
-
threadTs
|
|
441
|
+
threadTs,
|
|
423
442
|
identity: params.identity,
|
|
424
|
-
|
|
425
|
-
client: params.eventScope.client,
|
|
426
|
-
enterpriseEventScope: params.eventScope
|
|
427
|
-
} : {},
|
|
443
|
+
eventScope: params.eventScope,
|
|
428
444
|
...params.metadata ? { metadata: params.metadata } : {},
|
|
429
445
|
...blocks ? { blocks } : {}
|
|
430
446
|
});
|
|
431
447
|
streamChannelId = sent.channelId || streamChannelId;
|
|
432
448
|
streamMessageId = sent.messageId || streamMessageId;
|
|
433
449
|
if (!streamChannelId || !streamMessageId) {
|
|
434
|
-
|
|
450
|
+
stopTrackingConversationBoundary();
|
|
451
|
+
streamState.stopped = true;
|
|
435
452
|
params.warn?.("slack stream preview stopped (missing identifiers from sendMessage)");
|
|
436
453
|
return;
|
|
437
454
|
}
|
|
438
|
-
|
|
455
|
+
lastVisibleUpdate = {
|
|
456
|
+
text: trimmed,
|
|
457
|
+
...blocks ? { blocks } : {}
|
|
458
|
+
};
|
|
459
|
+
if (pendingBoundary && params.conversationChannelId === streamChannelId) pendingBoundary.setMessageTs(streamMessageId);
|
|
460
|
+
else {
|
|
461
|
+
stopTrackingConversationBoundary();
|
|
462
|
+
untrackConversationBoundary = trackSlackDraftMessage({
|
|
463
|
+
accountId: params.accountId,
|
|
464
|
+
teamId: params.eventScope?.teamId,
|
|
465
|
+
channelId: streamChannelId,
|
|
466
|
+
threadTs,
|
|
467
|
+
messageTs: streamMessageId,
|
|
468
|
+
onInterveningMessage: forceNewMessage
|
|
469
|
+
}).stop;
|
|
470
|
+
}
|
|
439
471
|
} catch (err) {
|
|
440
|
-
|
|
472
|
+
stopTrackingConversationBoundary();
|
|
473
|
+
streamState.stopped = true;
|
|
441
474
|
params.warn?.(`slack stream preview failed: ${formatSlackError(err)}`);
|
|
442
475
|
}
|
|
443
476
|
};
|
|
444
|
-
const loop =
|
|
477
|
+
const { loop, update, discardPending, seal } = createFinalizableDraftStreamControlsForState({
|
|
445
478
|
throttleMs,
|
|
446
|
-
|
|
447
|
-
sendOrEditStreamMessage
|
|
479
|
+
state: streamState,
|
|
480
|
+
sendOrEditStreamMessage,
|
|
481
|
+
emptyValue: "",
|
|
482
|
+
isEmpty: (value) => !normalizeUpdate(value).text.trim()
|
|
448
483
|
});
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
|
|
484
|
+
const stopTrackingConversationBoundary = () => {
|
|
485
|
+
untrackConversationBoundary?.();
|
|
486
|
+
untrackConversationBoundary = void 0;
|
|
452
487
|
};
|
|
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;
|
|
488
|
+
const removeMessage = async (channelId, messageId) => {
|
|
466
489
|
try {
|
|
467
490
|
await remove(channelId, messageId, {
|
|
468
491
|
token: params.token,
|
|
@@ -473,31 +496,131 @@ function createSlackDraftStream(params) {
|
|
|
473
496
|
params.warn?.(`slack stream preview cleanup failed: ${formatSlackError(err)}`);
|
|
474
497
|
}
|
|
475
498
|
};
|
|
499
|
+
const clear = async () => {
|
|
500
|
+
stopTrackingConversationBoundary();
|
|
501
|
+
await discardPending();
|
|
502
|
+
const channelId = streamChannelId;
|
|
503
|
+
const messageId = streamMessageId;
|
|
504
|
+
streamChannelId = void 0;
|
|
505
|
+
streamMessageId = void 0;
|
|
506
|
+
lastVisibleUpdate = void 0;
|
|
507
|
+
lastSentKey = "";
|
|
508
|
+
if (!channelId || !messageId) return;
|
|
509
|
+
await removeMessage(channelId, messageId);
|
|
510
|
+
};
|
|
476
511
|
const forceNewMessage = () => {
|
|
512
|
+
stopTrackingConversationBoundary();
|
|
513
|
+
streamState.stopped = false;
|
|
514
|
+
streamState.final = false;
|
|
515
|
+
if (streamChannelId && streamMessageId && !finalizedMessageIds.has(streamMessageId)) detachedMessages.push({
|
|
516
|
+
channelId: streamChannelId,
|
|
517
|
+
messageId: streamMessageId
|
|
518
|
+
});
|
|
477
519
|
streamMessageId = void 0;
|
|
478
520
|
streamChannelId = void 0;
|
|
521
|
+
lastVisibleUpdate = void 0;
|
|
479
522
|
lastSentKey = "";
|
|
480
|
-
pendingUpdate = void 0;
|
|
481
523
|
loop.resetPending();
|
|
482
524
|
};
|
|
525
|
+
const dropDetachedMessages = async () => {
|
|
526
|
+
let message;
|
|
527
|
+
while ((message = detachedMessages.shift()) !== void 0) {
|
|
528
|
+
const { channelId, messageId } = message;
|
|
529
|
+
await removeMessage(channelId, messageId);
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
const discardPendingAndStopTracking = async () => {
|
|
533
|
+
stopTrackingConversationBoundary();
|
|
534
|
+
await discardPending();
|
|
535
|
+
};
|
|
536
|
+
const finalizeMessage = async (messageId, editFinal) => {
|
|
537
|
+
const channelId = streamChannelId;
|
|
538
|
+
const previousUpdate = lastVisibleUpdate;
|
|
539
|
+
if (!channelId || streamMessageId !== messageId || !previousUpdate) return false;
|
|
540
|
+
await editFinal();
|
|
541
|
+
if (streamChannelId === channelId && streamMessageId === messageId) {
|
|
542
|
+
finalizedMessageIds.add(messageId);
|
|
543
|
+
stopTrackingConversationBoundary();
|
|
544
|
+
return true;
|
|
545
|
+
}
|
|
546
|
+
try {
|
|
547
|
+
await edit(channelId, messageId, previousUpdate.text, {
|
|
548
|
+
cfg: params.cfg,
|
|
549
|
+
token: params.token,
|
|
550
|
+
accountId: params.accountId,
|
|
551
|
+
...params.eventScope ? { client: params.eventScope.client } : {},
|
|
552
|
+
...previousUpdate.blocks ? { blocks: previousUpdate.blocks } : {}
|
|
553
|
+
});
|
|
554
|
+
} catch (err) {
|
|
555
|
+
params.warn?.(`slack stream preview restore failed: ${formatSlackError(err)}`);
|
|
556
|
+
}
|
|
557
|
+
return false;
|
|
558
|
+
};
|
|
483
559
|
params.log?.(`slack stream preview ready (maxChars=${maxChars}, throttleMs=${throttleMs})`);
|
|
484
560
|
return {
|
|
485
|
-
update
|
|
486
|
-
const normalized = normalizeUpdate(update);
|
|
487
|
-
pendingUpdate = update;
|
|
488
|
-
loop.update(normalized.text);
|
|
489
|
-
},
|
|
561
|
+
update,
|
|
490
562
|
flush: loop.flush,
|
|
491
563
|
clear,
|
|
492
|
-
discardPending,
|
|
493
|
-
seal
|
|
494
|
-
stop,
|
|
564
|
+
discardPending: discardPendingAndStopTracking,
|
|
565
|
+
seal,
|
|
495
566
|
forceNewMessage,
|
|
567
|
+
dropDetachedMessages,
|
|
568
|
+
finalizeMessage,
|
|
496
569
|
messageId: () => streamMessageId,
|
|
497
570
|
channelId: () => streamChannelId
|
|
498
571
|
};
|
|
499
572
|
}
|
|
500
573
|
//#endregion
|
|
574
|
+
//#region extensions/slack/src/stream-mode.ts
|
|
575
|
+
function resolveSlackStreamingConfig(params) {
|
|
576
|
+
return {
|
|
577
|
+
mode: resolveSlackStreamingMode(params),
|
|
578
|
+
nativeStreaming: resolveSlackNativeStreaming(params)
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
function applyAppendOnlyStreamUpdate(params) {
|
|
582
|
+
const incoming = params.incoming.trimEnd();
|
|
583
|
+
if (!incoming) return {
|
|
584
|
+
rendered: params.rendered,
|
|
585
|
+
source: params.source,
|
|
586
|
+
changed: false
|
|
587
|
+
};
|
|
588
|
+
if (!params.rendered) return {
|
|
589
|
+
rendered: incoming,
|
|
590
|
+
source: incoming,
|
|
591
|
+
changed: true
|
|
592
|
+
};
|
|
593
|
+
if (incoming === params.source) return {
|
|
594
|
+
rendered: params.rendered,
|
|
595
|
+
source: params.source,
|
|
596
|
+
changed: false
|
|
597
|
+
};
|
|
598
|
+
if (incoming.startsWith(params.rendered)) return {
|
|
599
|
+
rendered: incoming,
|
|
600
|
+
source: incoming,
|
|
601
|
+
changed: incoming !== params.rendered
|
|
602
|
+
};
|
|
603
|
+
if (incoming.startsWith(params.source)) {
|
|
604
|
+
const delta = incoming.slice(params.source.length);
|
|
605
|
+
return {
|
|
606
|
+
rendered: `${params.rendered}${delta}`,
|
|
607
|
+
source: incoming,
|
|
608
|
+
changed: delta.length > 0
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
if (params.source.startsWith(incoming)) return {
|
|
612
|
+
rendered: params.rendered,
|
|
613
|
+
source: params.source,
|
|
614
|
+
changed: false
|
|
615
|
+
};
|
|
616
|
+
const separator = params.separator ?? (params.rendered.endsWith("\n") ? "" : "\n");
|
|
617
|
+
return {
|
|
618
|
+
rendered: `${params.rendered}${separator}${incoming}`,
|
|
619
|
+
source: incoming,
|
|
620
|
+
changed: true
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
//#endregion
|
|
501
624
|
//#region extensions/slack/src/progress-blocks.ts
|
|
502
625
|
const SLACK_PROGRESS_FIELD_MAX = 1800;
|
|
503
626
|
const DEFAULT_SLACK_PROGRESS_DETAIL_MAX_CHARS = 120;
|
|
@@ -505,6 +628,14 @@ const DEFAULT_SLACK_PROGRESS_TASK_DETAIL_MAX_CHARS = 48;
|
|
|
505
628
|
const SLACK_PROGRESS_CHUNK_TEXT_MAX = 256;
|
|
506
629
|
const SLACK_PROGRESS_TASK_TITLE_MAX = 120;
|
|
507
630
|
const SLACK_PROGRESS_PLAN_FALLBACK_TITLE = "Thinking";
|
|
631
|
+
const SLACK_PROGRESS_LINE_DELTA_RE = /(?:^|\s)\+(\d+)\s+[−-](\d+)(?=\s|$)/u;
|
|
632
|
+
function buildSessionSources(url) {
|
|
633
|
+
return [{
|
|
634
|
+
type: "url_source",
|
|
635
|
+
url,
|
|
636
|
+
text: "Open in OpenClaw"
|
|
637
|
+
}];
|
|
638
|
+
}
|
|
508
639
|
function field(text) {
|
|
509
640
|
return {
|
|
510
641
|
type: "mrkdwn",
|
|
@@ -535,18 +666,31 @@ function lineDetailParts(line) {
|
|
|
535
666
|
function legacyLineTitle(line) {
|
|
536
667
|
return `${line.icon ?? "•"} *${escapeSlackMrkdwn(line.label)}*`;
|
|
537
668
|
}
|
|
669
|
+
function isAuthoredProgressLine(line) {
|
|
670
|
+
return line.id === "reasoning" || line.id?.startsWith("commentary:") === true;
|
|
671
|
+
}
|
|
538
672
|
function legacyLineDetail(line, maxChars) {
|
|
539
673
|
const detail = lineDetailParts(line).join(" · ");
|
|
540
|
-
|
|
674
|
+
if (detail) return escapeSlackMrkdwn(compactDetail(detail, maxChars));
|
|
675
|
+
if (isAuthoredProgressLine(line)) return normalizeSlackOutboundText(compactDetail(line.text.replace(/^(?:🧠|💬)\s+/u, ""), maxChars));
|
|
676
|
+
return "—";
|
|
541
677
|
}
|
|
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();
|
|
678
|
+
function lineTaskTitle(line) {
|
|
679
|
+
const label = (line.kind === "command-output" ? line.toolName : void 0) || line.label.replace(/\s+/g, " ").trim() || line.toolName || line.kind || "Update";
|
|
545
680
|
const fallback = line.text.replace(/\s+/g, " ").trim();
|
|
546
|
-
if (
|
|
547
|
-
if (fallback && fallback !== label) return compactTitle(fallback);
|
|
681
|
+
if (fallback && fallback !== label) return compactTitle(lineDetailParts(line).length > 0 || line.status ? label : fallback);
|
|
548
682
|
return compactTitle(label);
|
|
549
683
|
}
|
|
684
|
+
function lineTaskDetails(line, maxLineChars) {
|
|
685
|
+
const detail = line.detail?.replace(SLACK_PROGRESS_LINE_DELTA_RE, "").replace(/\s+·\s*$/u, "").trim();
|
|
686
|
+
return detail && detail !== line.status?.trim() ? compactDetail(detail, maxLineChars) : void 0;
|
|
687
|
+
}
|
|
688
|
+
function lineTaskOutput(line) {
|
|
689
|
+
const match = line.detail ? SLACK_PROGRESS_LINE_DELTA_RE.exec(line.detail) : null;
|
|
690
|
+
if (match) return `+${match[1]} −${match[2]}`;
|
|
691
|
+
const status = line.status?.replace(/\s+/g, " ").trim();
|
|
692
|
+
return status && lineTaskStatus(line) === "error" ? status : void 0;
|
|
693
|
+
}
|
|
550
694
|
function lineTaskStatus(line) {
|
|
551
695
|
const normalized = line.status?.replace(/\s+/g, " ").trim().toLowerCase();
|
|
552
696
|
if (!normalized) return "in_progress";
|
|
@@ -594,15 +738,20 @@ function buildPlanTasks(params) {
|
|
|
594
738
|
contentIdOccurrences.set(id, occurrence);
|
|
595
739
|
id = `${id}_${occurrence}`;
|
|
596
740
|
}
|
|
597
|
-
|
|
741
|
+
const details = lineTaskDetails(line, maxLineChars);
|
|
742
|
+
const output = lineTaskOutput(line);
|
|
743
|
+
const task = {
|
|
598
744
|
id,
|
|
599
|
-
title: lineTaskTitle(line
|
|
745
|
+
title: lineTaskTitle(line),
|
|
600
746
|
status: lineTaskStatus(line)
|
|
601
747
|
};
|
|
748
|
+
if (details) task.details = details;
|
|
749
|
+
if (output) task.output = output;
|
|
750
|
+
return task;
|
|
602
751
|
});
|
|
603
752
|
}
|
|
604
753
|
function resolvePlanTitle(params) {
|
|
605
|
-
return compactChunkText(params.title?.trim() || params.label?.trim() || params.tasks.at(-1)?.title || SLACK_PROGRESS_PLAN_FALLBACK_TITLE);
|
|
754
|
+
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
755
|
}
|
|
607
756
|
function buildSlackProgressStreamChunks(params) {
|
|
608
757
|
const tasks = buildPlanTasks({
|
|
@@ -612,38 +761,91 @@ function buildSlackProgressStreamChunks(params) {
|
|
|
612
761
|
});
|
|
613
762
|
if (tasks.length === 0) {
|
|
614
763
|
const title = params.title?.trim() || params.label?.trim();
|
|
615
|
-
|
|
764
|
+
if (!title) return;
|
|
765
|
+
if (!params.sessionUrl && !params.diffStat) return [{
|
|
616
766
|
type: "plan_update",
|
|
617
767
|
title: compactChunkText(title)
|
|
618
|
-
}]
|
|
768
|
+
}];
|
|
769
|
+
return [{
|
|
770
|
+
type: "plan_update",
|
|
771
|
+
title: compactChunkText(title)
|
|
772
|
+
}, {
|
|
773
|
+
type: "task_update",
|
|
774
|
+
id: "openclaw_summary",
|
|
775
|
+
title: "Completed",
|
|
776
|
+
status: "complete",
|
|
777
|
+
...formatTaskDiffOutput(params.diffStat) ? { output: formatTaskDiffOutput(params.diffStat) } : {},
|
|
778
|
+
...params.sessionUrl ? { sources: buildSessionSources(params.sessionUrl) } : {}
|
|
779
|
+
}];
|
|
619
780
|
}
|
|
781
|
+
const title = resolvePlanTitle({
|
|
782
|
+
label: params.label,
|
|
783
|
+
title: params.title,
|
|
784
|
+
tasks
|
|
785
|
+
});
|
|
786
|
+
const finalTaskIndex = tasks.length - 1;
|
|
787
|
+
const diffOutput = formatTaskDiffOutput(params.diffStat);
|
|
788
|
+
const taskChunks = tasks.map((task, index) => {
|
|
789
|
+
const chunk = {
|
|
790
|
+
type: "task_update",
|
|
791
|
+
id: task.id,
|
|
792
|
+
title: task.title,
|
|
793
|
+
status: task.status === "in_progress" ? params.finalInProgressStatus ?? (params.completeInProgress ? "complete" : task.status) : task.status
|
|
794
|
+
};
|
|
795
|
+
if (task.details) chunk.details = task.details;
|
|
796
|
+
if (task.output) chunk.output = task.output;
|
|
797
|
+
if (index === finalTaskIndex && diffOutput) chunk.output = [task.output, diffOutput].filter(Boolean).join(" · ");
|
|
798
|
+
if (index === finalTaskIndex && params.sessionUrl) chunk.sources = buildSessionSources(params.sessionUrl);
|
|
799
|
+
return chunk;
|
|
800
|
+
});
|
|
620
801
|
return [{
|
|
621
802
|
type: "plan_update",
|
|
622
|
-
title
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
803
|
+
title
|
|
804
|
+
}, ...taskChunks];
|
|
805
|
+
}
|
|
806
|
+
function formatDiffStat(diffStat) {
|
|
807
|
+
if (!diffStat || diffStat.files === 0 && diffStat.added === 0 && diffStat.removed === 0) return;
|
|
808
|
+
return [
|
|
809
|
+
`📝 ${diffStat.files} files`,
|
|
810
|
+
...diffStat.added > 0 ? [`+${diffStat.added}`] : [],
|
|
811
|
+
...diffStat.removed > 0 ? [`−${diffStat.removed}`] : []
|
|
812
|
+
].join(" ");
|
|
813
|
+
}
|
|
814
|
+
function formatTaskDiffOutput(diffStat) {
|
|
815
|
+
return diffStat && (diffStat.added > 0 || diffStat.removed > 0) ? `+${diffStat.added} −${diffStat.removed}` : void 0;
|
|
816
|
+
}
|
|
817
|
+
function buildActivityText(lines, maxLineChars) {
|
|
818
|
+
const rendered = [];
|
|
819
|
+
let length = 0;
|
|
820
|
+
for (const line of lines.slice(-50).toReversed()) {
|
|
821
|
+
const row = `${legacyLineTitle(line)} — ${legacyLineDetail(line, maxLineChars)}`;
|
|
822
|
+
const nextLength = length + row.length + (rendered.length > 0 ? 1 : 0);
|
|
823
|
+
if (nextLength > SLACK_PROGRESS_FIELD_MAX) break;
|
|
824
|
+
rendered.push(row);
|
|
825
|
+
length = nextLength;
|
|
826
|
+
}
|
|
827
|
+
return rendered.toReversed().join("\n");
|
|
828
|
+
}
|
|
829
|
+
function buildSlackProgressCardBlocks(params) {
|
|
636
830
|
const maxLineChars = resolveMaxLineChars(params.maxLineChars, DEFAULT_SLACK_PROGRESS_DETAIL_MAX_CHARS);
|
|
637
831
|
const planLines = formatPlanChecklistLines(params.plan ?? [], {
|
|
638
832
|
maxLines: 50,
|
|
639
833
|
maxLineChars
|
|
640
834
|
});
|
|
641
835
|
const narration = params.narration?.replace(/\s+/g, " ").trim();
|
|
642
|
-
const
|
|
643
|
-
|
|
836
|
+
const activityText = buildActivityText(params.lines, maxLineChars);
|
|
837
|
+
const diffStat = formatDiffStat(params.diffStat);
|
|
838
|
+
const workingFooter = [
|
|
839
|
+
...params.toolCalls && params.toolCalls > 0 ? [`🛠️ ${params.toolCalls} tools`] : [],
|
|
840
|
+
...diffStat ? [diffStat] : [],
|
|
841
|
+
...params.elapsedSeconds && params.elapsedSeconds > 0 ? [`⏱ ${params.elapsedSeconds}s`] : []
|
|
842
|
+
].join(" · ");
|
|
843
|
+
const footer = params.state === "working" ? workingFooter : diffStat;
|
|
844
|
+
return [
|
|
845
|
+
{
|
|
644
846
|
type: "section",
|
|
645
|
-
text: field(
|
|
646
|
-
}
|
|
847
|
+
text: field(`${params.state === "working" ? "🔄" : params.state === "success" ? "✅" : "❌"} *${escapeSlackMrkdwn(params.title.trim() || "Working")}*`)
|
|
848
|
+
},
|
|
647
849
|
...narration ? [{
|
|
648
850
|
type: "section",
|
|
649
851
|
text: field(`_${escapeSlackMrkdwn(narration)}_`)
|
|
@@ -651,52 +853,131 @@ function buildSlackProgressDraftBlocks(params) {
|
|
|
651
853
|
...planLines.length > 0 ? [{
|
|
652
854
|
type: "section",
|
|
653
855
|
text: field(planLines.map((line) => escapeSlackMrkdwn(line)).join("\n"))
|
|
856
|
+
}] : [],
|
|
857
|
+
...activityText ? [{
|
|
858
|
+
type: "section",
|
|
859
|
+
text: field(activityText)
|
|
860
|
+
}] : [],
|
|
861
|
+
...footer ? [{
|
|
862
|
+
type: "context",
|
|
863
|
+
elements: [field(footer)]
|
|
864
|
+
}] : [],
|
|
865
|
+
...params.state !== "working" && params.sessionUrl ? [{
|
|
866
|
+
type: "actions",
|
|
867
|
+
elements: [{
|
|
868
|
+
type: "button",
|
|
869
|
+
action_id: SLACK_SESSION_LINK_ACTION_ID,
|
|
870
|
+
text: {
|
|
871
|
+
type: "plain_text",
|
|
872
|
+
text: "Open in OpenClaw"
|
|
873
|
+
},
|
|
874
|
+
url: params.sessionUrl
|
|
875
|
+
}]
|
|
654
876
|
}] : []
|
|
655
877
|
].slice(0, 50);
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
878
|
+
}
|
|
879
|
+
const EMPTY_SLACK_NATIVE_STREAM_SNAPSHOT = { tasks: /* @__PURE__ */ new Map() };
|
|
880
|
+
const SLACK_TASK_FIELD_SEPARATOR = " · ";
|
|
881
|
+
function resolveTaskFieldDelta(previous, incoming) {
|
|
882
|
+
if (!incoming) return {
|
|
883
|
+
field: previous,
|
|
884
|
+
delta: void 0
|
|
885
|
+
};
|
|
886
|
+
if (previous?.rendered.includes(incoming)) return {
|
|
887
|
+
field: {
|
|
888
|
+
rendered: previous.rendered,
|
|
889
|
+
source: incoming
|
|
890
|
+
},
|
|
891
|
+
delta: void 0
|
|
892
|
+
};
|
|
893
|
+
const next = applyAppendOnlyStreamUpdate({
|
|
894
|
+
incoming,
|
|
895
|
+
rendered: previous?.rendered ?? "",
|
|
896
|
+
source: previous?.source ?? "",
|
|
897
|
+
separator: SLACK_TASK_FIELD_SEPARATOR
|
|
898
|
+
});
|
|
899
|
+
const delta = next.changed ? next.rendered.slice(previous?.rendered.length ?? 0) : void 0;
|
|
900
|
+
return {
|
|
901
|
+
field: {
|
|
902
|
+
rendered: next.rendered,
|
|
903
|
+
source: next.source
|
|
904
|
+
},
|
|
905
|
+
delta
|
|
906
|
+
};
|
|
662
907
|
}
|
|
663
908
|
/**
|
|
664
|
-
*
|
|
665
|
-
*
|
|
666
|
-
*
|
|
667
|
-
*
|
|
909
|
+
* Turns a full task snapshot into the delta Slack must receive. Native streams
|
|
910
|
+
* key rows by persistent id with no removal chunk: unchanged rows are omitted,
|
|
911
|
+
* changed rows carry only their unsent field text, and rows that dropped out
|
|
912
|
+
* (plan shrinks, tool-line <-> plan source switches) get a final complete
|
|
913
|
+
* update or they linger in_progress forever.
|
|
668
914
|
*/
|
|
669
915
|
function reconcileSlackNativeTaskChunks(params) {
|
|
670
916
|
const nextTasks = /* @__PURE__ */ new Map();
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
917
|
+
let planTitle = params.previous.planTitle;
|
|
918
|
+
const emitted = [];
|
|
919
|
+
for (const chunk of params.chunks ?? []) {
|
|
920
|
+
if (chunk.type === "plan_update") {
|
|
921
|
+
if (chunk.title !== planTitle) {
|
|
922
|
+
planTitle = chunk.title;
|
|
923
|
+
emitted.push(chunk);
|
|
924
|
+
}
|
|
925
|
+
continue;
|
|
926
|
+
}
|
|
927
|
+
if (chunk.type !== "task_update") {
|
|
928
|
+
emitted.push(chunk);
|
|
929
|
+
continue;
|
|
930
|
+
}
|
|
931
|
+
const previousRow = params.previous.tasks.get(chunk.id);
|
|
932
|
+
const status = chunk.status;
|
|
933
|
+
const details = resolveTaskFieldDelta(previousRow?.details, chunk.details);
|
|
934
|
+
const output = resolveTaskFieldDelta(previousRow?.output, chunk.output);
|
|
935
|
+
const sourcesChanged = Boolean(chunk.sources) && !previousRow?.sourcesSent;
|
|
936
|
+
const row = {
|
|
937
|
+
title: chunk.title,
|
|
938
|
+
status
|
|
679
939
|
};
|
|
680
|
-
|
|
681
|
-
|
|
940
|
+
if (details.field) row.details = details.field;
|
|
941
|
+
if (output.field) row.output = output.field;
|
|
942
|
+
if (sourcesChanged || previousRow?.sourcesSent) row.sourcesSent = true;
|
|
943
|
+
nextTasks.set(chunk.id, row);
|
|
944
|
+
if (!(!previousRow || previousRow.title !== chunk.title || previousRow.status !== status || Boolean(details.delta) || Boolean(output.delta) || sourcesChanged)) continue;
|
|
945
|
+
const update = {
|
|
946
|
+
type: "task_update",
|
|
947
|
+
id: chunk.id,
|
|
948
|
+
title: chunk.title,
|
|
949
|
+
status
|
|
950
|
+
};
|
|
951
|
+
if (details.delta) update.details = details.delta;
|
|
952
|
+
if (output.delta) update.output = output.delta;
|
|
953
|
+
if (sourcesChanged) update.sources = chunk.sources;
|
|
954
|
+
emitted.push(update);
|
|
955
|
+
}
|
|
956
|
+
for (const [id, row] of params.previous.tasks) {
|
|
957
|
+
if (nextTasks.has(id)) continue;
|
|
958
|
+
if (row.status === "complete" || row.status === "error") {
|
|
959
|
+
nextTasks.set(id, row);
|
|
960
|
+
continue;
|
|
961
|
+
}
|
|
962
|
+
nextTasks.set(id, {
|
|
963
|
+
...row,
|
|
964
|
+
status: "complete"
|
|
965
|
+
});
|
|
966
|
+
emitted.push({
|
|
682
967
|
type: "task_update",
|
|
683
968
|
id,
|
|
684
|
-
title:
|
|
969
|
+
title: row.title,
|
|
685
970
|
status: "complete"
|
|
686
|
-
};
|
|
687
|
-
}
|
|
688
|
-
for (const [id, task] of params.previousTasks) if (!nextTasks.has(id)) nextTasks.set(id, task);
|
|
971
|
+
});
|
|
972
|
+
}
|
|
689
973
|
return {
|
|
690
|
-
chunks:
|
|
691
|
-
|
|
974
|
+
chunks: emitted.length > 0 ? emitted : void 0,
|
|
975
|
+
snapshot: {
|
|
976
|
+
...planTitle ? { planTitle } : {},
|
|
977
|
+
tasks: nextTasks
|
|
978
|
+
}
|
|
692
979
|
};
|
|
693
980
|
}
|
|
694
|
-
function buildSlackProgressStreamStartChunks(params) {
|
|
695
|
-
return buildSlackProgressStreamChunks(params);
|
|
696
|
-
}
|
|
697
|
-
function buildSlackProgressStreamUpdateChunks(params) {
|
|
698
|
-
return buildSlackProgressStreamChunks(params);
|
|
699
|
-
}
|
|
700
981
|
function buildSlackProgressStreamCompletionChunks(params) {
|
|
701
982
|
return buildSlackProgressStreamChunks({
|
|
702
983
|
...params,
|
|
@@ -704,48 +985,48 @@ function buildSlackProgressStreamCompletionChunks(params) {
|
|
|
704
985
|
});
|
|
705
986
|
}
|
|
706
987
|
//#endregion
|
|
707
|
-
//#region extensions/slack/src/
|
|
708
|
-
function
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
988
|
+
//#region extensions/slack/src/monitor/message-handler/dispatch-progress-render.ts
|
|
989
|
+
function resolveStructuredProgressLines(lines) {
|
|
990
|
+
return lines.map((line) => {
|
|
991
|
+
if (typeof line !== "string") return line;
|
|
992
|
+
const reasoning = line.startsWith("🧠 ");
|
|
993
|
+
const text = line.replace(/^(?:🧠|💬)\s+/u, "").replace(/^_(.*)_$/su, "$1").trim();
|
|
994
|
+
return {
|
|
995
|
+
...reasoning ? { id: "reasoning" } : {},
|
|
996
|
+
kind: "item",
|
|
997
|
+
text,
|
|
998
|
+
label: reasoning ? "Reasoning" : "Update",
|
|
999
|
+
prefix: false
|
|
1000
|
+
};
|
|
1001
|
+
});
|
|
715
1002
|
}
|
|
716
|
-
function
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
};
|
|
743
|
-
const separator = params.rendered.endsWith("\n") ? "" : "\n";
|
|
744
|
-
return {
|
|
745
|
-
rendered: `${params.rendered}${separator}${incoming}`,
|
|
746
|
-
source: incoming,
|
|
747
|
-
changed: true
|
|
748
|
-
};
|
|
1003
|
+
function resolveNativeProgressPlan(snapshot) {
|
|
1004
|
+
return snapshot.plan?.length ? snapshot.plan : void 0;
|
|
1005
|
+
}
|
|
1006
|
+
function resolveNativeProgressLines(snapshot) {
|
|
1007
|
+
const lines = resolveStructuredProgressLines(snapshot.lines).filter((line) => line.id !== "reasoning" && line.id?.startsWith("commentary:") !== true);
|
|
1008
|
+
if (snapshot.plan?.length || !snapshot.planExplanation) return lines;
|
|
1009
|
+
const explanationLine = buildChannelProgressDraftLine({
|
|
1010
|
+
event: "plan",
|
|
1011
|
+
phase: "update",
|
|
1012
|
+
explanation: snapshot.planExplanation
|
|
1013
|
+
});
|
|
1014
|
+
return explanationLine ? [...lines, explanationLine] : lines;
|
|
1015
|
+
}
|
|
1016
|
+
function resolveNativeProgressNarration(snapshot) {
|
|
1017
|
+
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);
|
|
1018
|
+
return paragraphs.length > 0 ? paragraphs.join("\n\n") : void 0;
|
|
1019
|
+
}
|
|
1020
|
+
function combineProgressHeadlineAndExplanation(headline, explanation) {
|
|
1021
|
+
return headline && explanation && headline !== explanation ? `${headline} — ${explanation}` : headline ?? explanation;
|
|
1022
|
+
}
|
|
1023
|
+
function buildNativeProgressChunks(params) {
|
|
1024
|
+
return buildSlackProgressStreamChunks({
|
|
1025
|
+
title: params.title,
|
|
1026
|
+
lines: resolveNativeProgressLines(params.snapshot),
|
|
1027
|
+
plan: resolveNativeProgressPlan(params.snapshot),
|
|
1028
|
+
maxLineChars: params.maxLineChars
|
|
1029
|
+
});
|
|
749
1030
|
}
|
|
750
1031
|
//#endregion
|
|
751
1032
|
//#region extensions/slack/src/monitor/message-handler/preview-finalize.ts
|
|
@@ -795,8 +1076,9 @@ async function readSlackMessageAfterEditError(params) {
|
|
|
795
1076
|
channel: params.channelId,
|
|
796
1077
|
ts: params.threadTs,
|
|
797
1078
|
latest: params.messageId,
|
|
1079
|
+
oldest: params.messageId,
|
|
798
1080
|
inclusive: true,
|
|
799
|
-
limit:
|
|
1081
|
+
limit: 1
|
|
800
1082
|
})).messages ?? []).find((message) => message?.ts === params.messageId) ?? null;
|
|
801
1083
|
const message = (await params.client.conversations.history({
|
|
802
1084
|
token: params.token,
|
|
@@ -826,7 +1108,7 @@ async function didSlackPreviewEditApplyAfterError(params) {
|
|
|
826
1108
|
}
|
|
827
1109
|
async function finalizeSlackPreviewEdit(params) {
|
|
828
1110
|
try {
|
|
829
|
-
await
|
|
1111
|
+
await editSlackRenderedMessage(params.channelId, params.messageId, params.text, {
|
|
830
1112
|
token: params.token,
|
|
831
1113
|
accountId: params.accountId,
|
|
832
1114
|
client: params.client,
|
|
@@ -853,74 +1135,196 @@ async function finalizeSlackPreviewEdit(params) {
|
|
|
853
1135
|
}
|
|
854
1136
|
}
|
|
855
1137
|
//#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
|
-
|
|
1138
|
+
//#region extensions/slack/src/monitor/message-handler/dispatch-progress-card.ts
|
|
1139
|
+
function createSlackDraftProgressCardRuntime(params) {
|
|
1140
|
+
const { account, cfg, ctx, prepared, slackClient } = params.setup;
|
|
1141
|
+
let latestFallbackText = "";
|
|
1142
|
+
let finalStatus;
|
|
1143
|
+
const resolveSessionUrl = () => {
|
|
1144
|
+
if (cfg.gateway?.controlUi?.enabled === false) return;
|
|
1145
|
+
const publicOrigin = resolveGatewayPublicOrigin(cfg);
|
|
1146
|
+
if (!publicOrigin) return;
|
|
1147
|
+
const url = new URL(publicOrigin);
|
|
1148
|
+
const path = buildControlUiSessionPath({
|
|
1149
|
+
namespace: "chat",
|
|
1150
|
+
sessionKey: prepared.route.sessionKey,
|
|
1151
|
+
fallbackAgentId: prepared.route.agentId,
|
|
1152
|
+
basePath: cfg.gateway?.controlUi?.basePath
|
|
865
1153
|
});
|
|
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
|
|
1154
|
+
if (!path) return;
|
|
1155
|
+
url.pathname = path;
|
|
1156
|
+
return url.toString();
|
|
1157
|
+
};
|
|
1158
|
+
const resolveText = (snapshot) => latestFallbackText || formatChannelProgressDraftText({
|
|
1159
|
+
entry: account.config,
|
|
1160
|
+
lines: [...snapshot.lines],
|
|
1161
|
+
seed: params.progressSeed,
|
|
1162
|
+
formatLine: formatSlackProgressDraftLine,
|
|
1163
|
+
narration: snapshot.statusHeadline,
|
|
1164
|
+
plan: snapshot.plan
|
|
898
1165
|
});
|
|
899
|
-
|
|
1166
|
+
const resolvePresentation = (snapshot, state) => {
|
|
1167
|
+
const title = params.explicitTitle ?? snapshot.statusHeadline ?? "Working";
|
|
1168
|
+
return buildSlackProgressCardBlocks({
|
|
1169
|
+
state,
|
|
1170
|
+
title,
|
|
1171
|
+
narration: params.explicitTitle ? combineProgressHeadlineAndExplanation(snapshot.statusHeadline, snapshot.planExplanation) : snapshot.planExplanation && snapshot.planExplanation !== title ? snapshot.planExplanation : void 0,
|
|
1172
|
+
plan: snapshot.plan,
|
|
1173
|
+
lines: resolveStructuredProgressLines(snapshot.lines),
|
|
1174
|
+
maxLineChars: params.maxLineChars,
|
|
1175
|
+
diffStat: snapshot.diffStat,
|
|
1176
|
+
...state === "working" ? {
|
|
1177
|
+
toolCalls: params.progressWorkCounter.toolCalls,
|
|
1178
|
+
elapsedSeconds: params.progressWorkCounter.elapsedSeconds
|
|
1179
|
+
} : { sessionUrl: resolveSessionUrl() }
|
|
1180
|
+
});
|
|
1181
|
+
};
|
|
1182
|
+
const finalize = async (status, snapshot = params.getSnapshot(), fallbackText = resolveText(snapshot)) => {
|
|
1183
|
+
if (!params.draftStream || !params.enabled) return false;
|
|
1184
|
+
await params.draftStream.dropDetachedMessages();
|
|
1185
|
+
const terminalStatus = finalStatus === "error" || status === "error" ? "error" : "success";
|
|
1186
|
+
if (finalStatus === terminalStatus) return true;
|
|
1187
|
+
await params.draftStream.flush();
|
|
1188
|
+
const channelId = params.draftStream.channelId();
|
|
1189
|
+
const messageId = params.draftStream.messageId();
|
|
1190
|
+
if (!channelId || !messageId) return false;
|
|
1191
|
+
await params.draftStream.seal();
|
|
1192
|
+
try {
|
|
1193
|
+
const finalized = await params.draftStream.finalizeMessage(messageId, async () => {
|
|
1194
|
+
await finalizeSlackPreviewEdit({
|
|
1195
|
+
client: slackClient,
|
|
1196
|
+
token: ctx.botToken,
|
|
1197
|
+
accountId: account.accountId,
|
|
1198
|
+
channelId,
|
|
1199
|
+
messageId,
|
|
1200
|
+
text: fallbackText,
|
|
1201
|
+
blocks: resolvePresentation(snapshot, terminalStatus),
|
|
1202
|
+
threadTs: params.getThreadTs()
|
|
1203
|
+
});
|
|
1204
|
+
});
|
|
1205
|
+
if (finalized) finalStatus = terminalStatus;
|
|
1206
|
+
return finalized;
|
|
1207
|
+
} catch (err) {
|
|
1208
|
+
logVerbose(`slack: progress card final edit failed (${formatSlackError(err)})`);
|
|
1209
|
+
return false;
|
|
1210
|
+
}
|
|
1211
|
+
};
|
|
1212
|
+
return {
|
|
1213
|
+
resolveSessionUrl,
|
|
1214
|
+
resolveText,
|
|
1215
|
+
resolvePresentation,
|
|
1216
|
+
finalize,
|
|
1217
|
+
get hasTerminalized() {
|
|
1218
|
+
return finalStatus !== void 0;
|
|
1219
|
+
},
|
|
1220
|
+
setFallbackText(text) {
|
|
1221
|
+
latestFallbackText = text;
|
|
1222
|
+
},
|
|
1223
|
+
reset() {
|
|
1224
|
+
latestFallbackText = "";
|
|
1225
|
+
finalStatus = void 0;
|
|
1226
|
+
}
|
|
1227
|
+
};
|
|
900
1228
|
}
|
|
901
|
-
function
|
|
902
|
-
|
|
1229
|
+
function formatSlackProgressDraftLine(line) {
|
|
1230
|
+
if (/^(?:🧠|💬)\s/u.test(line)) return line;
|
|
1231
|
+
const italicCommentary = /^_(.*)_$/su.exec(line);
|
|
1232
|
+
if (!italicCommentary) return escapeSlackMrkdwn(line);
|
|
1233
|
+
return `_${italicCommentary[1].split(/(`[^`\n]+`)/u).map((segment, index) => {
|
|
1234
|
+
if (index % 2 === 0) return escapeSlackMrkdwn(segment);
|
|
1235
|
+
return `\`${segment.slice(1, -1).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">")}\``;
|
|
1236
|
+
}).join("")}_`;
|
|
903
1237
|
}
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1238
|
+
//#endregion
|
|
1239
|
+
//#region extensions/slack/src/monitor/message-handler/dispatch-progress-native.ts
|
|
1240
|
+
function createSlackNativeProgressTransport(params) {
|
|
1241
|
+
const { ctx, message, replyPlan, slackClient, slackIdentity, slackStreamFallbackTeamId } = params.setup;
|
|
1242
|
+
const { delivery } = params;
|
|
1243
|
+
const markDelivered = (threadTs) => {
|
|
1244
|
+
if (!delivery.streamSession?.delivered) return false;
|
|
1245
|
+
delivery.observedReplyDelivery = true;
|
|
1246
|
+
if (threadTs) {
|
|
1247
|
+
delivery.usedReplyThreadTs ??= threadTs;
|
|
1248
|
+
delivery.rememberDeliveredThreadTs("block", threadTs);
|
|
1249
|
+
}
|
|
1250
|
+
return true;
|
|
1251
|
+
};
|
|
1252
|
+
const waitForStart = async () => {
|
|
1253
|
+
if (delivery.streamSession || !delivery.nativeProgressStreamStartPromise) return true;
|
|
1254
|
+
try {
|
|
1255
|
+
await delivery.nativeProgressStreamStartPromise;
|
|
1256
|
+
} catch {
|
|
1257
|
+
delivery.streamFailed = true;
|
|
1258
|
+
return false;
|
|
1259
|
+
}
|
|
1260
|
+
return !delivery.streamFailed;
|
|
1261
|
+
};
|
|
1262
|
+
const start = async (update) => {
|
|
1263
|
+
const streamThreadTs = replyPlan.nextThreadTs();
|
|
1264
|
+
if (!streamThreadTs) {
|
|
1265
|
+
logVerbose("slack-stream: no reply thread target for native progress stream start, falling back");
|
|
1266
|
+
delivery.streamFailed = true;
|
|
1267
|
+
return false;
|
|
1268
|
+
}
|
|
1269
|
+
delivery.nativeProgressStreamThreadTs = streamThreadTs;
|
|
1270
|
+
const startPromise = (async () => {
|
|
1271
|
+
const session = await startSlackStream({
|
|
1272
|
+
client: slackClient,
|
|
1273
|
+
channel: message.channel,
|
|
1274
|
+
threadTs: streamThreadTs,
|
|
1275
|
+
...update.text ? { text: update.text } : {},
|
|
1276
|
+
...update.chunks?.length ? { chunks: update.chunks } : {},
|
|
1277
|
+
taskDisplayMode: "plan",
|
|
1278
|
+
...slackIdentity ? { identity: slackIdentity } : {},
|
|
1279
|
+
teamId: await resolveSlackStreamRecipientTeamId({
|
|
1280
|
+
client: slackClient,
|
|
1281
|
+
token: ctx.botToken,
|
|
1282
|
+
userId: message.user,
|
|
1283
|
+
fallbackTeamId: slackStreamFallbackTeamId
|
|
1284
|
+
}),
|
|
1285
|
+
userId: message.user
|
|
1286
|
+
});
|
|
1287
|
+
delivery.streamSession = session;
|
|
1288
|
+
return session;
|
|
1289
|
+
})();
|
|
1290
|
+
delivery.nativeProgressStreamStartPromise = startPromise;
|
|
1291
|
+
try {
|
|
1292
|
+
if (!await startPromise) return false;
|
|
1293
|
+
const delivered = markDelivered(streamThreadTs);
|
|
1294
|
+
return update.chunks?.length ? delivered : true;
|
|
1295
|
+
} finally {
|
|
1296
|
+
if (delivery.nativeProgressStreamStartPromise === startPromise) delivery.nativeProgressStreamStartPromise = null;
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1299
|
+
const append = async (update) => {
|
|
1300
|
+
const session = delivery.streamSession;
|
|
1301
|
+
if (!session) return false;
|
|
1302
|
+
await appendSlackStream({
|
|
1303
|
+
session,
|
|
1304
|
+
...update.text ? { text: update.text } : {},
|
|
1305
|
+
...update.chunks?.length ? { chunks: update.chunks } : {}
|
|
1306
|
+
});
|
|
1307
|
+
const delivered = markDelivered(delivery.nativeProgressStreamThreadTs);
|
|
1308
|
+
return update.chunks?.length ? delivered : true;
|
|
1309
|
+
};
|
|
1310
|
+
return {
|
|
1311
|
+
append,
|
|
1312
|
+
start,
|
|
1313
|
+
waitForStart
|
|
910
1314
|
};
|
|
911
|
-
return params.streamStarted ? buildSlackProgressStreamUpdateChunks(input) : buildSlackProgressStreamStartChunks(input);
|
|
912
1315
|
}
|
|
913
1316
|
//#endregion
|
|
914
1317
|
//#region extensions/slack/src/monitor/message-handler/dispatch-progress.ts
|
|
915
1318
|
function createSlackProgressRuntime(runtimeParams) {
|
|
916
1319
|
const { setup, delivery, resetPreviewDeliveryState } = runtimeParams;
|
|
917
|
-
const { account, cfg, ctx, hasSlackCustomIdentity, message, prepared, replyPlan, runtime, slackClient, slackIdentity, slackMessageMetadata, slackStreaming,
|
|
1320
|
+
const { account, cfg, ctx, hasSlackCustomIdentity, message, prepared, replyPlan, runtime, slackClient, slackIdentity, slackMessageMetadata, slackStreaming, shouldUseDraftStream, useStreaming, previewStreamingEnabled } = setup;
|
|
918
1321
|
const draftStream = shouldUseDraftStream ? createSlackDraftStream({
|
|
919
1322
|
target: prepared.replyTarget,
|
|
920
1323
|
cfg,
|
|
921
1324
|
token: ctx.botToken,
|
|
922
1325
|
accountId: account.accountId,
|
|
923
|
-
|
|
1326
|
+
conversationChannelId: message.channel,
|
|
1327
|
+
eventScope: prepared.eventScope,
|
|
924
1328
|
...!hasSlackCustomIdentity && slackIdentity ? { identity: slackIdentity } : {},
|
|
925
1329
|
...slackMessageMetadata ? { metadata: slackMessageMetadata } : {},
|
|
926
1330
|
maxChars: Math.min(ctx.textLimit, SLACK_TEXT_LIMIT),
|
|
@@ -933,40 +1337,61 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
933
1337
|
warn: logVerbose
|
|
934
1338
|
}) : void 0;
|
|
935
1339
|
let hasStreamedMessage = false;
|
|
936
|
-
const
|
|
1340
|
+
const isProgressMode = slackStreaming.mode === "progress";
|
|
937
1341
|
const useNativeProgressStreaming = useStreaming && slackStreaming.mode === "progress";
|
|
938
1342
|
const progressDraftActive = Boolean(draftStream) || useNativeProgressStreaming;
|
|
939
1343
|
const previewToolProgressEnabled = progressDraftActive && resolveChannelStreamingPreviewToolProgress(account.config, true, slackStreaming.mode);
|
|
940
1344
|
let shouldYieldDraftProgress = () => false;
|
|
941
1345
|
const suppressDefaultToolProgressMessages = resolveChannelStreamingSuppressDefaultToolProgressMessages(account.config, {
|
|
942
1346
|
draftStreamActive: Boolean(draftStream) || useNativeProgressStreaming,
|
|
1347
|
+
mode: slackStreaming.mode,
|
|
943
1348
|
previewToolProgressEnabled,
|
|
944
1349
|
previewStreamingEnabled
|
|
945
1350
|
});
|
|
946
1351
|
let previewToolProgressSuppressed = false;
|
|
947
|
-
let
|
|
948
|
-
let nativeTaskState = /* @__PURE__ */ new Map();
|
|
1352
|
+
let nativeStreamSnapshot = EMPTY_SLACK_NATIVE_STREAM_SNAPSHOT;
|
|
949
1353
|
let appendRenderedText = "";
|
|
950
1354
|
let appendSourceText = "";
|
|
951
1355
|
let nativeProgressCompletionSent = false;
|
|
952
1356
|
let nativeProgressTerminalStatus = "complete";
|
|
953
|
-
let
|
|
954
|
-
|
|
955
|
-
let
|
|
956
|
-
|
|
1357
|
+
let nativeNarrationRenderedText = "";
|
|
1358
|
+
let nativeNarrationSourceText = "";
|
|
1359
|
+
let nativeStreamOrder = Promise.resolve();
|
|
1360
|
+
const withNativeStreamOrder = (task) => {
|
|
1361
|
+
const run = nativeStreamOrder.then(task, task);
|
|
1362
|
+
nativeStreamOrder = run.catch(() => void 0);
|
|
1363
|
+
return run;
|
|
1364
|
+
};
|
|
1365
|
+
const progressWorkCounter = createChannelProgressWorkCounter();
|
|
957
1366
|
const progressSeed = `${account.accountId}:${message.channel}`;
|
|
958
|
-
const
|
|
1367
|
+
const slackProgressStyle = resolveSlackProgressStyle(account.config);
|
|
1368
|
+
const useDraftProgressCard = Boolean(draftStream) && isProgressMode && slackProgressStyle === "card";
|
|
959
1369
|
const explicitProgressTitle = resolveExplicitSlackProgressTitle(account.config);
|
|
960
1370
|
const progressDraftMaxLineChars = resolveChannelProgressDraftMaxLineChars(account.config);
|
|
961
|
-
const
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
}
|
|
969
|
-
|
|
1371
|
+
const progressCard = createSlackDraftProgressCardRuntime({
|
|
1372
|
+
setup: {
|
|
1373
|
+
account,
|
|
1374
|
+
cfg,
|
|
1375
|
+
ctx,
|
|
1376
|
+
prepared,
|
|
1377
|
+
slackClient
|
|
1378
|
+
},
|
|
1379
|
+
draftStream,
|
|
1380
|
+
enabled: useDraftProgressCard,
|
|
1381
|
+
progressWorkCounter,
|
|
1382
|
+
progressSeed,
|
|
1383
|
+
explicitTitle: explicitProgressTitle,
|
|
1384
|
+
maxLineChars: progressDraftMaxLineChars,
|
|
1385
|
+
getSnapshot: () => progressDraft.getSnapshot(),
|
|
1386
|
+
getThreadTs: () => delivery.usedReplyThreadTs
|
|
1387
|
+
});
|
|
1388
|
+
const nativeTransport = createSlackNativeProgressTransport({
|
|
1389
|
+
setup,
|
|
1390
|
+
delivery
|
|
1391
|
+
});
|
|
1392
|
+
const dropDetachedProgressCards = async () => {
|
|
1393
|
+
if (!useDraftProgressCard) return;
|
|
1394
|
+
await draftStream?.dropDetachedMessages();
|
|
970
1395
|
};
|
|
971
1396
|
const appendNativeProgressCompletion = async (isError) => {
|
|
972
1397
|
const session = delivery.streamSession;
|
|
@@ -986,150 +1411,151 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
986
1411
|
runtime.error?.(danger(`slack-stream: native progress completion failed: ${formatSlackError(err)}`));
|
|
987
1412
|
}
|
|
988
1413
|
};
|
|
1414
|
+
const resolveNativeProgressTitle = (snapshot) => combineProgressHeadlineAndExplanation(explicitProgressTitle ?? snapshot.statusHeadline, snapshot.planExplanation);
|
|
989
1415
|
const buildNativeProgressChunks$1 = (snapshot) => buildNativeProgressChunks({
|
|
990
1416
|
snapshot,
|
|
991
|
-
|
|
992
|
-
title: combineProgressHeadlineAndExplanation(explicitProgressTitle ?? snapshot.statusHeadline, snapshot.planExplanation),
|
|
1417
|
+
title: resolveNativeProgressTitle(snapshot),
|
|
993
1418
|
maxLineChars: progressDraftMaxLineChars
|
|
994
1419
|
});
|
|
995
|
-
const
|
|
996
|
-
const
|
|
997
|
-
|
|
998
|
-
if (
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
}
|
|
1420
|
+
const normalizeProgressText = (text) => text?.replace(/\s+/gu, " ").trim() ?? "";
|
|
1421
|
+
const isRenderedAsProgressTitle = (text) => {
|
|
1422
|
+
const candidate = normalizeProgressText(text);
|
|
1423
|
+
if (!candidate) return false;
|
|
1424
|
+
const title = normalizeProgressText(resolveNativeProgressTitle(progressDraft.getSnapshot()));
|
|
1425
|
+
return title.length > 0 && title.includes(candidate);
|
|
1002
1426
|
};
|
|
1003
|
-
const
|
|
1004
|
-
const
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
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();
|
|
1040
|
-
};
|
|
1041
|
-
const appendNativeProgressStream = async (chunks, chunkKey) => {
|
|
1042
|
-
if (!delivery.streamSession) return;
|
|
1043
|
-
await appendSlackStream({
|
|
1044
|
-
session: delivery.streamSession,
|
|
1045
|
-
chunks
|
|
1427
|
+
const resolveNarrationUpdate = (incoming) => {
|
|
1428
|
+
const next = applyAppendOnlyStreamUpdate({
|
|
1429
|
+
incoming: incoming ?? "",
|
|
1430
|
+
rendered: nativeNarrationRenderedText,
|
|
1431
|
+
source: nativeNarrationSourceText
|
|
1046
1432
|
});
|
|
1047
|
-
|
|
1048
|
-
|
|
1433
|
+
return {
|
|
1434
|
+
next,
|
|
1435
|
+
delta: next.changed ? next.rendered.slice(nativeNarrationRenderedText.length) : ""
|
|
1436
|
+
};
|
|
1049
1437
|
};
|
|
1050
|
-
const updateNativeProgressStream =
|
|
1438
|
+
const updateNativeProgressStream = () => withNativeStreamOrder(updateNativeProgressStreamNow);
|
|
1439
|
+
const updateNativeProgressStreamNow = async () => {
|
|
1051
1440
|
const snapshot = progressDraft.getSnapshot();
|
|
1052
1441
|
const progressLines = resolveNativeProgressLines(snapshot);
|
|
1053
|
-
const
|
|
1054
|
-
|
|
1055
|
-
if (!
|
|
1442
|
+
const narrationUpdate = resolveNarrationUpdate(resolveNativeProgressNarration(snapshot));
|
|
1443
|
+
const hasRetirableNativeTasks = [...nativeStreamSnapshot.tasks.values()].some((task) => task.status !== "complete" && task.status !== "error");
|
|
1444
|
+
if (!useNativeProgressStreaming || delivery.streamFailed || progressLines.length === 0 && !snapshot.plan?.length && !snapshot.statusHeadline && !explicitProgressTitle && !hasRetirableNativeTasks && !narrationUpdate.delta) return false;
|
|
1445
|
+
if (!await nativeTransport.waitForStart()) return false;
|
|
1056
1446
|
const reconciled = reconcileSlackNativeTaskChunks({
|
|
1057
|
-
|
|
1447
|
+
previous: nativeStreamSnapshot,
|
|
1058
1448
|
chunks: buildNativeProgressChunks$1(snapshot)
|
|
1059
1449
|
});
|
|
1060
1450
|
const chunks = reconciled.chunks;
|
|
1061
|
-
if (!chunks?.length) return;
|
|
1062
|
-
const chunkKey = JSON.stringify(chunks);
|
|
1063
|
-
if (chunkKey === nativeProgressChunkKey) return;
|
|
1451
|
+
if (!chunks?.length && !narrationUpdate.delta) return false;
|
|
1064
1452
|
try {
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1453
|
+
const hadSession = Boolean(delivery.streamSession);
|
|
1454
|
+
const streamUpdate = {
|
|
1455
|
+
...narrationUpdate.delta ? { text: narrationUpdate.delta } : {},
|
|
1456
|
+
...chunks?.length ? { chunks } : {}
|
|
1457
|
+
};
|
|
1458
|
+
if (!(hadSession ? await nativeTransport.append(streamUpdate) : await nativeTransport.start(streamUpdate))) return false;
|
|
1459
|
+
if (!hadSession) replyPlan.markSent();
|
|
1460
|
+
if (narrationUpdate.next.changed) {
|
|
1461
|
+
nativeNarrationRenderedText = narrationUpdate.next.rendered;
|
|
1462
|
+
nativeNarrationSourceText = narrationUpdate.next.source;
|
|
1463
|
+
}
|
|
1464
|
+
if (chunks?.length) nativeStreamSnapshot = reconciled.snapshot;
|
|
1465
|
+
return true;
|
|
1068
1466
|
} catch (err) {
|
|
1069
1467
|
runtime.error?.(danger(`slack-stream: native progress stream failed: ${formatSlackError(err)}, falling back`));
|
|
1070
1468
|
delivery.streamFailed = true;
|
|
1469
|
+
return false;
|
|
1071
1470
|
}
|
|
1072
1471
|
};
|
|
1073
|
-
const
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1472
|
+
const appendNativeNarration = (payload, kind) => withNativeStreamOrder(() => appendNativeNarrationNow(payload, kind));
|
|
1473
|
+
const appendNativeNarrationNow = async (payload, kind) => {
|
|
1474
|
+
if (isRenderedAsProgressTitle(payload.text)) return false;
|
|
1475
|
+
const narrationUpdate = resolveNarrationUpdate(payload.text?.trimEnd());
|
|
1476
|
+
if (!narrationUpdate.delta) return false;
|
|
1477
|
+
await delivery.deliverWithStreaming({
|
|
1478
|
+
payload,
|
|
1479
|
+
kind,
|
|
1480
|
+
streamText: narrationUpdate.delta,
|
|
1481
|
+
appendSeparator: false,
|
|
1482
|
+
taskDisplayMode: "plan"
|
|
1082
1483
|
});
|
|
1484
|
+
if (!delivery.streamFailed) {
|
|
1485
|
+
nativeNarrationRenderedText = narrationUpdate.next.rendered;
|
|
1486
|
+
nativeNarrationSourceText = narrationUpdate.next.source;
|
|
1487
|
+
}
|
|
1488
|
+
return true;
|
|
1489
|
+
};
|
|
1490
|
+
const resetProgressTurnState = () => {
|
|
1491
|
+
progressWorkCounter.reset();
|
|
1492
|
+
nativeNarrationRenderedText = "";
|
|
1493
|
+
nativeNarrationSourceText = "";
|
|
1083
1494
|
};
|
|
1084
1495
|
const progressDraft = createChannelProgressDraftCompositor({
|
|
1085
1496
|
entry: account.config,
|
|
1086
1497
|
mode: slackStreaming.mode,
|
|
1087
|
-
active: progressDraftActive
|
|
1498
|
+
active: progressDraftActive,
|
|
1088
1499
|
seed: progressSeed,
|
|
1089
|
-
formatLine:
|
|
1500
|
+
formatLine: formatSlackProgressDraftLine,
|
|
1090
1501
|
reasoningLinePrefix: "🧠 ",
|
|
1091
|
-
commentaryLinePrefix: "
|
|
1502
|
+
commentaryLinePrefix: "",
|
|
1092
1503
|
reasoningGate: previewToolProgressEnabled,
|
|
1093
|
-
commentaryItalics:
|
|
1094
|
-
|
|
1504
|
+
commentaryItalics: true,
|
|
1505
|
+
buildProgressEventLine: (input, options) => input.event === "tool" || input.event === "item" || input.event === "command-output" ? buildChannelProgressDraftLineForEntry(account.config, input, options) : buildChannelProgressDraftLine(input, options),
|
|
1506
|
+
updateOnLineChange: useNativeProgressStreaming || useDraftProgressCard,
|
|
1095
1507
|
update: async (previewText, options) => {
|
|
1096
|
-
if (useNativeProgressStreaming)
|
|
1097
|
-
|
|
1098
|
-
return;
|
|
1099
|
-
}
|
|
1100
|
-
if (!draftStream) return;
|
|
1508
|
+
if (useNativeProgressStreaming) return await updateNativeProgressStream();
|
|
1509
|
+
if (!draftStream) return false;
|
|
1101
1510
|
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 ? {
|
|
1511
|
+
progressCard.setFallbackText(previewText);
|
|
1512
|
+
draftStream.update(useDraftProgressCard ? {
|
|
1112
1513
|
text: previewText,
|
|
1113
|
-
blocks:
|
|
1514
|
+
blocks: progressCard.resolvePresentation(snapshot, "working")
|
|
1114
1515
|
} : previewText);
|
|
1115
1516
|
hasStreamedMessage = true;
|
|
1116
1517
|
if (options?.flush) await draftStream.flush();
|
|
1518
|
+
return Boolean(draftStream.messageId() && draftStream.channelId());
|
|
1117
1519
|
}
|
|
1118
1520
|
});
|
|
1119
1521
|
const commentaryProgressEnabled = progressDraft.commentaryProgressEnabled;
|
|
1522
|
+
const deliverNativeFinal = (payload, kind) => withNativeStreamOrder(() => deliverNativeFinalNow(payload, kind));
|
|
1523
|
+
const deliverNativeFinalNow = async (payload, kind) => {
|
|
1524
|
+
progressDraft.markFinalReplyStarted();
|
|
1525
|
+
const streamReady = await nativeTransport.waitForStart();
|
|
1526
|
+
const finalThreadTs = delivery.streamSession?.threadTs ?? delivery.nativeProgressStreamThreadTs;
|
|
1527
|
+
if (payload.isError !== true && streamReady && Boolean(delivery.streamSession) && delivery.isStreamingEligible(payload, { maxTextBytes: 4e3 })) {
|
|
1528
|
+
await appendNativeProgressCompletion(false);
|
|
1529
|
+
await delivery.deliverWithStreaming({
|
|
1530
|
+
payload,
|
|
1531
|
+
kind
|
|
1532
|
+
});
|
|
1533
|
+
} else {
|
|
1534
|
+
await delivery.deliverNormally({
|
|
1535
|
+
payload,
|
|
1536
|
+
kind,
|
|
1537
|
+
forcedThreadTs: finalThreadTs
|
|
1538
|
+
});
|
|
1539
|
+
await appendNativeProgressCompletion(payload.isError === true);
|
|
1540
|
+
}
|
|
1541
|
+
progressDraft.markFinalReplyDelivered();
|
|
1542
|
+
};
|
|
1120
1543
|
const buildNativeProgressCompletionChunks = (finalInProgressStatus) => {
|
|
1121
1544
|
const snapshot = progressDraft.getSnapshot();
|
|
1122
1545
|
const lines = resolveNativeProgressLines(snapshot);
|
|
1123
|
-
const
|
|
1124
|
-
|
|
1546
|
+
const sessionUrl = progressCard.resolveSessionUrl();
|
|
1547
|
+
const hasRetirableNativeTasks = [...nativeStreamSnapshot.tasks.values()].some((task) => task.status !== "complete" && task.status !== "error");
|
|
1548
|
+
if (lines.length === 0 && !snapshot.plan?.length && !hasRetirableNativeTasks && !snapshot.diffStat && !sessionUrl) return;
|
|
1125
1549
|
return reconcileSlackNativeTaskChunks({
|
|
1126
|
-
|
|
1550
|
+
previous: nativeStreamSnapshot,
|
|
1127
1551
|
chunks: buildSlackProgressStreamCompletionChunks({
|
|
1128
|
-
title: resolveNativeProgressTitle(snapshot),
|
|
1552
|
+
title: resolveNativeProgressTitle(snapshot) ?? (lines.length === 0 && !snapshot.plan?.length ? "Working" : void 0),
|
|
1129
1553
|
lines,
|
|
1130
1554
|
plan: resolveNativeProgressPlan(snapshot),
|
|
1131
1555
|
maxLineChars: progressDraftMaxLineChars,
|
|
1132
|
-
finalInProgressStatus
|
|
1556
|
+
finalInProgressStatus,
|
|
1557
|
+
diffStat: snapshot.diffStat,
|
|
1558
|
+
sessionUrl
|
|
1133
1559
|
})
|
|
1134
1560
|
}).chunks;
|
|
1135
1561
|
};
|
|
@@ -1144,12 +1570,6 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1144
1570
|
...slackMessageMetadata ? { metadata: slackMessageMetadata } : {}
|
|
1145
1571
|
});
|
|
1146
1572
|
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
1573
|
} catch (err) {
|
|
1154
1574
|
const error = formatSlackError(err);
|
|
1155
1575
|
delivery.emitAcknowledgedStreamedDeliveries();
|
|
@@ -1162,16 +1582,16 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1162
1582
|
delivery.streamFailed = false;
|
|
1163
1583
|
};
|
|
1164
1584
|
const pushPlanProgress = async (steps, explanation) => {
|
|
1165
|
-
if (
|
|
1166
|
-
|
|
1167
|
-
return;
|
|
1585
|
+
if (isProgressMode) {
|
|
1586
|
+
if (slackProgressStyle === "compact") return false;
|
|
1587
|
+
return await progressDraft.pushPlanProgress(steps, { explanation });
|
|
1168
1588
|
}
|
|
1169
|
-
if (previewToolProgressSuppressed || !draftStream) return;
|
|
1589
|
+
if (previewToolProgressSuppressed || !draftStream) return false;
|
|
1170
1590
|
const text = formatChannelProgressDraftText({
|
|
1171
1591
|
entry: account.config,
|
|
1172
|
-
lines:
|
|
1592
|
+
lines: [...progressDraft.getSnapshot().lines],
|
|
1173
1593
|
seed: progressSeed,
|
|
1174
|
-
formatLine:
|
|
1594
|
+
formatLine: formatSlackProgressDraftLine,
|
|
1175
1595
|
narration: explanation,
|
|
1176
1596
|
plan: steps
|
|
1177
1597
|
});
|
|
@@ -1179,37 +1599,25 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1179
1599
|
draftStream.update(text);
|
|
1180
1600
|
hasStreamedMessage = true;
|
|
1181
1601
|
}
|
|
1602
|
+
return false;
|
|
1182
1603
|
};
|
|
1183
1604
|
const pushPreviewProgress = async (line, options) => {
|
|
1184
|
-
if (!draftStream && !useNativeProgressStreaming) return;
|
|
1185
|
-
if (options?.toolName !== void 0 && !isChannelProgressDraftWorkToolName(options.toolName)) return;
|
|
1605
|
+
if (!draftStream && !useNativeProgressStreaming) return false;
|
|
1606
|
+
if (options?.toolName !== void 0 && !isChannelProgressDraftWorkToolName(options.toolName)) return false;
|
|
1186
1607
|
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;
|
|
1608
|
+
if (isProgressMode) {
|
|
1609
|
+
if (!line || !normalized) return await progressDraft.noteActivity();
|
|
1610
|
+
return await progressDraft.pushToolProgress(line, options);
|
|
1194
1611
|
}
|
|
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;
|
|
1612
|
+
if (!line || !normalized || !draftStream || !previewToolProgressEnabled) return false;
|
|
1613
|
+
return await progressDraft.pushToolProgress(line, options);
|
|
1206
1614
|
};
|
|
1207
1615
|
const updateDraftFromPartial = (text) => {
|
|
1208
1616
|
const trimmed = text?.trimEnd();
|
|
1209
|
-
if (!trimmed) return;
|
|
1210
|
-
if (
|
|
1617
|
+
if (!trimmed) return false;
|
|
1618
|
+
if (slackStreaming.mode === "block") {
|
|
1211
1619
|
previewToolProgressSuppressed = true;
|
|
1212
|
-
|
|
1620
|
+
progressDraft.suppress();
|
|
1213
1621
|
const next = applyAppendOnlyStreamUpdate({
|
|
1214
1622
|
incoming: trimmed,
|
|
1215
1623
|
rendered: appendRenderedText,
|
|
@@ -1217,32 +1625,33 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1217
1625
|
});
|
|
1218
1626
|
appendRenderedText = next.rendered;
|
|
1219
1627
|
appendSourceText = next.source;
|
|
1220
|
-
if (!next.changed) return;
|
|
1628
|
+
if (!next.changed) return false;
|
|
1221
1629
|
draftStream?.update(next.rendered);
|
|
1222
1630
|
hasStreamedMessage = true;
|
|
1223
|
-
return;
|
|
1631
|
+
return false;
|
|
1224
1632
|
}
|
|
1225
|
-
if (
|
|
1633
|
+
if (isProgressMode) return false;
|
|
1226
1634
|
previewToolProgressSuppressed = true;
|
|
1227
|
-
|
|
1635
|
+
progressDraft.suppress();
|
|
1228
1636
|
draftStream?.update(trimmed);
|
|
1229
1637
|
hasStreamedMessage = true;
|
|
1638
|
+
return false;
|
|
1230
1639
|
};
|
|
1231
1640
|
const pushReasoningProgress = async (payload) => {
|
|
1232
|
-
if (!payload?.text) return;
|
|
1233
|
-
if (
|
|
1641
|
+
if (!payload?.text) return false;
|
|
1642
|
+
if (!isProgressMode) {
|
|
1234
1643
|
const normalized = progressDraft.mergeReasoningProgress(payload.text, { snapshot: payload.isReasoningSnapshot === true }).replace(/^_(.*)_$/su, "$1").trim();
|
|
1235
|
-
if (!normalized) return;
|
|
1236
|
-
await pushPreviewProgress({
|
|
1644
|
+
if (!normalized) return false;
|
|
1645
|
+
const visible = await pushPreviewProgress({
|
|
1237
1646
|
id: "reasoning",
|
|
1238
1647
|
kind: "item",
|
|
1239
1648
|
text: normalized,
|
|
1240
1649
|
label: "Reasoning"
|
|
1241
1650
|
});
|
|
1242
|
-
|
|
1651
|
+
progressDraft.mergeReasoningProgress(normalized, { snapshot: true });
|
|
1652
|
+
return visible;
|
|
1243
1653
|
}
|
|
1244
|
-
|
|
1245
|
-
await progressDraft.pushReasoningProgress(payload.text, { snapshot: payload.isReasoningSnapshot === true });
|
|
1654
|
+
return await progressDraft.pushReasoningProgress(payload.text, { snapshot: payload.isReasoningSnapshot === true });
|
|
1246
1655
|
};
|
|
1247
1656
|
const resetDraftDeliveryState = () => {
|
|
1248
1657
|
hasStreamedMessage = false;
|
|
@@ -1250,44 +1659,62 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1250
1659
|
appendSourceText = "";
|
|
1251
1660
|
};
|
|
1252
1661
|
const resetDraftProgressState = () => {
|
|
1253
|
-
progressDraft.resetReasoningProgress();
|
|
1254
1662
|
previewToolProgressSuppressed = false;
|
|
1255
|
-
|
|
1663
|
+
progressDraft.reset();
|
|
1256
1664
|
};
|
|
1257
1665
|
const beginNewProgressTurn = async (options) => {
|
|
1666
|
+
const priorSnapshot = progressDraft.getSnapshot();
|
|
1667
|
+
const priorFallbackText = progressCard.resolveText(priorSnapshot);
|
|
1258
1668
|
const completionChunks = useNativeProgressStreaming && !nativeProgressCompletionSent ? buildNativeProgressCompletionChunks(nativeProgressTerminalStatus) : void 0;
|
|
1259
1669
|
if (!progressDraft.beginNewTurn(options)) return false;
|
|
1260
1670
|
if (useNativeProgressStreaming) await finishNativeProgressTurn(completionChunks);
|
|
1261
|
-
else
|
|
1671
|
+
else {
|
|
1672
|
+
await progressCard.finalize("success", priorSnapshot, priorFallbackText);
|
|
1673
|
+
draftStream?.forceNewMessage();
|
|
1674
|
+
await dropDetachedProgressCards();
|
|
1675
|
+
}
|
|
1262
1676
|
resetProgressTurnState();
|
|
1263
|
-
|
|
1677
|
+
nativeStreamSnapshot = EMPTY_SLACK_NATIVE_STREAM_SNAPSHOT;
|
|
1264
1678
|
nativeProgressCompletionSent = false;
|
|
1265
1679
|
nativeProgressTerminalStatus = "complete";
|
|
1266
|
-
|
|
1680
|
+
progressCard.reset();
|
|
1267
1681
|
resetPreviewDeliveryState();
|
|
1268
1682
|
delivery.resetDeliveryTracker();
|
|
1269
|
-
progressReceiptCollapsed = false;
|
|
1270
1683
|
return true;
|
|
1271
1684
|
};
|
|
1685
|
+
const onDraftBoundary = !shouldUseDraftStream && !useNativeProgressStreaming ? void 0 : async () => {
|
|
1686
|
+
if (isProgressMode) {
|
|
1687
|
+
await beginNewProgressTurn();
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1690
|
+
if (hasStreamedMessage) draftStream?.forceNewMessage();
|
|
1691
|
+
resetDraftDeliveryState();
|
|
1692
|
+
resetDraftProgressState();
|
|
1693
|
+
};
|
|
1694
|
+
const onQueuedFollowupAdmitted = !shouldUseDraftStream && !useNativeProgressStreaming ? void 0 : async () => {
|
|
1695
|
+
await draftStream?.flush();
|
|
1696
|
+
resetPreviewDeliveryState();
|
|
1697
|
+
if (isProgressMode) await beginNewProgressTurn({ force: true });
|
|
1698
|
+
else draftStream?.forceNewMessage();
|
|
1699
|
+
delivery.resetDeliveryTracker();
|
|
1700
|
+
resetDraftDeliveryState();
|
|
1701
|
+
resetDraftProgressState();
|
|
1702
|
+
};
|
|
1703
|
+
const onQueuedFollowupSettled = !useDraftProgressCard ? void 0 : async () => {
|
|
1704
|
+
if (!progressCard.hasTerminalized) await draftStream?.clear();
|
|
1705
|
+
await dropDetachedProgressCards();
|
|
1706
|
+
};
|
|
1272
1707
|
return {
|
|
1273
1708
|
draftStream,
|
|
1274
|
-
|
|
1709
|
+
isProgressMode,
|
|
1710
|
+
useDraftProgressCard,
|
|
1275
1711
|
useNativeProgressStreaming,
|
|
1276
1712
|
progressDraftActive,
|
|
1277
1713
|
previewToolProgressEnabled,
|
|
1278
1714
|
suppressDefaultToolProgressMessages,
|
|
1279
1715
|
progressDraft,
|
|
1280
1716
|
commentaryProgressEnabled,
|
|
1281
|
-
|
|
1282
|
-
get progressReceiptCollapsed() {
|
|
1283
|
-
return progressReceiptCollapsed;
|
|
1284
|
-
},
|
|
1285
|
-
get pendingNativeProgressReceipt() {
|
|
1286
|
-
return pendingNativeProgressReceipt;
|
|
1287
|
-
},
|
|
1288
|
-
set pendingNativeProgressReceipt(value) {
|
|
1289
|
-
pendingNativeProgressReceipt = value;
|
|
1290
|
-
},
|
|
1717
|
+
progressWorkCounter,
|
|
1291
1718
|
get nativeProgressCompletionSent() {
|
|
1292
1719
|
return nativeProgressCompletionSent;
|
|
1293
1720
|
},
|
|
@@ -1297,33 +1724,17 @@ function createSlackProgressRuntime(runtimeParams) {
|
|
|
1297
1724
|
get nativeProgressTerminalStatus() {
|
|
1298
1725
|
return nativeProgressTerminalStatus;
|
|
1299
1726
|
},
|
|
1300
|
-
|
|
1301
|
-
beginNewProgressTurn,
|
|
1727
|
+
appendNativeNarration,
|
|
1302
1728
|
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
|
-
},
|
|
1729
|
+
deliverNativeFinal,
|
|
1730
|
+
dropDetachedProgressCards,
|
|
1731
|
+
finalizeDraftProgressCard: progressCard.finalize,
|
|
1732
|
+
onDraftBoundary,
|
|
1733
|
+
onQueuedFollowupAdmitted,
|
|
1734
|
+
onQueuedFollowupSettled,
|
|
1322
1735
|
pushPlanProgress,
|
|
1323
|
-
pushPreviewProgress,
|
|
1324
1736
|
pushReasoningProgress,
|
|
1325
1737
|
updateDraftFromPartial,
|
|
1326
|
-
waitForNativeProgressStreamStart,
|
|
1327
1738
|
setShouldYieldDraftProgress: (value) => {
|
|
1328
1739
|
shouldYieldDraftProgress = value;
|
|
1329
1740
|
},
|
|
@@ -1398,7 +1809,7 @@ async function createSlackDispatchSetup(prepared) {
|
|
|
1398
1809
|
sessionKey: inboundLastRouteSessionKey,
|
|
1399
1810
|
deliveryContext: {
|
|
1400
1811
|
channel: "slack",
|
|
1401
|
-
to: `user:${message.user}`,
|
|
1812
|
+
to: prepared.ctxPayload.OriginatingTo ?? prepared.ctxPayload.To ?? `user:${message.user}`,
|
|
1402
1813
|
accountId: route.accountId,
|
|
1403
1814
|
threadId: prepared.ctxPayload.MessageThreadId ?? prepared.ctxPayload.TransportThreadId
|
|
1404
1815
|
},
|
|
@@ -1434,6 +1845,7 @@ async function createSlackDispatchSetup(prepared) {
|
|
|
1434
1845
|
const messageTs = message.ts ?? message.event_ts;
|
|
1435
1846
|
const incomingThreadTs = message.thread_ts;
|
|
1436
1847
|
let didSetStatus = false;
|
|
1848
|
+
let didAddTypingReaction = false;
|
|
1437
1849
|
const statusReactionsEnabled = prepared.ctxPayload.InboundEventKind !== "room_event" && Boolean(prepared.ackReactionPromise) && Boolean(reactionMessageTs) && cfg.messages?.statusReactions?.enabled === true;
|
|
1438
1850
|
const statusReactions = createStatusReactionController({
|
|
1439
1851
|
enabled: statusReactionsEnabled,
|
|
@@ -1480,6 +1892,7 @@ async function createSlackDispatchSetup(prepared) {
|
|
|
1480
1892
|
});
|
|
1481
1893
|
const typingTarget = statusThreadTs ? `${message.channel}/${statusThreadTs}` : message.channel;
|
|
1482
1894
|
const typingReaction = ctx.typingReaction;
|
|
1895
|
+
const threadStatusGate = { hasVisibleOutput: () => false };
|
|
1483
1896
|
const { onModelSelected, ...replyPipeline } = createChannelMessageReplyPipeline({
|
|
1484
1897
|
cfg,
|
|
1485
1898
|
agentId: route.agentId,
|
|
@@ -1491,35 +1904,44 @@ async function createSlackDispatchSetup(prepared) {
|
|
|
1491
1904
|
},
|
|
1492
1905
|
typing: {
|
|
1493
1906
|
start: async () => {
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1907
|
+
if (!threadStatusGate.hasVisibleOutput()) {
|
|
1908
|
+
didSetStatus = true;
|
|
1909
|
+
await ctx.setSlackThreadStatus({
|
|
1910
|
+
channelId: message.channel,
|
|
1911
|
+
threadTs: statusThreadTs,
|
|
1912
|
+
status: "is typing...",
|
|
1913
|
+
eventScope: prepared.eventScope
|
|
1914
|
+
});
|
|
1915
|
+
}
|
|
1916
|
+
if (typingReaction && message.ts) {
|
|
1917
|
+
didAddTypingReaction = true;
|
|
1918
|
+
await reactSlackMessage(message.channel, message.ts, typingReaction, {
|
|
1919
|
+
token: ctx.botToken,
|
|
1920
|
+
client: slackClient
|
|
1921
|
+
}).catch((err) => {
|
|
1922
|
+
logVerbose(`slack send: typing reaction failed: ${formatSlackError(err)}`);
|
|
1923
|
+
});
|
|
1924
|
+
}
|
|
1507
1925
|
},
|
|
1508
1926
|
stop: async () => {
|
|
1509
|
-
if (
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1927
|
+
if (didSetStatus) {
|
|
1928
|
+
didSetStatus = false;
|
|
1929
|
+
await ctx.setSlackThreadStatus({
|
|
1930
|
+
channelId: message.channel,
|
|
1931
|
+
threadTs: statusThreadTs,
|
|
1932
|
+
status: "",
|
|
1933
|
+
eventScope: prepared.eventScope
|
|
1934
|
+
});
|
|
1935
|
+
}
|
|
1936
|
+
if (didAddTypingReaction && typingReaction && message.ts) {
|
|
1937
|
+
didAddTypingReaction = false;
|
|
1938
|
+
await removeSlackReaction(message.channel, message.ts, typingReaction, {
|
|
1939
|
+
token: ctx.botToken,
|
|
1940
|
+
client: slackClient
|
|
1941
|
+
}).catch((err) => {
|
|
1942
|
+
logVerbose(`slack send: typing reaction removal failed: ${formatSlackError(err)}`);
|
|
1943
|
+
});
|
|
1944
|
+
}
|
|
1523
1945
|
},
|
|
1524
1946
|
onStartError: (err) => {
|
|
1525
1947
|
logTypingFailure({
|
|
@@ -1541,10 +1963,7 @@ async function createSlackDispatchSetup(prepared) {
|
|
|
1541
1963
|
}
|
|
1542
1964
|
}
|
|
1543
1965
|
});
|
|
1544
|
-
const slackStreaming = resolveSlackStreamingConfig({
|
|
1545
|
-
streaming: account.config.streaming,
|
|
1546
|
-
nativeStreaming: resolveChannelStreamingNativeTransport(account.config)
|
|
1547
|
-
});
|
|
1966
|
+
const slackStreaming = resolveSlackStreamingConfig({ streaming: account.config.streaming });
|
|
1548
1967
|
const streamThreadHint = forcedReplyThreadTs ?? resolveSlackStreamingThreadHint({
|
|
1549
1968
|
replyToMode: replyDeliveryMode,
|
|
1550
1969
|
incomingThreadTs,
|
|
@@ -1553,7 +1972,7 @@ async function createSlackDispatchSetup(prepared) {
|
|
|
1553
1972
|
});
|
|
1554
1973
|
const hookRunner = getGlobalHookRunner();
|
|
1555
1974
|
const allowPreHookProviderStreaming = !((hookRunner?.hasHooks("reply_payload_sending") ?? false) || (hookRunner?.hasHooks("message_sending") ?? false));
|
|
1556
|
-
const previewStreamingEnabled = allowPreHookProviderStreaming && !sourceRepliesAreToolOnly &&
|
|
1975
|
+
const previewStreamingEnabled = allowPreHookProviderStreaming && !sourceRepliesAreToolOnly && slackStreaming.mode !== "off";
|
|
1557
1976
|
const hasSlackCustomIdentity = Boolean(slackIdentity?.username || slackIdentity?.iconUrl || slackIdentity?.iconEmoji);
|
|
1558
1977
|
const useStreaming = shouldUseStreaming({
|
|
1559
1978
|
streamingEnabled: !sourceRepliesAreToolOnly && (allowPreHookProviderStreaming || slackStreaming.mode !== "progress") && isSlackStreamingEnabled({
|
|
@@ -1563,10 +1982,7 @@ async function createSlackDispatchSetup(prepared) {
|
|
|
1563
1982
|
}),
|
|
1564
1983
|
threadTs: streamThreadHint
|
|
1565
1984
|
});
|
|
1566
|
-
const shouldUseDraftStream =
|
|
1567
|
-
previewStreamingEnabled,
|
|
1568
|
-
useStreaming
|
|
1569
|
-
});
|
|
1985
|
+
const shouldUseDraftStream = previewStreamingEnabled && !useStreaming;
|
|
1570
1986
|
const blockStreamingEnabled = resolveChannelStreamingBlockEnabled(account.config);
|
|
1571
1987
|
const disableBlockStreaming = sourceRepliesAreToolOnly ? true : resolveSlackDisableBlockStreaming({
|
|
1572
1988
|
useStreaming,
|
|
@@ -1604,6 +2020,7 @@ async function createSlackDispatchSetup(prepared) {
|
|
|
1604
2020
|
statusReactionsEnabled,
|
|
1605
2021
|
statusReactions,
|
|
1606
2022
|
hasRepliedRef,
|
|
2023
|
+
threadStatusGate,
|
|
1607
2024
|
replyPlan,
|
|
1608
2025
|
onModelSelected,
|
|
1609
2026
|
replyPipeline,
|
|
@@ -1632,11 +2049,6 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
1632
2049
|
observedFinalReplyDelivery: false
|
|
1633
2050
|
};
|
|
1634
2051
|
const streamedDeliveries = [];
|
|
1635
|
-
const streamedFailuresOwnedByDispatcher = {
|
|
1636
|
-
tool: 0,
|
|
1637
|
-
block: 0,
|
|
1638
|
-
final: 0
|
|
1639
|
-
};
|
|
1640
2052
|
const refreshStreamedAcknowledgements = (session) => {
|
|
1641
2053
|
if (session.pendingText.length === 0) for (const delivery of streamedDeliveries) delivery.acknowledged = true;
|
|
1642
2054
|
};
|
|
@@ -1752,7 +2164,7 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
1752
2164
|
...slackMessageMetadata ? { metadata: slackMessageMetadata } : {},
|
|
1753
2165
|
...messageSentDeliveryHookContext,
|
|
1754
2166
|
deferMessageSentHooks: true,
|
|
1755
|
-
|
|
2167
|
+
eventScope: prepared.eventScope
|
|
1756
2168
|
});
|
|
1757
2169
|
markSlackStreamFallbackDelivered(session);
|
|
1758
2170
|
if (!session.stopped) try {
|
|
@@ -1800,7 +2212,7 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
1800
2212
|
...slackIdentity ? { identity: slackIdentity } : {},
|
|
1801
2213
|
...slackMessageMetadata ? { metadata: slackMessageMetadata } : {},
|
|
1802
2214
|
...messageSentDeliveryHookContext,
|
|
1803
|
-
|
|
2215
|
+
eventScope: prepared.eventScope
|
|
1804
2216
|
});
|
|
1805
2217
|
state.observedReplyDelivery = true;
|
|
1806
2218
|
if (params.kind === "final") state.observedFinalReplyDelivery = true;
|
|
@@ -1819,10 +2231,7 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
1819
2231
|
return deliveryReplyThreadTs;
|
|
1820
2232
|
};
|
|
1821
2233
|
const deliverBufferedStreamFallback = async (params) => {
|
|
1822
|
-
if (!await deliverPendingStreamFallback(params.session, params.err))
|
|
1823
|
-
streamedFailuresOwnedByDispatcher[params.kind] += 1;
|
|
1824
|
-
return false;
|
|
1825
|
-
}
|
|
2234
|
+
if (!await deliverPendingStreamFallback(params.session, params.err)) return false;
|
|
1826
2235
|
replyPlan.markSent();
|
|
1827
2236
|
if (params.kind === "final") state.observedFinalReplyDelivery = true;
|
|
1828
2237
|
deliveryTracker.markDelivered({
|
|
@@ -1834,12 +2243,16 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
1834
2243
|
rememberDeliveredThreadTs(params.kind, params.session.threadTs);
|
|
1835
2244
|
return true;
|
|
1836
2245
|
};
|
|
2246
|
+
const isStreamingEligible = (payload, options) => {
|
|
2247
|
+
const reply = resolveSendableOutboundReplyParts(payload);
|
|
2248
|
+
const renderPlan = resolveSlackReplyRenderPlan(payload);
|
|
2249
|
+
const plannedBlocks = renderPlan.mode === "single" ? renderPlan.blocks : renderPlan.blockPart?.blocks;
|
|
2250
|
+
return !state.streamFailed && !reply.hasMedia && renderPlan.mode !== "split" && !plannedBlocks?.length && !readSlackReplyBlocks(payload)?.length && reply.hasText && (!options?.maxTextBytes || countSlackTextUtf8Bytes(reply.trimmedText) <= options.maxTextBytes);
|
|
2251
|
+
};
|
|
1837
2252
|
const deliverWithStreaming = async (params) => {
|
|
1838
2253
|
if (params.payload.isReasoning === true) return;
|
|
1839
2254
|
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) {
|
|
2255
|
+
if (!isStreamingEligible(params.payload)) {
|
|
1843
2256
|
await deliverNormally({
|
|
1844
2257
|
payload: params.payload,
|
|
1845
2258
|
kind: params.kind,
|
|
@@ -1847,7 +2260,8 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
1847
2260
|
});
|
|
1848
2261
|
return;
|
|
1849
2262
|
}
|
|
1850
|
-
const text = reply.trimmedText;
|
|
2263
|
+
const text = params.streamText ?? reply.trimmedText;
|
|
2264
|
+
const hookContent = reply.trimmedText;
|
|
1851
2265
|
let plannedThreadTs;
|
|
1852
2266
|
try {
|
|
1853
2267
|
if (!state.streamSession && state.nativeProgressStreamStartPromise) await state.nativeProgressStreamStartPromise;
|
|
@@ -1885,6 +2299,7 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
1885
2299
|
channel: message.channel,
|
|
1886
2300
|
threadTs: streamThreadTs,
|
|
1887
2301
|
text,
|
|
2302
|
+
...params.taskDisplayMode ? { taskDisplayMode: params.taskDisplayMode } : {},
|
|
1888
2303
|
...slackIdentity ? { identity: slackIdentity } : {},
|
|
1889
2304
|
teamId: await resolveSlackStreamRecipientTeamId({
|
|
1890
2305
|
client: slackClient,
|
|
@@ -1899,7 +2314,7 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
1899
2314
|
state.observedReplyDelivery = true;
|
|
1900
2315
|
if (params.kind === "final") state.observedFinalReplyDelivery = true;
|
|
1901
2316
|
}
|
|
1902
|
-
if (
|
|
2317
|
+
if (hookContent) rememberStreamedDelivery(params.kind, hookContent, state.streamSession);
|
|
1903
2318
|
rememberDeliveredThreadTs(params.kind, streamThreadTs);
|
|
1904
2319
|
replyPlan.markSent();
|
|
1905
2320
|
deliveryTracker.markDelivered({
|
|
@@ -1919,13 +2334,13 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
1919
2334
|
logVerbose("slack-stream: suppressed duplicate append payload");
|
|
1920
2335
|
return;
|
|
1921
2336
|
}
|
|
1922
|
-
if (
|
|
2337
|
+
if (hookContent) recordStreamedDelivery(params.kind, hookContent);
|
|
1923
2338
|
await appendSlackStream({
|
|
1924
2339
|
session: state.streamSession,
|
|
1925
|
-
text: "\n"
|
|
2340
|
+
text: `${params.appendSeparator === false ? "" : "\n"}${text}`
|
|
1926
2341
|
});
|
|
1927
2342
|
refreshStreamedAcknowledgements(state.streamSession);
|
|
1928
|
-
if (state.streamSession.delivered) {
|
|
2343
|
+
if (state.streamSession.delivered && state.streamSession.pendingText.length === 0) {
|
|
1929
2344
|
state.observedReplyDelivery = true;
|
|
1930
2345
|
if (params.kind === "final") state.observedFinalReplyDelivery = true;
|
|
1931
2346
|
}
|
|
@@ -1975,23 +2390,6 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
1975
2390
|
});
|
|
1976
2391
|
}
|
|
1977
2392
|
};
|
|
1978
|
-
const reconcileCounts = (counts) => {
|
|
1979
|
-
const next = { ...counts };
|
|
1980
|
-
let changed = false;
|
|
1981
|
-
for (const kind of [
|
|
1982
|
-
"tool",
|
|
1983
|
-
"block",
|
|
1984
|
-
"final"
|
|
1985
|
-
]) {
|
|
1986
|
-
const failedStreamedCount = streamedDeliveries.filter((delivery) => delivery.kind === kind && delivery.outcome === "failure").length;
|
|
1987
|
-
const additionalFailedStreamed = Math.max(0, failedStreamedCount - streamedFailuresOwnedByDispatcher[kind]);
|
|
1988
|
-
if (additionalFailedStreamed > 0) {
|
|
1989
|
-
next[kind] = Math.max(0, (next[kind] ?? 0) - additionalFailedStreamed);
|
|
1990
|
-
changed = true;
|
|
1991
|
-
}
|
|
1992
|
-
}
|
|
1993
|
-
return changed ? next : counts;
|
|
1994
|
-
};
|
|
1995
2393
|
return Object.assign(state, {
|
|
1996
2394
|
acknowledgeStoppedStreamedDeliveries,
|
|
1997
2395
|
deliverNormally,
|
|
@@ -2000,9 +2398,9 @@ function createSlackStreamingDeliveryRuntime(setup) {
|
|
|
2000
2398
|
emitAcknowledgedStreamedDeliveries,
|
|
2001
2399
|
emitFailedPendingStreamedDeliveries,
|
|
2002
2400
|
hasDelivered: (params) => deliveryTracker.hasDelivered(params),
|
|
2401
|
+
isStreamingEligible,
|
|
2003
2402
|
markPreviewPayloadDelivered,
|
|
2004
2403
|
rememberDeliveredThreadTs,
|
|
2005
|
-
reconcileCounts,
|
|
2006
2404
|
resetDeliveryTracker: () => {
|
|
2007
2405
|
deliveryTracker = createSlackEventDeliveryTracker();
|
|
2008
2406
|
}
|
|
@@ -2024,47 +2422,66 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2024
2422
|
}
|
|
2025
2423
|
});
|
|
2026
2424
|
const draftStream = progress.draftStream;
|
|
2425
|
+
setup.threadStatusGate.hasVisibleOutput = () => delivery.observedReplyDelivery || draftPreviewCommitted.value || Boolean(draftStream?.messageId());
|
|
2426
|
+
const failureNoticeThreadTs = message.thread_ts;
|
|
2427
|
+
const failureNoticeTeamId = prepared.eventScope?.teamId;
|
|
2428
|
+
let sawTerminalFailurePayload = false;
|
|
2429
|
+
let pendingFailureNotice;
|
|
2430
|
+
const filterPassiveThreadFailure = (payload) => {
|
|
2431
|
+
if (payload.isError !== true || prepared.ctxPayload.ChatType !== "channel" || isReplyPayloadNonTerminalToolErrorWarning(payload)) return payload;
|
|
2432
|
+
sawTerminalFailurePayload = true;
|
|
2433
|
+
if (delivery.observedReplyDelivery || draftPreviewCommitted.value) return payload;
|
|
2434
|
+
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;
|
|
2435
|
+
const noticeThreadTs = failureNoticeThreadTs ?? (explicitlyAddressed ? statusThreadTs : void 0);
|
|
2436
|
+
const notice = {
|
|
2437
|
+
accountId: account.accountId,
|
|
2438
|
+
channelId: message.channel,
|
|
2439
|
+
...noticeThreadTs ? { threadTs: noticeThreadTs } : {},
|
|
2440
|
+
failureText: payload.text ?? "",
|
|
2441
|
+
...failureNoticeTeamId ? { teamId: failureNoticeTeamId } : {}
|
|
2442
|
+
};
|
|
2443
|
+
if (failureNoticeThreadTs && !explicitlyAddressed && prepared.ctxPayload.MentionSource !== "implicit_thread" && !hasSlackThreadParticipation(notice.accountId, notice.channelId, failureNoticeThreadTs, failureNoticeTeamId)) {
|
|
2444
|
+
logVerbose("slack: suppressed passive failure before thread participation");
|
|
2445
|
+
return null;
|
|
2446
|
+
}
|
|
2447
|
+
if (!explicitlyAddressed && hasSlackThreadFailureNotice(notice)) {
|
|
2448
|
+
logVerbose("slack: suppressed repeated passive channel or thread failure");
|
|
2449
|
+
return null;
|
|
2450
|
+
}
|
|
2451
|
+
pendingFailureNotice = notice;
|
|
2452
|
+
return payload;
|
|
2453
|
+
};
|
|
2027
2454
|
const deliverSlackPayload = async (payload, info) => {
|
|
2028
2455
|
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();
|
|
2456
|
+
if (info.kind === "final" && slackStreaming.mode === "progress" && progress.isProgressMode) {
|
|
2032
2457
|
if (progress.useNativeProgressStreaming) {
|
|
2033
|
-
await progress.
|
|
2034
|
-
|
|
2458
|
+
await progress.deliverNativeFinal(payload, info.kind);
|
|
2459
|
+
return;
|
|
2460
|
+
}
|
|
2461
|
+
progress.progressDraft.markFinalReplyStarted();
|
|
2462
|
+
if (progress.useDraftProgressCard) {
|
|
2035
2463
|
await delivery.deliverNormally({
|
|
2036
2464
|
payload,
|
|
2037
2465
|
kind: info.kind,
|
|
2038
|
-
forcedThreadTs:
|
|
2466
|
+
forcedThreadTs: delivery.usedReplyThreadTs
|
|
2039
2467
|
});
|
|
2040
|
-
await progress.
|
|
2468
|
+
if (!await progress.finalizeDraftProgressCard(payload.isError === true ? "error" : "success")) await draftStream?.clear();
|
|
2041
2469
|
progress.progressDraft.markFinalReplyDelivered();
|
|
2042
|
-
if (!payload.isError && hadProgressDraft && delivery.streamSession) progress.pendingNativeProgressReceipt = progress.progressReceipt.buildSummaryLine();
|
|
2043
2470
|
return;
|
|
2044
2471
|
}
|
|
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
2472
|
}
|
|
2067
2473
|
if (progress.useNativeProgressStreaming) {
|
|
2474
|
+
if (info.kind !== "final" && payload.isError !== true) {
|
|
2475
|
+
if (!delivery.isStreamingEligible(payload)) {
|
|
2476
|
+
await delivery.deliverNormally({
|
|
2477
|
+
payload,
|
|
2478
|
+
kind: info.kind,
|
|
2479
|
+
forcedThreadTs: delivery.streamSession?.threadTs ?? delivery.nativeProgressStreamThreadTs
|
|
2480
|
+
});
|
|
2481
|
+
return;
|
|
2482
|
+
}
|
|
2483
|
+
return await progress.appendNativeNarration(payload, info.kind) ? void 0 : { visibleReplySent: false };
|
|
2484
|
+
}
|
|
2068
2485
|
await delivery.deliverNormally({
|
|
2069
2486
|
payload,
|
|
2070
2487
|
kind: info.kind,
|
|
@@ -2087,61 +2504,9 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2087
2504
|
const requiresSeparateFallbackDelivery = replyRenderPlan.mode === "split";
|
|
2088
2505
|
const trimmedFinalText = replyRenderPlan.mode === "single" ? replyRenderPlan.text.trim() : replyRenderPlan.fallbackText.trim();
|
|
2089
2506
|
const previewFinalText = replyRenderPlan.mode === "single" && replyRenderPlan.textIsSlackMrkdwn ? trimmedFinalText : normalizeSlackOutboundText((replySourceText ?? "").trim());
|
|
2507
|
+
const previewFinalTextFitsEdit = countSlackTextUtf8Bytes(previewFinalText) <= SLACK_EDIT_TEXT_MAX_BYTES;
|
|
2090
2508
|
const shouldRestoreTtsSupplementTextForPreviewFallback = Boolean(ttsSupplement) && ttsSupplement?.visibleTextAlreadyDelivered !== true && Boolean(draftStream) && !draftPreviewCommitted.value && !delivery.observedFinalReplyDelivery && previewStreamingEnabled && !payload.text?.trim();
|
|
2091
|
-
|
|
2092
|
-
const channelId = draftStream.channelId();
|
|
2093
|
-
const messageId = draftStream.messageId();
|
|
2094
|
-
if (channelId && messageId) {
|
|
2095
|
-
const finalThreadTs = delivery.usedReplyThreadTs ?? statusThreadTs;
|
|
2096
|
-
await draftStream.flush();
|
|
2097
|
-
await draftStream.seal();
|
|
2098
|
-
try {
|
|
2099
|
-
await finalizeSlackPreviewEdit({
|
|
2100
|
-
client: slackClient,
|
|
2101
|
-
token: ctx.botToken,
|
|
2102
|
-
accountId: account.accountId,
|
|
2103
|
-
channelId,
|
|
2104
|
-
messageId,
|
|
2105
|
-
text: previewFinalText,
|
|
2106
|
-
...slackBlocks?.length ? { blocks: slackBlocks } : {},
|
|
2107
|
-
threadTs: finalThreadTs
|
|
2108
|
-
});
|
|
2109
|
-
} catch (err) {
|
|
2110
|
-
logVerbose(`slack: preview final edit failed; falling back to standard send (${formatSlackError(err)})`);
|
|
2111
|
-
await draftStream.discardPending();
|
|
2112
|
-
let delivered = false;
|
|
2113
|
-
try {
|
|
2114
|
-
await delivery.deliverNormally({
|
|
2115
|
-
payload: payload.text?.trim() ? payload : {
|
|
2116
|
-
...payload,
|
|
2117
|
-
text: ttsSupplement.spokenText
|
|
2118
|
-
},
|
|
2119
|
-
kind: info.kind,
|
|
2120
|
-
forcedThreadTs: finalThreadTs
|
|
2121
|
-
});
|
|
2122
|
-
delivered = true;
|
|
2123
|
-
} finally {
|
|
2124
|
-
if (delivered) await draftStream.clear();
|
|
2125
|
-
}
|
|
2126
|
-
return;
|
|
2127
|
-
}
|
|
2128
|
-
draftPreviewCommitted.value = true;
|
|
2129
|
-
delivery.observedFinalReplyDelivery = true;
|
|
2130
|
-
delivery.observedReplyDelivery = true;
|
|
2131
|
-
replyPlan.markSent();
|
|
2132
|
-
await delivery.deliverNormally({
|
|
2133
|
-
payload: buildTtsSupplementMediaPayload(payload),
|
|
2134
|
-
kind: info.kind,
|
|
2135
|
-
forcedThreadTs: finalThreadTs
|
|
2136
|
-
});
|
|
2137
|
-
delivery.markPreviewPayloadDelivered({
|
|
2138
|
-
kind: info.kind,
|
|
2139
|
-
payload,
|
|
2140
|
-
threadTs: finalThreadTs
|
|
2141
|
-
});
|
|
2142
|
-
return;
|
|
2143
|
-
}
|
|
2144
|
-
}
|
|
2509
|
+
let ttsPreviewFinalization;
|
|
2145
2510
|
await deliverWithFinalizableLivePreviewAdapter({
|
|
2146
2511
|
kind: info.kind,
|
|
2147
2512
|
payload,
|
|
@@ -2161,7 +2526,7 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2161
2526
|
}
|
|
2162
2527
|
} : void 0,
|
|
2163
2528
|
buildFinalEdit: () => {
|
|
2164
|
-
if (hasSlackCustomIdentity || !previewStreamingEnabled || reply.hasMedia && !ttsSupplement || payload.isError || requiresSeparateFallbackDelivery || trimmedFinalText.length === 0 && !slackBlocks?.length) return;
|
|
2529
|
+
if (hasSlackCustomIdentity || !previewStreamingEnabled || reply.hasMedia && !ttsSupplement || payload.isError || requiresSeparateFallbackDelivery || !previewFinalTextFitsEdit || trimmedFinalText.length === 0 && !slackBlocks?.length) return;
|
|
2165
2530
|
return {
|
|
2166
2531
|
text: previewFinalText,
|
|
2167
2532
|
blocks: slackBlocks,
|
|
@@ -2174,16 +2539,19 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2174
2539
|
payload,
|
|
2175
2540
|
threadTs: edit.threadTs
|
|
2176
2541
|
})) return;
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2542
|
+
if (ttsSupplement) ttsPreviewFinalization = { threadTs: edit.threadTs };
|
|
2543
|
+
if (!await draftStream?.finalizeMessage(preview.messageId, async () => {
|
|
2544
|
+
await finalizeSlackPreviewEdit({
|
|
2545
|
+
client: slackClient,
|
|
2546
|
+
token: ctx.botToken,
|
|
2547
|
+
accountId: account.accountId,
|
|
2548
|
+
channelId: preview.channelId,
|
|
2549
|
+
messageId: preview.messageId,
|
|
2550
|
+
text: edit.text,
|
|
2551
|
+
...edit.blocks?.length ? { blocks: edit.blocks } : {},
|
|
2552
|
+
threadTs: edit.threadTs
|
|
2553
|
+
});
|
|
2554
|
+
})) throw new Error("Slack preview moved below a newer conversation message");
|
|
2187
2555
|
if (!ttsSupplement) emitSlackMessageSentHooks({
|
|
2188
2556
|
...messageSentHookContext,
|
|
2189
2557
|
to: messageSentHookTarget,
|
|
@@ -2227,18 +2595,20 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2227
2595
|
}),
|
|
2228
2596
|
deliverNormally: async () => {
|
|
2229
2597
|
await delivery.deliverNormally({
|
|
2230
|
-
payload: shouldRestoreTtsSupplementTextForPreviewFallback ? {
|
|
2598
|
+
payload: shouldRestoreTtsSupplementTextForPreviewFallback || ttsPreviewFinalization && !payload.text?.trim() ? {
|
|
2231
2599
|
...payload,
|
|
2232
2600
|
text: ttsSupplement?.spokenText
|
|
2233
2601
|
} : payload,
|
|
2234
|
-
kind: info.kind
|
|
2602
|
+
kind: info.kind,
|
|
2603
|
+
...ttsPreviewFinalization?.threadTs ? { forcedThreadTs: ttsPreviewFinalization.threadTs } : {}
|
|
2235
2604
|
});
|
|
2236
2605
|
}
|
|
2237
2606
|
});
|
|
2607
|
+
if (info.kind === "final") progress.progressDraft.markFinalReplyDelivered();
|
|
2238
2608
|
};
|
|
2239
2609
|
let dispatchError;
|
|
2240
|
-
let
|
|
2241
|
-
let
|
|
2610
|
+
let agentRunFailed = false;
|
|
2611
|
+
let settledDispatchResult;
|
|
2242
2612
|
try {
|
|
2243
2613
|
const turnResult = await dispatchChannelInboundTurn({
|
|
2244
2614
|
cfg,
|
|
@@ -2251,6 +2621,10 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2251
2621
|
ctxPayload: prepared.ctxPayload,
|
|
2252
2622
|
dispatcherOptions: {
|
|
2253
2623
|
...replyPipeline,
|
|
2624
|
+
transformReplyPayload: (payload) => {
|
|
2625
|
+
const transformed = replyPipeline.transformReplyPayload ? replyPipeline.transformReplyPayload(payload) : payload;
|
|
2626
|
+
return transformed ? filterPassiveThreadFailure(transformed) : null;
|
|
2627
|
+
},
|
|
2254
2628
|
humanDelay: resolveHumanDelayConfig(cfg, route.agentId)
|
|
2255
2629
|
},
|
|
2256
2630
|
delivery: {
|
|
@@ -2272,120 +2646,82 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2272
2646
|
commentaryProgressEnabled: progress.commentaryProgressEnabled ? true : void 0,
|
|
2273
2647
|
progressPreambleEnabled: progress.progressDraftActive && slackStreaming.mode === "progress" ? true : void 0,
|
|
2274
2648
|
commentaryPayloadsEnabled: progress.commentaryProgressEnabled ? true : void 0,
|
|
2649
|
+
shouldDeliverCommentaryPayloads: progress.commentaryProgressEnabled ? progress.shouldYieldDraftProgress : void 0,
|
|
2275
2650
|
onVerboseProgressVisibility: progress.commentaryProgressEnabled ? (isActive) => {
|
|
2276
2651
|
progress.setShouldYieldDraftProgress(isActive);
|
|
2277
2652
|
} : void 0,
|
|
2278
2653
|
allowProgressCallbacksWhenSourceDeliverySuppressed: sourceReplyDeliveryMode === "message_tool_only" && statusReactionsEnabled ? true : void 0,
|
|
2279
2654
|
allowToolLifecycleWhenProgressHidden: statusReactionsEnabled ? true : void 0,
|
|
2280
2655
|
onPartialReply: useStreaming ? void 0 : !previewStreamingEnabled ? void 0 : async (payload) => {
|
|
2281
|
-
progress.updateDraftFromPartial(payload.text);
|
|
2656
|
+
return progress.updateDraftFromPartial(payload.text);
|
|
2282
2657
|
},
|
|
2283
|
-
onAssistantMessageStart: progress.onDraftBoundary
|
|
2658
|
+
onAssistantMessageStart: progress.onDraftBoundary ? async () => {
|
|
2659
|
+
await progress.onDraftBoundary?.();
|
|
2660
|
+
return false;
|
|
2661
|
+
} : void 0,
|
|
2284
2662
|
onReasoningEnd: async () => {
|
|
2285
|
-
progress.progressReceipt.closeReasoning();
|
|
2286
2663
|
await progress.onDraftBoundary?.();
|
|
2664
|
+
return false;
|
|
2287
2665
|
},
|
|
2288
2666
|
onQueuedFollowupAdmitted: progress.onQueuedFollowupAdmitted,
|
|
2667
|
+
onQueuedFollowupSettled: progress.onQueuedFollowupSettled,
|
|
2289
2668
|
onReasoningStream: statusReactionsEnabled || progress.previewToolProgressEnabled ? async (payload) => {
|
|
2290
|
-
await progress.pushReasoningProgress(payload);
|
|
2291
|
-
if (!statusReactionsEnabled) return;
|
|
2669
|
+
const visible = await progress.pushReasoningProgress(payload);
|
|
2670
|
+
if (!statusReactionsEnabled) return visible;
|
|
2292
2671
|
await statusReactions.setThinking();
|
|
2672
|
+
return visible;
|
|
2293
2673
|
} : void 0,
|
|
2294
2674
|
onToolStart: async (payload) => {
|
|
2295
2675
|
if (statusReactionsEnabled) await statusReactions.setTool(payload.name);
|
|
2296
|
-
if (payload.phase === "start") progress.
|
|
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 });
|
|
2676
|
+
if (payload.phase === "start") progress.progressWorkCounter.noteToolCall(payload.name);
|
|
2677
|
+
return await progress.progressDraft.pushToolEvent(payload);
|
|
2305
2678
|
},
|
|
2306
2679
|
onItemEvent: async (payload) => {
|
|
2307
|
-
if (progress.
|
|
2308
|
-
if (progress.shouldYieldDraftProgress()) return;
|
|
2309
|
-
await progress.progressDraft.pushPreambleHeadline(payload.progressText, { itemId: payload.itemId });
|
|
2310
|
-
if (progress.commentaryProgressEnabled) {
|
|
2311
|
-
|
|
2312
|
-
}
|
|
2313
|
-
return;
|
|
2680
|
+
if (progress.isProgressMode && payload.kind === "preamble") {
|
|
2681
|
+
if (progress.shouldYieldDraftProgress()) return false;
|
|
2682
|
+
const headlineVisible = await progress.progressDraft.pushPreambleHeadline(payload.progressText, { itemId: payload.itemId });
|
|
2683
|
+
if (progress.commentaryProgressEnabled) return await progress.progressDraft.pushCommentaryProgress(payload.progressText, { itemId: payload.itemId }) || headlineVisible;
|
|
2684
|
+
return headlineVisible;
|
|
2314
2685
|
}
|
|
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
|
-
}));
|
|
2686
|
+
return await progress.progressDraft.pushItemEvent(payload);
|
|
2328
2687
|
},
|
|
2329
2688
|
onPlanUpdate: async (payload) => {
|
|
2330
|
-
if (payload.phase !== "update") return;
|
|
2331
|
-
await progress.pushPlanProgress(payload.steps, payload.explanation);
|
|
2689
|
+
if (payload.phase !== "update") return false;
|
|
2690
|
+
return await progress.pushPlanProgress(payload.steps, payload.explanation);
|
|
2332
2691
|
},
|
|
2333
2692
|
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
|
-
}));
|
|
2693
|
+
return await progress.progressDraft.pushApprovalEvent(payload);
|
|
2343
2694
|
},
|
|
2344
2695
|
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
|
-
}));
|
|
2696
|
+
return await progress.progressDraft.pushCommandOutputEvent(payload);
|
|
2356
2697
|
},
|
|
2357
2698
|
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
|
-
}));
|
|
2699
|
+
return await progress.progressDraft.pushPatchEvent(payload);
|
|
2371
2700
|
}
|
|
2372
2701
|
}
|
|
2373
2702
|
});
|
|
2374
2703
|
if (turnResult.dispatched) {
|
|
2375
2704
|
const result = turnResult.dispatchResult;
|
|
2376
|
-
|
|
2377
|
-
|
|
2705
|
+
settledDispatchResult = result;
|
|
2706
|
+
const agentRunOutcome = readAgentRunTerminalOutcome(result);
|
|
2707
|
+
agentRunFailed = agentRunOutcome === "failed";
|
|
2708
|
+
if (agentRunOutcome === "completed" && !sawTerminalFailurePayload && prepared.ctxPayload.ChatType === "channel") clearSlackThreadFailureNotice({
|
|
2709
|
+
accountId: account.accountId,
|
|
2710
|
+
channelId: message.channel,
|
|
2711
|
+
...failureNoticeThreadTs ? { threadTs: failureNoticeThreadTs } : {},
|
|
2712
|
+
...failureNoticeTeamId ? { teamId: failureNoticeTeamId } : {}
|
|
2713
|
+
});
|
|
2378
2714
|
}
|
|
2379
2715
|
} catch (err) {
|
|
2380
2716
|
dispatchError = err;
|
|
2381
2717
|
} finally {
|
|
2382
2718
|
progress.progressDraft.cancel();
|
|
2383
|
-
await draftStream?.discardPending();
|
|
2719
|
+
if (!progress.useDraftProgressCard) await draftStream?.discardPending();
|
|
2384
2720
|
}
|
|
2385
2721
|
let streamFallbackDelivered = false;
|
|
2386
2722
|
const finalStream = delivery.streamSession;
|
|
2387
2723
|
if (finalStream && !finalStream.stopped) try {
|
|
2388
|
-
const completionChunks = progress.useNativeProgressStreaming && !progress.nativeProgressCompletionSent ? progress.buildNativeProgressCompletionChunks(dispatchError ? "error" : progress.nativeProgressTerminalStatus) : void 0;
|
|
2724
|
+
const completionChunks = progress.useNativeProgressStreaming && !progress.nativeProgressCompletionSent ? progress.buildNativeProgressCompletionChunks(dispatchError || agentRunFailed ? "error" : progress.nativeProgressTerminalStatus) : void 0;
|
|
2389
2725
|
if (completionChunks?.length) progress.nativeProgressCompletionSent = true;
|
|
2390
2726
|
const stopResult = await stopSlackStream({
|
|
2391
2727
|
session: finalStream,
|
|
@@ -2393,12 +2729,6 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2393
2729
|
...slackMessageMetadata ? { metadata: slackMessageMetadata } : {}
|
|
2394
2730
|
});
|
|
2395
2731
|
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
2732
|
} catch (err) {
|
|
2403
2733
|
if (err instanceof SlackStreamNotDeliveredError) {
|
|
2404
2734
|
streamFallbackDelivered = await delivery.deliverPendingStreamFallback(finalStream, err);
|
|
@@ -2411,50 +2741,45 @@ async function dispatchPreparedSlackMessage(prepared) {
|
|
|
2411
2741
|
if (!finalStream.delivered) dispatchError ??= err;
|
|
2412
2742
|
}
|
|
2413
2743
|
}
|
|
2414
|
-
|
|
2415
|
-
queuedFinal = queuedFinal && (counts.final ?? 0) > 0;
|
|
2416
|
-
const anyReplyDelivered = hasVisibleInboundReplyDispatch({
|
|
2417
|
-
queuedFinal,
|
|
2418
|
-
counts
|
|
2419
|
-
}, {
|
|
2744
|
+
const anyReplyDelivered = hasVisibleInboundReplyDispatch(settledDispatchResult, {
|
|
2420
2745
|
observedReplyDelivery: delivery.observedReplyDelivery,
|
|
2421
2746
|
fallbackDelivered: streamFallbackDelivered
|
|
2422
2747
|
});
|
|
2423
|
-
if (
|
|
2424
|
-
|
|
2748
|
+
if (pendingFailureNotice && anyReplyDelivered) recordSlackThreadFailureNotice(pendingFailureNotice);
|
|
2749
|
+
if (dispatchError || agentRunFailed) await progress.finalizeDraftProgressCard("error");
|
|
2750
|
+
await progress.dropDetachedProgressCards();
|
|
2751
|
+
if (statusReactionsEnabled) if (dispatchError || agentRunFailed) {
|
|
2752
|
+
await statusReactions.setError();
|
|
2753
|
+
statusReactions.restoreInitial();
|
|
2754
|
+
} else if (anyReplyDelivered) {
|
|
2425
2755
|
await statusReactions.setDone();
|
|
2426
2756
|
statusReactions.restoreInitial();
|
|
2427
2757
|
} else await statusReactions.restoreInitial();
|
|
2428
2758
|
const participationThreadTs = delivery.usedReplyThreadTs ?? statusThreadTs;
|
|
2429
2759
|
if (anyReplyDelivered && participationThreadTs) recordSlackThreadParticipation(account.accountId, message.channel, participationThreadTs, {
|
|
2430
2760
|
agentId: route.agentId,
|
|
2431
|
-
|
|
2761
|
+
teamId: prepared.eventScope?.teamId
|
|
2432
2762
|
});
|
|
2433
2763
|
if (dispatchError) throw toErrorObject(dispatchError, "Slack dispatch failed");
|
|
2434
|
-
if (!anyReplyDelivered && !draftPreviewCommitted.value) {
|
|
2764
|
+
if (!anyReplyDelivered && !draftPreviewCommitted.value && !(agentRunFailed && progress.useDraftProgressCard)) {
|
|
2435
2765
|
await draftStream?.clear();
|
|
2766
|
+
await draftStream?.dropDetachedMessages();
|
|
2436
2767
|
return;
|
|
2437
2768
|
}
|
|
2438
2769
|
if (shouldLogVerbose()) {
|
|
2439
|
-
const finalCount =
|
|
2770
|
+
const finalCount = resolveInboundReplyDispatchCounts(settledDispatchResult).final;
|
|
2440
2771
|
logVerbose(`slack: delivered ${finalCount} reply${finalCount === 1 ? "" : "ies"} to ${prepared.replyTarget}`);
|
|
2441
2772
|
}
|
|
2442
2773
|
}
|
|
2443
2774
|
//#endregion
|
|
2444
2775
|
//#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
2776
|
function normalizeEntity(value) {
|
|
2452
|
-
const entity =
|
|
2453
|
-
const type =
|
|
2777
|
+
const entity = asOptionalRecord(value);
|
|
2778
|
+
const type = normalizeOptionalString(entity?.type);
|
|
2454
2779
|
if (!entity || !type) return;
|
|
2455
|
-
const teamId =
|
|
2780
|
+
const teamId = normalizeOptionalString(entity.team_id);
|
|
2456
2781
|
if (type === "slack#/types/channel_id" || type === "slack#/types/canvas_id" || type === "slack#/types/list_id") {
|
|
2457
|
-
const entityValue =
|
|
2782
|
+
const entityValue = normalizeOptionalString(entity.value);
|
|
2458
2783
|
return entityValue ? {
|
|
2459
2784
|
type,
|
|
2460
2785
|
value: entityValue,
|
|
@@ -2462,9 +2787,9 @@ function normalizeEntity(value) {
|
|
|
2462
2787
|
} : void 0;
|
|
2463
2788
|
}
|
|
2464
2789
|
if (type !== "slack#/types/message_context") return;
|
|
2465
|
-
const message =
|
|
2466
|
-
const channelId =
|
|
2467
|
-
const messageTs =
|
|
2790
|
+
const message = asOptionalRecord(entity.value);
|
|
2791
|
+
const channelId = normalizeOptionalString(message?.channel_id);
|
|
2792
|
+
const messageTs = normalizeOptionalString(message?.message_ts);
|
|
2468
2793
|
return channelId && messageTs ? {
|
|
2469
2794
|
type,
|
|
2470
2795
|
value: {
|
|
@@ -2475,10 +2800,10 @@ function normalizeEntity(value) {
|
|
|
2475
2800
|
} : void 0;
|
|
2476
2801
|
}
|
|
2477
2802
|
function isSlackAppContext(value) {
|
|
2478
|
-
return Boolean(
|
|
2803
|
+
return Boolean(asOptionalRecord(value));
|
|
2479
2804
|
}
|
|
2480
2805
|
function normalizeSlackAppContextEntities(value) {
|
|
2481
|
-
const context =
|
|
2806
|
+
const context = asOptionalRecord(value);
|
|
2482
2807
|
if (!Array.isArray(context?.entities)) return [];
|
|
2483
2808
|
return context.entities.flatMap((entity) => {
|
|
2484
2809
|
const normalized = normalizeEntity(entity);
|
|
@@ -2486,89 +2811,194 @@ function normalizeSlackAppContextEntities(value) {
|
|
|
2486
2811
|
});
|
|
2487
2812
|
}
|
|
2488
2813
|
//#endregion
|
|
2814
|
+
//#region extensions/slack/src/monitor/thread.ts
|
|
2815
|
+
const THREAD_STARTER_CACHE = /* @__PURE__ */ new Map();
|
|
2816
|
+
const THREAD_STARTER_CACHE_TTL_MS = 360 * 6e4;
|
|
2817
|
+
const THREAD_STARTER_CACHE_MAX = 2e3;
|
|
2818
|
+
function evictThreadStarterCache() {
|
|
2819
|
+
const now = asDateTimestampMs(Date.now());
|
|
2820
|
+
if (now === void 0) {
|
|
2821
|
+
THREAD_STARTER_CACHE.clear();
|
|
2822
|
+
return;
|
|
2823
|
+
}
|
|
2824
|
+
for (const [cacheKey, entry] of THREAD_STARTER_CACHE.entries()) if (asDateTimestampMs(entry.expiresAt) === void 0 || entry.expiresAt <= now) THREAD_STARTER_CACHE.delete(cacheKey);
|
|
2825
|
+
pruneMapToMaxSize(THREAD_STARTER_CACHE, THREAD_STARTER_CACHE_MAX);
|
|
2826
|
+
}
|
|
2827
|
+
function formatSlackFilePlaceholder(files) {
|
|
2828
|
+
return `[attached: ${formatSlackFileReferenceList(files)}]`;
|
|
2829
|
+
}
|
|
2830
|
+
function pushUniqueText(parts, value, options = {}) {
|
|
2831
|
+
const text = options.preserveWhitespace ? typeof value === "string" && value.trim().length > 0 ? value : void 0 : normalizeOptionalString(value);
|
|
2832
|
+
if (text && !parts.includes(text)) parts.push(text);
|
|
2833
|
+
}
|
|
2834
|
+
function resolveSlackBlocksFallbackText(blocks) {
|
|
2835
|
+
return resolveSlackBlocksText(blocks)?.text;
|
|
2836
|
+
}
|
|
2837
|
+
function resolveSlackAttachmentFallbackText(attachments) {
|
|
2838
|
+
if (!Array.isArray(attachments) || attachments.length === 0) return;
|
|
2839
|
+
const parts = [];
|
|
2840
|
+
for (const attachment of attachments) {
|
|
2841
|
+
const excludeTableBlocks = isSlackUnfurlAttachment(attachment);
|
|
2842
|
+
const fallbackBlocks = (blocks) => excludeTableBlocks ? blocks?.filter((block) => !hasSlackTableBlock([block])) : blocks;
|
|
2843
|
+
pushUniqueText(parts, attachment.pretext);
|
|
2844
|
+
pushUniqueText(parts, attachment.title);
|
|
2845
|
+
pushUniqueText(parts, attachment.text);
|
|
2846
|
+
if (!(hasSlackTableBlock(attachment.blocks) && normalizeOptionalString(attachment.fallback) === "[no preview available]")) pushUniqueText(parts, attachment.fallback);
|
|
2847
|
+
for (const field of attachment.fields ?? []) {
|
|
2848
|
+
pushUniqueText(parts, field.title);
|
|
2849
|
+
pushUniqueText(parts, field.value);
|
|
2850
|
+
}
|
|
2851
|
+
pushUniqueText(parts, resolveSlackBlocksFallbackText(fallbackBlocks(attachment.blocks)), { preserveWhitespace: true });
|
|
2852
|
+
pushUniqueText(parts, resolveSlackBlocksFallbackText(fallbackBlocks(attachment.message_blocks)), { preserveWhitespace: true });
|
|
2853
|
+
}
|
|
2854
|
+
return parts.length > 0 ? parts.join("\n") : void 0;
|
|
2855
|
+
}
|
|
2856
|
+
function resolveSlackMessageText(message) {
|
|
2857
|
+
const messageText = normalizeOptionalString(message.text) ?? resolveSlackAttachmentFallbackText(message.attachments);
|
|
2858
|
+
return resolveSlackMessageText$1({
|
|
2859
|
+
...message,
|
|
2860
|
+
text: messageText
|
|
2861
|
+
}, { preserveMessageTextWhitespace: true });
|
|
2862
|
+
}
|
|
2863
|
+
async function resolveSlackThreadStarter(params) {
|
|
2864
|
+
evictThreadStarterCache();
|
|
2865
|
+
const cacheKey = JSON.stringify([
|
|
2866
|
+
params.workspaceScope.accountId,
|
|
2867
|
+
params.workspaceScope.teamId,
|
|
2868
|
+
params.channelId,
|
|
2869
|
+
params.threadTs
|
|
2870
|
+
]);
|
|
2871
|
+
const cached = THREAD_STARTER_CACHE.get(cacheKey);
|
|
2872
|
+
if (cached) {
|
|
2873
|
+
const now = asDateTimestampMs(Date.now());
|
|
2874
|
+
if (now !== void 0 && cached.expiresAt > now) return cached.value;
|
|
2875
|
+
THREAD_STARTER_CACHE.delete(cacheKey);
|
|
2876
|
+
}
|
|
2877
|
+
try {
|
|
2878
|
+
const message = (await params.client.conversations.replies({
|
|
2879
|
+
channel: params.channelId,
|
|
2880
|
+
ts: params.threadTs,
|
|
2881
|
+
limit: 1,
|
|
2882
|
+
inclusive: true
|
|
2883
|
+
}))?.messages?.[0];
|
|
2884
|
+
const text = message ? resolveSlackMessageText(message) : void 0;
|
|
2885
|
+
const files = message?.files?.length ? message.files : void 0;
|
|
2886
|
+
if (!message || !text && !files) return null;
|
|
2887
|
+
const starter = {
|
|
2888
|
+
text: text || formatSlackFilePlaceholder(files),
|
|
2889
|
+
userId: message.user,
|
|
2890
|
+
botId: message.bot_id,
|
|
2891
|
+
ts: message.ts,
|
|
2892
|
+
files
|
|
2893
|
+
};
|
|
2894
|
+
const expiresAt = resolveExpiresAtMsFromDurationMs(THREAD_STARTER_CACHE_TTL_MS);
|
|
2895
|
+
if (expiresAt !== void 0) {
|
|
2896
|
+
if (THREAD_STARTER_CACHE.has(cacheKey)) THREAD_STARTER_CACHE.delete(cacheKey);
|
|
2897
|
+
THREAD_STARTER_CACHE.set(cacheKey, {
|
|
2898
|
+
value: starter,
|
|
2899
|
+
expiresAt
|
|
2900
|
+
});
|
|
2901
|
+
evictThreadStarterCache();
|
|
2902
|
+
}
|
|
2903
|
+
return starter;
|
|
2904
|
+
} catch (err) {
|
|
2905
|
+
logVerbose$1(`slack thread starter fetch failed channel=${params.channelId} ts=${params.threadTs}: ${formatErrorMessage(err)}`);
|
|
2906
|
+
return null;
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
const SLACK_THREAD_HISTORY_MAX_PAGES = 3;
|
|
2910
|
+
/**
|
|
2911
|
+
* Fetches the most recent messages in a Slack thread (excluding the current message).
|
|
2912
|
+
* Used to populate thread context when a new thread session starts.
|
|
2913
|
+
*
|
|
2914
|
+
* Uses cursor pagination and keeps only the latest N retained messages when the full
|
|
2915
|
+
* thread fits in the bounded fetch window.
|
|
2916
|
+
*/
|
|
2917
|
+
async function resolveSlackThreadHistory(params) {
|
|
2918
|
+
const maxMessages = params.limit ?? 20;
|
|
2919
|
+
if (!Number.isFinite(maxMessages) || maxMessages <= 0) return [];
|
|
2920
|
+
const fetchLimit = 200;
|
|
2921
|
+
const retained = [];
|
|
2922
|
+
let cursor;
|
|
2923
|
+
let pagesFetched = 0;
|
|
2924
|
+
try {
|
|
2925
|
+
do {
|
|
2926
|
+
pagesFetched += 1;
|
|
2927
|
+
const response = await params.client.conversations.replies({
|
|
2928
|
+
channel: params.channelId,
|
|
2929
|
+
ts: params.threadTs,
|
|
2930
|
+
limit: fetchLimit,
|
|
2931
|
+
inclusive: true,
|
|
2932
|
+
...cursor ? { cursor } : {}
|
|
2933
|
+
});
|
|
2934
|
+
for (const msg of response.messages ?? []) {
|
|
2935
|
+
const text = resolveSlackMessageText(msg);
|
|
2936
|
+
if (!text && !msg.files?.length) continue;
|
|
2937
|
+
if (params.currentMessageTs && msg.ts === params.currentMessageTs) continue;
|
|
2938
|
+
retained.push([msg, text]);
|
|
2939
|
+
}
|
|
2940
|
+
if (retained.length > maxMessages) retained.splice(0, retained.length - maxMessages);
|
|
2941
|
+
const next = response.response_metadata?.next_cursor;
|
|
2942
|
+
cursor = typeof next === "string" && next.trim().length > 0 ? next.trim() : void 0;
|
|
2943
|
+
} while (cursor && pagesFetched < SLACK_THREAD_HISTORY_MAX_PAGES);
|
|
2944
|
+
if (cursor) {
|
|
2945
|
+
logVerbose$1(`slack thread history capped channel=${params.channelId} ts=${params.threadTs} pages=${SLACK_THREAD_HISTORY_MAX_PAGES}`);
|
|
2946
|
+
return [];
|
|
2947
|
+
}
|
|
2948
|
+
return retained.map(([message, text]) => ({
|
|
2949
|
+
text: text ?? formatSlackFilePlaceholder(message.files),
|
|
2950
|
+
userId: message.user,
|
|
2951
|
+
botId: message.bot_id,
|
|
2952
|
+
ts: message.ts,
|
|
2953
|
+
files: message.files
|
|
2954
|
+
}));
|
|
2955
|
+
} catch (err) {
|
|
2956
|
+
logVerbose$1(`slack thread history fetch failed channel=${params.channelId} ts=${params.threadTs}: ${formatErrorMessage(err)}`);
|
|
2957
|
+
return [];
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2960
|
+
//#endregion
|
|
2489
2961
|
//#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
2962
|
function isSlackAudioFile(file) {
|
|
2493
2963
|
if (file.subtype === "slack_audio") return true;
|
|
2494
2964
|
if ((file.mimetype?.split(";")[0]?.trim().toLowerCase())?.startsWith("audio/")) return true;
|
|
2495
2965
|
return Boolean(mimeTypeFromFilePath(file.name)?.startsWith("audio/"));
|
|
2496
2966
|
}
|
|
2967
|
+
const slackPreflightAudio = createChannelPreflightAudio({
|
|
2968
|
+
channel: "slack",
|
|
2969
|
+
isAudio: isSlackAudioFile
|
|
2970
|
+
});
|
|
2497
2971
|
function findCaptionlessSlackAudioFile(message) {
|
|
2498
2972
|
if (message.text?.trim()) return;
|
|
2499
2973
|
return message.files?.slice(0, 8).find(isSlackAudioFile);
|
|
2500
2974
|
}
|
|
2501
2975
|
function formatSlackAudioTranscriptForAgent(params) {
|
|
2502
|
-
return [
|
|
2503
|
-
}
|
|
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
|
-
};
|
|
2976
|
+
return [formatAudioTranscriptForAgent(params.transcript), params.rawBody].filter(Boolean).join("\n");
|
|
2520
2977
|
}
|
|
2521
2978
|
async function resolveSlackPreflightAudioTranscript(params) {
|
|
2522
2979
|
const mediaIndex = params.media.findIndex((entry) => entry.contentType?.toLowerCase().startsWith("audio/"));
|
|
2523
2980
|
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);
|
|
2981
|
+
const transcript = await slackPreflightAudio.resolve({ request: {
|
|
2982
|
+
ctx: {
|
|
2983
|
+
media: [...params.media],
|
|
2984
|
+
Provider: "slack",
|
|
2985
|
+
Surface: "slack",
|
|
2986
|
+
OriginatingChannel: "slack",
|
|
2987
|
+
OriginatingTo: params.originatingTo,
|
|
2988
|
+
AccountId: params.accountId,
|
|
2989
|
+
MessageThreadId: params.messageThreadId,
|
|
2990
|
+
ChatType: "channel",
|
|
2991
|
+
SessionKey: params.sessionKey
|
|
2992
|
+
},
|
|
2993
|
+
cfg: params.cfg
|
|
2994
|
+
} });
|
|
2995
|
+
return transcript ? {
|
|
2996
|
+
transcript,
|
|
2997
|
+
mediaIndex
|
|
2998
|
+
} : null;
|
|
2551
2999
|
}
|
|
2552
3000
|
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
|
-
}
|
|
3001
|
+
await slackPreflightAudio.send(params);
|
|
2572
3002
|
}
|
|
2573
3003
|
async function discardSlackPreflightMedia(media) {
|
|
2574
3004
|
await Promise.allSettled((media ?? []).map((entry) => fs.rm(entry.path, { force: true })));
|
|
@@ -2578,7 +3008,7 @@ async function discardSlackPreflightMedia(media) {
|
|
|
2578
3008
|
const SLACK_MENTION_RESOLUTION_CONCURRENCY = 4;
|
|
2579
3009
|
const SLACK_MENTION_RESOLUTION_MAX_LOOKUPS_PER_MESSAGE = 20;
|
|
2580
3010
|
const SLACK_USER_MENTION_RE$1 = /<@([A-Z0-9]+)(?:\|[^>]+)?>/gi;
|
|
2581
|
-
const loadSlackMediaModule$1 = createLazyRuntimeModule(() => import("./actions-
|
|
3011
|
+
const loadSlackMediaModule$1 = createLazyRuntimeModule(() => import("./actions-DFk0ANVB.js").then((n) => n._));
|
|
2582
3012
|
function collectUniqueSlackMentionIds$1(texts) {
|
|
2583
3013
|
const seen = /* @__PURE__ */ new Set();
|
|
2584
3014
|
const mentionIds = [];
|
|
@@ -2639,7 +3069,14 @@ async function resolveSlackMessageContent(params) {
|
|
|
2639
3069
|
const mergedMedia = [...media ?? [], ...attachmentContent?.media ?? []];
|
|
2640
3070
|
const effectiveDirectMedia = mergedMedia.length > 0 ? mergedMedia : null;
|
|
2641
3071
|
const mediaPlaceholder = effectiveDirectMedia ? effectiveDirectMedia.map((item) => item.placeholder).join(" ") : void 0;
|
|
2642
|
-
const
|
|
3072
|
+
const fallbackFileIds = /* @__PURE__ */ new Set();
|
|
3073
|
+
const fallbackFiles = [...ownFiles ?? [], ...attachmentContent?.files ?? []].filter((file) => {
|
|
3074
|
+
const fileId = normalizeOptionalString(file.id);
|
|
3075
|
+
if (!fileId) return true;
|
|
3076
|
+
if (fallbackFileIds.has(fileId)) return false;
|
|
3077
|
+
fallbackFileIds.add(fileId);
|
|
3078
|
+
return true;
|
|
3079
|
+
});
|
|
2643
3080
|
const fileOnlyFallback = !mediaPlaceholder && fallbackFiles.length > 0 ? fallbackFiles.slice(0, 8).map((file) => formatSlackFileReference(file)).join(", ") : void 0;
|
|
2644
3081
|
const fileOnlyPlaceholder = fileOnlyFallback ? `[Slack file: ${fileOnlyFallback}]` : void 0;
|
|
2645
3082
|
let botAttachmentText;
|
|
@@ -2652,7 +3089,7 @@ async function resolveSlackMessageContent(params) {
|
|
|
2652
3089
|
botAttachmentText = botAttachmentTextParts.length > 0 ? botAttachmentTextParts.join("\n") : void 0;
|
|
2653
3090
|
}
|
|
2654
3091
|
const textParts = [
|
|
2655
|
-
resolveSlackMessageText(params.message),
|
|
3092
|
+
resolveSlackMessageText$1(params.message),
|
|
2656
3093
|
attachmentContent?.text,
|
|
2657
3094
|
botAttachmentText
|
|
2658
3095
|
];
|
|
@@ -2678,13 +3115,18 @@ async function resolveSlackMessageContent(params) {
|
|
|
2678
3115
|
renderedMentions.set(result.userId, result.rendered);
|
|
2679
3116
|
}
|
|
2680
3117
|
}
|
|
2681
|
-
|
|
3118
|
+
let rawBody = [
|
|
2682
3119
|
renderSlackUserMentions(textParts[0], renderedMentions),
|
|
2683
3120
|
renderSlackUserMentions(textParts[1], renderedMentions),
|
|
2684
3121
|
renderSlackUserMentions(textParts[2], renderedMentions),
|
|
2685
3122
|
mediaPlaceholder,
|
|
2686
3123
|
fileOnlyPlaceholder
|
|
2687
3124
|
].filter(Boolean).join("\n") || "";
|
|
3125
|
+
const unavailableImageCount = attachmentContent?.unavailableImageCount ?? 0;
|
|
3126
|
+
if (unavailableImageCount > 0) rawBody = formatInboundMediaUnavailableText({
|
|
3127
|
+
body: rawBody,
|
|
3128
|
+
notice: `[slack ${unavailableImageCount > 1 ? `${unavailableImageCount} forwarded images` : "forwarded image"} unavailable]`
|
|
3129
|
+
});
|
|
2688
3130
|
if (!rawBody) return null;
|
|
2689
3131
|
return {
|
|
2690
3132
|
rawBody,
|
|
@@ -2765,68 +3207,8 @@ async function resolveSlackDmHistoryContext(params) {
|
|
|
2765
3207
|
}
|
|
2766
3208
|
//#endregion
|
|
2767
3209
|
//#region extensions/slack/src/monitor/message-handler/prepare-routing.ts
|
|
2768
|
-
const slackRouteBindingConfigCache = /* @__PURE__ */ new WeakMap();
|
|
2769
|
-
function slackTargetDefaultKindForPeer(kind) {
|
|
2770
|
-
return kind === "direct" ? "user" : "channel";
|
|
2771
|
-
}
|
|
2772
|
-
function slackTargetKindMatchesPeer(peerKind, targetKind) {
|
|
2773
|
-
if (targetKind === "user") return peerKind === "direct";
|
|
2774
|
-
return peerKind === "channel" || peerKind === "group";
|
|
2775
|
-
}
|
|
2776
|
-
function normalizeSlackRouteBindingPeer(peer) {
|
|
2777
|
-
const rawId = peer.id.trim();
|
|
2778
|
-
if (!rawId || rawId === "*") return peer;
|
|
2779
|
-
const target = (() => {
|
|
2780
|
-
try {
|
|
2781
|
-
return parseSlackTarget(rawId, { defaultKind: slackTargetDefaultKindForPeer(peer.kind) });
|
|
2782
|
-
} catch {
|
|
2783
|
-
return;
|
|
2784
|
-
}
|
|
2785
|
-
})();
|
|
2786
|
-
if (!target || !slackTargetKindMatchesPeer(peer.kind, target.kind) || target.id === peer.id) return peer;
|
|
2787
|
-
return {
|
|
2788
|
-
...peer,
|
|
2789
|
-
id: target.id
|
|
2790
|
-
};
|
|
2791
|
-
}
|
|
2792
|
-
function normalizeSlackRouteBindingConfig(cfg) {
|
|
2793
|
-
const bindings = cfg.bindings;
|
|
2794
|
-
const cached = slackRouteBindingConfigCache.get(cfg);
|
|
2795
|
-
if (cached && cached.bindingsRef === bindings) return cached.normalizedCfg;
|
|
2796
|
-
if (!Array.isArray(bindings)) return cfg;
|
|
2797
|
-
let changed = false;
|
|
2798
|
-
const normalizedBindings = bindings.map((binding) => {
|
|
2799
|
-
if (binding.type === "acp" || binding.match.channel.trim().toLowerCase() !== "slack") return binding;
|
|
2800
|
-
const peer = binding.match.peer;
|
|
2801
|
-
if (!peer) return binding;
|
|
2802
|
-
const normalizedPeer = normalizeSlackRouteBindingPeer(peer);
|
|
2803
|
-
if (normalizedPeer === peer) return binding;
|
|
2804
|
-
changed = true;
|
|
2805
|
-
return {
|
|
2806
|
-
...binding,
|
|
2807
|
-
match: {
|
|
2808
|
-
...binding.match,
|
|
2809
|
-
peer: normalizedPeer
|
|
2810
|
-
}
|
|
2811
|
-
};
|
|
2812
|
-
});
|
|
2813
|
-
const normalizedCfg = changed ? {
|
|
2814
|
-
...cfg,
|
|
2815
|
-
bindings: normalizedBindings
|
|
2816
|
-
} : cfg;
|
|
2817
|
-
slackRouteBindingConfigCache.set(cfg, {
|
|
2818
|
-
bindingsRef: bindings,
|
|
2819
|
-
normalizedCfg
|
|
2820
|
-
});
|
|
2821
|
-
return normalizedCfg;
|
|
2822
|
-
}
|
|
2823
3210
|
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)}`;
|
|
3211
|
+
return qualifySlackConversationId(params.isDirectMessage ? `user:${params.message.user ?? "unknown"}` : params.message.channel, params.eventScope);
|
|
2830
3212
|
}
|
|
2831
3213
|
function resolveSlackInitialAgentRoute(params) {
|
|
2832
3214
|
const route = resolveAgentRoute({
|
|
@@ -2836,7 +3218,7 @@ function resolveSlackInitialAgentRoute(params) {
|
|
|
2836
3218
|
teamId: params.eventScope?.teamId || params.ctx.teamId || void 0,
|
|
2837
3219
|
peer: {
|
|
2838
3220
|
kind: params.isDirectMessage ? "direct" : params.isRoom ? "channel" : "group",
|
|
2839
|
-
id:
|
|
3221
|
+
id: qualifySlackRoutePeerId({
|
|
2840
3222
|
id: params.isDirectMessage ? params.message.user ?? "unknown" : params.message.channel,
|
|
2841
3223
|
kind: params.isDirectMessage ? "user" : "channel",
|
|
2842
3224
|
eventScope: params.eventScope
|
|
@@ -2844,8 +3226,11 @@ function resolveSlackInitialAgentRoute(params) {
|
|
|
2844
3226
|
}
|
|
2845
3227
|
});
|
|
2846
3228
|
if (!params.eventScope || !params.isDirectMessage || route.dmScope !== "main") return route;
|
|
2847
|
-
const
|
|
2848
|
-
|
|
3229
|
+
const sessionKey = resolveSlackEnterpriseMainDmSessionKey({
|
|
3230
|
+
baseSessionKey: route.sessionKey,
|
|
3231
|
+
accountId: params.account.accountId,
|
|
3232
|
+
eventScope: params.eventScope
|
|
3233
|
+
});
|
|
2849
3234
|
return {
|
|
2850
3235
|
...route,
|
|
2851
3236
|
sessionKey,
|
|
@@ -2880,44 +3265,18 @@ function resolveSlackRoutingContext(params) {
|
|
|
2880
3265
|
eventScope
|
|
2881
3266
|
});
|
|
2882
3267
|
const runtimeBindingThreadId = routedThreadId ?? (isDirectMessage && isThreadReply ? threadTs : void 0);
|
|
2883
|
-
const
|
|
2884
|
-
|
|
2885
|
-
conversation: {
|
|
2886
|
-
channel: "slack",
|
|
2887
|
-
accountId: account.accountId,
|
|
2888
|
-
conversationId: runtimeBindingThreadId,
|
|
2889
|
-
parentConversationId: baseConversationId
|
|
2890
|
-
}
|
|
2891
|
-
}) : null;
|
|
2892
|
-
const runtimeRoute = eventScope ? {
|
|
2893
|
-
route,
|
|
2894
|
-
bindingRecord: null,
|
|
2895
|
-
boundSessionKey: void 0
|
|
2896
|
-
} : boundThreadRoute?.boundSessionKey || boundThreadRoute?.bindingRecord ? boundThreadRoute : resolveRuntimeConversationBindingRoute({
|
|
3268
|
+
const bindingRoute = resolveSlackConversationBindingRoute({
|
|
3269
|
+
cfg: ctx.cfg,
|
|
2897
3270
|
route,
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
}
|
|
3271
|
+
accountId: account.accountId,
|
|
3272
|
+
baseConversationId,
|
|
3273
|
+
runtimeBindingThreadId,
|
|
3274
|
+
bindingsEnabled: !eventScope
|
|
2903
3275
|
});
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
const configuredRoute = resolveConfiguredBindingRoute({
|
|
2909
|
-
cfg: ctx.cfg,
|
|
2910
|
-
route,
|
|
2911
|
-
conversation: {
|
|
2912
|
-
channel: "slack",
|
|
2913
|
-
accountId: account.accountId,
|
|
2914
|
-
conversationId: baseConversationId
|
|
2915
|
-
}
|
|
2916
|
-
});
|
|
2917
|
-
configuredBinding = configuredRoute.bindingResolution;
|
|
2918
|
-
configuredBindingSessionKey = configuredRoute.boundSessionKey ?? "";
|
|
2919
|
-
route = configuredRoute.route;
|
|
2920
|
-
}
|
|
3276
|
+
const runtimeRoute = bindingRoute.runtimeRoute;
|
|
3277
|
+
const configuredBinding = bindingRoute.configuredRoute?.bindingResolution ?? null;
|
|
3278
|
+
const configuredBindingSessionKey = bindingRoute.configuredRoute?.boundSessionKey ?? "";
|
|
3279
|
+
route = bindingRoute.route;
|
|
2921
3280
|
const threadKeys = runtimeRoute.boundSessionKey || configuredBindingSessionKey ? {
|
|
2922
3281
|
sessionKey: route.sessionKey,
|
|
2923
3282
|
parentSessionKey: void 0
|
|
@@ -2953,8 +3312,12 @@ function isSlackThreadAuthorCurrentBot(params) {
|
|
|
2953
3312
|
return false;
|
|
2954
3313
|
}
|
|
2955
3314
|
function resolveSlackThreadHistoryFilterPolicy(params) {
|
|
2956
|
-
if (
|
|
2957
|
-
|
|
3315
|
+
if (params.retainCurrentBotHistory) return { currentBot: "all" };
|
|
3316
|
+
if (!params.includeBotStarterAsRootContext || !params.starterTs) return { currentBot: "omit" };
|
|
3317
|
+
return {
|
|
3318
|
+
currentBot: "root-only",
|
|
3319
|
+
rootTs: params.starterTs
|
|
3320
|
+
};
|
|
2958
3321
|
}
|
|
2959
3322
|
function applySlackThreadHistoryFilterPolicy(params) {
|
|
2960
3323
|
const kept = [];
|
|
@@ -2967,7 +3330,7 @@ function applySlackThreadHistoryFilterPolicy(params) {
|
|
|
2967
3330
|
kept.push(entry);
|
|
2968
3331
|
continue;
|
|
2969
3332
|
}
|
|
2970
|
-
if (params.policy.
|
|
3333
|
+
if (params.policy.currentBot === "all" || params.policy.currentBot === "root-only" && entry.ts === params.policy.rootTs) kept.push(entry);
|
|
2971
3334
|
else omittedCurrentBot += 1;
|
|
2972
3335
|
}
|
|
2973
3336
|
return {
|
|
@@ -2996,11 +3359,11 @@ function formatSlackThreadLabelSnippet(text) {
|
|
|
2996
3359
|
}
|
|
2997
3360
|
//#endregion
|
|
2998
3361
|
//#region extensions/slack/src/monitor/message-handler/prepare-thread-context.ts
|
|
2999
|
-
const loadSlackMediaModule = createLazyRuntimeModule(() => import("./actions-
|
|
3362
|
+
const loadSlackMediaModule = createLazyRuntimeModule(() => import("./actions-DFk0ANVB.js").then((n) => n._));
|
|
3000
3363
|
const SLACK_THREAD_CONTEXT_USER_LOOKUP_CONCURRENCY = 4;
|
|
3001
3364
|
function resolveSlackThreadSessionFreshness(params) {
|
|
3002
3365
|
return params.ctx.channelRuntime?.session?.resolveEntryResetFreshness?.({
|
|
3003
|
-
|
|
3366
|
+
agentId: params.agentId,
|
|
3004
3367
|
storePath: params.storePath,
|
|
3005
3368
|
sessionKey: params.sessionKey,
|
|
3006
3369
|
sessionCfg: params.ctx.cfg.session,
|
|
@@ -3066,6 +3429,7 @@ async function resolveSlackThreadContextData(params) {
|
|
|
3066
3429
|
let threadStarterMedia = null;
|
|
3067
3430
|
const threadSessionFreshness = params.isThreadReply && params.threadTs ? resolveSlackThreadSessionFreshness({
|
|
3068
3431
|
ctx: params.ctx,
|
|
3432
|
+
agentId: params.agentId,
|
|
3069
3433
|
storePath: params.storePath,
|
|
3070
3434
|
sessionKey: params.sessionKey
|
|
3071
3435
|
}) : void 0;
|
|
@@ -3073,7 +3437,9 @@ async function resolveSlackThreadContextData(params) {
|
|
|
3073
3437
|
storePath: params.storePath,
|
|
3074
3438
|
sessionKey: params.sessionKey
|
|
3075
3439
|
}) : void 0;
|
|
3076
|
-
const
|
|
3440
|
+
const isMissingThreadSession = threadSessionFreshness ? threadSessionFreshness.state === "missing" : threadSessionPreviousTimestamp === void 0;
|
|
3441
|
+
const isOutboundOnlyThreadSession = threadSessionFreshness !== void 0 && threadSessionFreshness.state !== "missing" && threadSessionFreshness.entry.lastInteractionAt === void 0 && threadSessionFreshness.entry.updatedAt !== 0;
|
|
3442
|
+
const shouldSeedInitialThreadContext = Boolean(params.isThreadReply && params.threadTs && (threadSessionFreshness ? threadSessionFreshness.state !== "fresh" || isOutboundOnlyThreadSession : threadSessionPreviousTimestamp === void 0));
|
|
3077
3443
|
const shouldLoadInitialThreadHistory = shouldSeedInitialThreadContext || params.forceInitialHistory === true;
|
|
3078
3444
|
if (!params.isThreadReply || !params.threadTs) return {
|
|
3079
3445
|
threadStarterBody,
|
|
@@ -3151,7 +3517,8 @@ async function resolveSlackThreadContextData(params) {
|
|
|
3151
3517
|
history: threadHistoryWithBotRoot,
|
|
3152
3518
|
policy: resolveSlackThreadHistoryFilterPolicy({
|
|
3153
3519
|
includeBotStarterAsRootContext,
|
|
3154
|
-
starterTs: currentBotRootTs
|
|
3520
|
+
starterTs: currentBotRootTs,
|
|
3521
|
+
retainCurrentBotHistory: params.isGroupDm && (isMissingThreadSession || isOutboundOnlyThreadSession)
|
|
3155
3522
|
}),
|
|
3156
3523
|
identity: botIdentity
|
|
3157
3524
|
});
|
|
@@ -3298,6 +3665,13 @@ const SLACK_HISTORY_MEDIA_MAX_ATTACHMENTS = 4;
|
|
|
3298
3665
|
const SLACK_HISTORY_MEDIA_MAX_BYTES = 10 * 1024 * 1024;
|
|
3299
3666
|
const SLACK_HISTORY_MEDIA_IDLE_TIMEOUT_MS = 1e3;
|
|
3300
3667
|
const SLACK_HISTORY_MEDIA_TOTAL_TIMEOUT_MS = 3e3;
|
|
3668
|
+
const SLACK_CHANNEL_ACCESS_DOCS_URL = "https://docs.openclaw.ai/channels/slack#access-control-and-routing";
|
|
3669
|
+
function resolveSlackGroupSessionSubject(params) {
|
|
3670
|
+
const channelName = normalizeOptionalString(params.channelName);
|
|
3671
|
+
const workspaceName = normalizeOptionalString(params.workspaceName);
|
|
3672
|
+
if (channelName && workspaceName) return `${workspaceName} #${channelName}`;
|
|
3673
|
+
return `Slack Channel (Workspace ID: ${params.workspaceId}, Channel ID: ${params.channelId})`;
|
|
3674
|
+
}
|
|
3301
3675
|
function recordString(record, key) {
|
|
3302
3676
|
return normalizeOptionalString(record?.[key]);
|
|
3303
3677
|
}
|
|
@@ -3504,6 +3878,8 @@ async function resolveSlackConversationContext(params) {
|
|
|
3504
3878
|
const isRoom = resolvedChannelType === "channel" || resolvedChannelType === "group";
|
|
3505
3879
|
const isRoomish = isRoom || isGroupDm;
|
|
3506
3880
|
const channelConfig = isRoom ? resolveSlackChannelConfig({
|
|
3881
|
+
teamId: params.eventScope?.teamId ?? ctx.teamId,
|
|
3882
|
+
allowUnscoped: ctx.installationIdentity?.kind !== "enterprise",
|
|
3507
3883
|
channelId: message.channel,
|
|
3508
3884
|
channelName,
|
|
3509
3885
|
channels: ctx.channelsConfig,
|
|
@@ -3545,14 +3921,38 @@ async function authorizeSlackInboundMessage(params) {
|
|
|
3545
3921
|
return null;
|
|
3546
3922
|
}
|
|
3547
3923
|
if (!ctx.isChannelAllowed({
|
|
3924
|
+
teamId: params.eventScope?.teamId ?? ctx.teamId,
|
|
3548
3925
|
channelId: message.channel,
|
|
3549
3926
|
channelName,
|
|
3550
3927
|
channelType: resolvedChannelType
|
|
3551
3928
|
})) {
|
|
3929
|
+
if (conversation.isRoom && ctx.groupPolicy === "allowlist" && params.explicitBotMention && !isBotMessage && message.user) {
|
|
3930
|
+
let subject = "This OpenClaw bot";
|
|
3931
|
+
if (ctx.botUserId) try {
|
|
3932
|
+
const botName = normalizeOptionalString((await ctx.resolveUserName(ctx.botUserId, params.eventScope))?.name);
|
|
3933
|
+
if (botName) subject = escapeSlackMrkdwn(botName);
|
|
3934
|
+
} catch (error) {
|
|
3935
|
+
logVerbose(`slack allowlist denial bot-name lookup failed: ${formatSlackError(error)}`);
|
|
3936
|
+
}
|
|
3937
|
+
try {
|
|
3938
|
+
await (params.eventScope?.client ?? ctx.app.client).chat.postEphemeral({
|
|
3939
|
+
token: ctx.botToken,
|
|
3940
|
+
channel: message.channel,
|
|
3941
|
+
user: message.user,
|
|
3942
|
+
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.>`
|
|
3943
|
+
});
|
|
3944
|
+
params.onVisibleDrop?.();
|
|
3945
|
+
} catch (error) {
|
|
3946
|
+
ctx.runtime.error?.(`slack allowlist denial notice failed for channel ${message.channel}: ${formatSlackError(error)}`);
|
|
3947
|
+
}
|
|
3948
|
+
}
|
|
3552
3949
|
logVerbose("slack: drop message (channel not allowed)");
|
|
3553
3950
|
return null;
|
|
3554
3951
|
}
|
|
3555
|
-
const allowFromLower = await resolveSlackEffectiveAllowFrom(ctx, {
|
|
3952
|
+
const allowFromLower = await resolveSlackEffectiveAllowFrom(ctx, {
|
|
3953
|
+
includePairingStore: isDirectMessage,
|
|
3954
|
+
eventScope: params.eventScope
|
|
3955
|
+
});
|
|
3556
3956
|
if (isDirectMessage) {
|
|
3557
3957
|
const directUserId = message.user;
|
|
3558
3958
|
if (!directUserId) {
|
|
@@ -3563,6 +3963,7 @@ async function authorizeSlackInboundMessage(params) {
|
|
|
3563
3963
|
ctx,
|
|
3564
3964
|
accountId: account.accountId,
|
|
3565
3965
|
senderId: directUserId,
|
|
3966
|
+
eventScope: params.eventScope,
|
|
3566
3967
|
allowFromLower,
|
|
3567
3968
|
resolveSenderName: (userId) => ctx.resolveUserName(userId, params.eventScope),
|
|
3568
3969
|
sendPairingReply: async (text) => {
|
|
@@ -3570,7 +3971,8 @@ async function authorizeSlackInboundMessage(params) {
|
|
|
3570
3971
|
cfg: ctx.cfg,
|
|
3571
3972
|
token: ctx.botToken,
|
|
3572
3973
|
client: params.eventScope?.client ?? ctx.app.client,
|
|
3573
|
-
accountId: account.accountId
|
|
3974
|
+
accountId: account.accountId,
|
|
3975
|
+
eventScope: params.eventScope
|
|
3574
3976
|
});
|
|
3575
3977
|
},
|
|
3576
3978
|
onDisabled: () => {
|
|
@@ -3590,10 +3992,10 @@ async function authorizeSlackInboundMessage(params) {
|
|
|
3590
3992
|
async function prepareSlackMessage(params) {
|
|
3591
3993
|
const { ctx, account, message, opts } = params;
|
|
3592
3994
|
const slackClient = opts.eventScope?.client ?? ctx.app.client;
|
|
3593
|
-
const threadStarterWorkspaceScope =
|
|
3995
|
+
const threadStarterWorkspaceScope = {
|
|
3594
3996
|
accountId: account.accountId,
|
|
3595
|
-
teamId: opts.eventScope.teamId
|
|
3596
|
-
}
|
|
3997
|
+
teamId: opts.eventScope?.teamId ?? ctx.teamId
|
|
3998
|
+
};
|
|
3597
3999
|
const cfg = ctx.cfg;
|
|
3598
4000
|
const conversation = await resolveSlackConversationContext({
|
|
3599
4001
|
ctx,
|
|
@@ -3602,16 +4004,20 @@ async function prepareSlackMessage(params) {
|
|
|
3602
4004
|
eventScope: opts.eventScope
|
|
3603
4005
|
});
|
|
3604
4006
|
const { channelInfo, channelName, isDirectMessage, isGroupDm, isRoom, isRoomish, channelConfig, allowBotsMode, isBotMessage } = conversation;
|
|
4007
|
+
const messageText = message.text ?? "";
|
|
4008
|
+
const mentionMetadata = collectSlackMentionMetadata(messageText);
|
|
4009
|
+
const normalizedBotUserId = normalizeSlackId(ctx.botUserId);
|
|
3605
4010
|
const authorization = await authorizeSlackInboundMessage({
|
|
3606
4011
|
ctx,
|
|
3607
4012
|
account,
|
|
3608
4013
|
message,
|
|
3609
4014
|
conversation,
|
|
3610
|
-
|
|
4015
|
+
explicitBotMention: opts.source === "app_mention" || Boolean(normalizedBotUserId && mentionMetadata.mentionedUserIds.includes(normalizedBotUserId)),
|
|
4016
|
+
eventScope: opts.eventScope,
|
|
4017
|
+
onVisibleDrop: opts.onVisibleDrop
|
|
3611
4018
|
});
|
|
3612
4019
|
if (!authorization) return null;
|
|
3613
4020
|
const { senderId, allowFromLower } = authorization;
|
|
3614
|
-
const messageText = message.text ?? "";
|
|
3615
4021
|
let resolvedSenderName = normalizeOptionalString(message.username);
|
|
3616
4022
|
const resolveSenderName = async () => {
|
|
3617
4023
|
if (resolvedSenderName) return resolvedSenderName;
|
|
@@ -3625,7 +4031,6 @@ async function prepareSlackMessage(params) {
|
|
|
3625
4031
|
resolvedSenderName = message.user ?? message.bot_id ?? "unknown";
|
|
3626
4032
|
return resolvedSenderName;
|
|
3627
4033
|
};
|
|
3628
|
-
const mentionMetadata = collectSlackMentionMetadata(messageText);
|
|
3629
4034
|
const { mentionedUserIds, mentionedSubteamIds, hasAnyMention } = mentionMetadata;
|
|
3630
4035
|
const messageAssistantThreadContext = resolveSlackMessageAssistantThreadContext(message);
|
|
3631
4036
|
const assistantContextLookupChannelId = messageAssistantThreadContext?.assistantChannelId ?? message.channel;
|
|
@@ -3688,7 +4093,11 @@ async function prepareSlackMessage(params) {
|
|
|
3688
4093
|
}));
|
|
3689
4094
|
const buildPolicyMentionRegexes = (agentId) => resolveCachedMentionRegexes(ctx, agentId, {
|
|
3690
4095
|
provider: "slack",
|
|
3691
|
-
conversationId:
|
|
4096
|
+
conversationId: formatSlackTarget({
|
|
4097
|
+
teamId: opts.eventScope?.teamId,
|
|
4098
|
+
kind: "channel",
|
|
4099
|
+
id: message.channel
|
|
4100
|
+
}),
|
|
3692
4101
|
providerPolicy: account.config.mentionPatterns
|
|
3693
4102
|
});
|
|
3694
4103
|
let mentionRegexes = buildPolicyMentionRegexes(routing.route.agentId);
|
|
@@ -3729,13 +4138,13 @@ async function prepareSlackMessage(params) {
|
|
|
3729
4138
|
name: void 0
|
|
3730
4139
|
});
|
|
3731
4140
|
let implicitMentionKinds = [];
|
|
3732
|
-
if (!isDirectMessage &&
|
|
3733
|
-
const replyToBotKinds = implicitMentionKindWhen("reply_to_bot", message.parent_user_id === ctx.botUserId);
|
|
4141
|
+
if (!isDirectMessage && message.thread_ts && !wasMentioned) {
|
|
4142
|
+
const replyToBotKinds = implicitMentionKindWhen("reply_to_bot", Boolean(ctx.botUserId && message.parent_user_id === ctx.botUserId));
|
|
3734
4143
|
implicitMentionKinds = replyToBotKinds.length > 0 ? replyToBotKinds : implicitMentionKindWhen("bot_thread_participant", await hasSlackThreadParticipationWithPersistence({
|
|
3735
4144
|
accountId: account.accountId,
|
|
3736
4145
|
channelId: message.channel,
|
|
3737
4146
|
threadTs: message.thread_ts,
|
|
3738
|
-
|
|
4147
|
+
teamId: opts.eventScope?.teamId
|
|
3739
4148
|
}));
|
|
3740
4149
|
}
|
|
3741
4150
|
const recordDroppedHistory = async (reason) => {
|
|
@@ -3843,12 +4252,14 @@ async function prepareSlackMessage(params) {
|
|
|
3843
4252
|
const hasControlCommandInMessage = hasControlCommand(textForCommandDetection, cfg);
|
|
3844
4253
|
const hasAbortRequest = isAbortRequestText(textForCommandDetection);
|
|
3845
4254
|
const channelUsersAllowlistConfigured = isRoom && Array.isArray(channelConfig?.users) && channelConfig.users.length > 0;
|
|
3846
|
-
const
|
|
4255
|
+
const resolveMessageIngress = async (contextBinding, threadId) => await resolveSlackCommandIngress({
|
|
3847
4256
|
ctx,
|
|
4257
|
+
teamId: opts.eventScope?.teamId ?? ctx.teamId,
|
|
3848
4258
|
senderId,
|
|
3849
4259
|
senderName: senderNameForAuth,
|
|
3850
4260
|
channelType: conversation.resolvedChannelType ?? "channel",
|
|
3851
4261
|
channelId: message.channel,
|
|
4262
|
+
threadId,
|
|
3852
4263
|
ownerAllowFromLower: allowFromLower,
|
|
3853
4264
|
channelUsers: isRoom ? channelConfig?.users : void 0,
|
|
3854
4265
|
allowTextCommands,
|
|
@@ -3863,8 +4274,10 @@ async function prepareSlackMessage(params) {
|
|
|
3863
4274
|
requireMention: shouldRequireMention,
|
|
3864
4275
|
allowTextCommands,
|
|
3865
4276
|
implicitMentions
|
|
3866
|
-
}
|
|
4277
|
+
},
|
|
4278
|
+
contextBinding
|
|
3867
4279
|
});
|
|
4280
|
+
let messageIngress = await resolveMessageIngress();
|
|
3868
4281
|
const senderGate = messageIngress.senderAccess.gate;
|
|
3869
4282
|
if (isRoomish && senderGate?.allowed === false) {
|
|
3870
4283
|
logVerbose(`Blocked unauthorized slack sender ${senderId} (not in sender allowlist)`);
|
|
@@ -3885,6 +4298,18 @@ async function prepareSlackMessage(params) {
|
|
|
3885
4298
|
channel: "slack",
|
|
3886
4299
|
accountId: account.accountId
|
|
3887
4300
|
});
|
|
4301
|
+
const preflightChannelTarget = formatSlackTarget({
|
|
4302
|
+
teamId: opts.eventScope?.teamId,
|
|
4303
|
+
kind: "channel",
|
|
4304
|
+
id: message.channel,
|
|
4305
|
+
explicitKind: true
|
|
4306
|
+
});
|
|
4307
|
+
const replyRouteTarget = formatSlackTarget({
|
|
4308
|
+
teamId: opts.eventScope?.teamId,
|
|
4309
|
+
kind: isDirectMessage ? "user" : "channel",
|
|
4310
|
+
id: isDirectMessage ? senderId : message.channel,
|
|
4311
|
+
explicitKind: true
|
|
4312
|
+
});
|
|
3888
4313
|
const commandAuthorized = messageIngress.commandAccess.authorized;
|
|
3889
4314
|
if (isRoomish && messageIngress.commandAccess.shouldBlockControlCommand) {
|
|
3890
4315
|
logInboundDrop({
|
|
@@ -3931,7 +4356,7 @@ async function prepareSlackMessage(params) {
|
|
|
3931
4356
|
media: preflightMedia,
|
|
3932
4357
|
cfg,
|
|
3933
4358
|
accountId: account.accountId,
|
|
3934
|
-
originatingTo:
|
|
4359
|
+
originatingTo: preflightChannelTarget,
|
|
3935
4360
|
sessionKey: preflightRouting.sessionKey,
|
|
3936
4361
|
messageThreadId: preflightRouting.threadContext.messageThreadId
|
|
3937
4362
|
}) : null;
|
|
@@ -4076,14 +4501,23 @@ async function prepareSlackMessage(params) {
|
|
|
4076
4501
|
return false;
|
|
4077
4502
|
}) : statusReactionsWillHandle ? Promise.resolve(true) : null;
|
|
4078
4503
|
const roomLabel = channelName ? `#${channelName}` : `#${message.channel}`;
|
|
4504
|
+
const workspaceId = opts.eventScope?.teamId || ctx.teamId;
|
|
4505
|
+
const workspaceName = ctx.installationIdentity?.kind === "workspace" && ctx.installationIdentity.teamId === workspaceId ? ctx.installationIdentity.teamName : void 0;
|
|
4506
|
+
const groupSessionSubject = isRoomish ? resolveSlackGroupSessionSubject({
|
|
4507
|
+
channelId: message.channel,
|
|
4508
|
+
channelName,
|
|
4509
|
+
workspaceId,
|
|
4510
|
+
workspaceName
|
|
4511
|
+
}) : void 0;
|
|
4079
4512
|
const senderName = await resolveSenderName();
|
|
4513
|
+
const conversationAvatar = isDirectMessage && message.user ? ctx.resolveUserAvatar(message.user, opts.eventScope) : void 0;
|
|
4080
4514
|
const preview = truncateUtf16Safe(bodyForAgent.replace(/\s+/g, " "), 160);
|
|
4081
4515
|
const inboundLabel = isDirectMessage ? `Slack DM from ${senderName}` : `Slack message in ${roomLabel} from ${senderName}`;
|
|
4082
4516
|
const slackFrom = isDirectMessage ? `slack:${message.user}` : isRoom ? `slack:channel:${message.channel}` : `slack:group:${message.channel}`;
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
});
|
|
4517
|
+
enqueueRoutedSystemEvent(inboundLabel, {
|
|
4518
|
+
...route,
|
|
4519
|
+
sessionKey
|
|
4520
|
+
}, { contextKey: `slack:message:${message.channel}:${message.ts ?? "unknown"}` });
|
|
4087
4521
|
const envelopeFrom = resolveConversationLabel$1({
|
|
4088
4522
|
ChatType: chatType,
|
|
4089
4523
|
SenderName: senderName,
|
|
@@ -4147,7 +4581,6 @@ async function prepareSlackMessage(params) {
|
|
|
4147
4581
|
envelope: envelopeOptions
|
|
4148
4582
|
})
|
|
4149
4583
|
});
|
|
4150
|
-
const slackTo = isDirectMessage ? `user:${message.user}` : `channel:${message.channel}`;
|
|
4151
4584
|
const { channelMetadata, groupSystemPrompt } = resolveSlackRoomContextHints({
|
|
4152
4585
|
isRoomish,
|
|
4153
4586
|
channelInfo,
|
|
@@ -4155,8 +4588,10 @@ async function prepareSlackMessage(params) {
|
|
|
4155
4588
|
});
|
|
4156
4589
|
const { threadStarterBody, threadHistoryBody, shouldSeedInitialThreadContext, threadLabel, threadStarterMedia } = await resolveSlackThreadContextData({
|
|
4157
4590
|
ctx,
|
|
4591
|
+
agentId: route.agentId,
|
|
4158
4592
|
account,
|
|
4159
4593
|
message,
|
|
4594
|
+
isGroupDm,
|
|
4160
4595
|
isThreadReply,
|
|
4161
4596
|
threadTs,
|
|
4162
4597
|
threadStarter,
|
|
@@ -4180,8 +4615,20 @@ async function prepareSlackMessage(params) {
|
|
|
4180
4615
|
const commandBody = textForCommandDetection.trim();
|
|
4181
4616
|
const supplementalThreadHistoryBody = directThreadRoutedToDmSession && !threadHistoryBody ? threadStarterBody : threadHistoryBody;
|
|
4182
4617
|
const effectiveMessageThreadId = assistantThreadContext?.threadTs ?? agentViewThreadTs ?? threadContext.messageThreadId;
|
|
4618
|
+
const boundMessageThreadId = directThreadRoutedToDmSession ? void 0 : effectiveMessageThreadId;
|
|
4619
|
+
messageIngress = await resolveMessageIngress({
|
|
4620
|
+
agentId: route.agentId,
|
|
4621
|
+
sessionKey,
|
|
4622
|
+
messageId: threadContext.messageTs,
|
|
4623
|
+
inboundEventKind
|
|
4624
|
+
}, boundMessageThreadId);
|
|
4625
|
+
if (messageIngress.ingress.admission !== "dispatch") {
|
|
4626
|
+
logVerbose(`Blocked slack sender ${senderId} after final route binding`);
|
|
4627
|
+
return null;
|
|
4628
|
+
}
|
|
4183
4629
|
const agentContextEntities = isAgentViewMessage ? normalizeSlackAppContextEntities(message.app_context) : [];
|
|
4184
|
-
const ctxPayload = buildChannelInboundEventContext({
|
|
4630
|
+
const ctxPayload = (ctx.buildContext ?? buildChannelInboundEventContext)({
|
|
4631
|
+
channelIngress: messageIngress,
|
|
4185
4632
|
channel: "slack",
|
|
4186
4633
|
accountId: route.accountId,
|
|
4187
4634
|
messageId: threadContext.messageTs,
|
|
@@ -4196,22 +4643,32 @@ async function prepareSlackMessage(params) {
|
|
|
4196
4643
|
conversation: {
|
|
4197
4644
|
kind: chatType,
|
|
4198
4645
|
id: message.channel,
|
|
4646
|
+
routePeer: {
|
|
4647
|
+
kind: chatType,
|
|
4648
|
+
id: qualifySlackRoutePeerId({
|
|
4649
|
+
id: isDirectMessage ? message.user ?? "unknown" : message.channel,
|
|
4650
|
+
kind: isDirectMessage ? "user" : "channel",
|
|
4651
|
+
eventScope: opts.eventScope
|
|
4652
|
+
})
|
|
4653
|
+
},
|
|
4199
4654
|
label: envelopeFrom,
|
|
4200
4655
|
spaceId: opts.eventScope?.teamId || ctx.teamId || void 0,
|
|
4201
|
-
threadId:
|
|
4202
|
-
nativeChannelId: message.channel
|
|
4656
|
+
threadId: boundMessageThreadId,
|
|
4657
|
+
nativeChannelId: message.channel,
|
|
4658
|
+
avatar: conversationAvatar
|
|
4203
4659
|
},
|
|
4204
4660
|
route: {
|
|
4205
4661
|
agentId: route.agentId,
|
|
4206
4662
|
dmScope: route.dmScope,
|
|
4207
4663
|
accountId: route.accountId,
|
|
4208
|
-
routeSessionKey: sessionKey,
|
|
4664
|
+
routeSessionKey: route.sessionKey,
|
|
4665
|
+
dispatchSessionKey: sessionKey,
|
|
4209
4666
|
parentSessionKey: threadKeys.parentSessionKey
|
|
4210
4667
|
},
|
|
4211
4668
|
reply: {
|
|
4212
|
-
to:
|
|
4669
|
+
to: replyRouteTarget,
|
|
4213
4670
|
replyToId: threadContext.replyToId,
|
|
4214
|
-
messageThreadId:
|
|
4671
|
+
messageThreadId: boundMessageThreadId,
|
|
4215
4672
|
nativeChannelId: message.channel
|
|
4216
4673
|
},
|
|
4217
4674
|
message: {
|
|
@@ -4244,7 +4701,7 @@ async function prepareSlackMessage(params) {
|
|
|
4244
4701
|
groupSystemPrompt
|
|
4245
4702
|
},
|
|
4246
4703
|
extra: {
|
|
4247
|
-
GroupSubject:
|
|
4704
|
+
GroupSubject: groupSessionSubject,
|
|
4248
4705
|
ChannelPromptContext: channelMetadata ? [channelMetadata] : void 0,
|
|
4249
4706
|
ChannelStructuredContext: agentContextEntities.length > 0 ? [{
|
|
4250
4707
|
label: "Slack active context",
|
|
@@ -4284,16 +4741,16 @@ async function prepareSlackMessage(params) {
|
|
|
4284
4741
|
});
|
|
4285
4742
|
const pinnedMainDmOwner = isDirectMessage ? resolvePinnedMainDmOwnerFromAllowlist({
|
|
4286
4743
|
dmScope: cfg.session?.dmScope,
|
|
4287
|
-
allowFrom:
|
|
4744
|
+
allowFrom: allowFromLower,
|
|
4288
4745
|
normalizeEntry: normalizeSlackAllowOwnerEntry
|
|
4289
4746
|
}) : null;
|
|
4290
|
-
const replyTarget =
|
|
4747
|
+
const replyTarget = `channel:${message.channel}`;
|
|
4291
4748
|
if (!replyTarget) return null;
|
|
4292
4749
|
if (preflightAudioTranscript) await sendSlackPreflightAudioTranscriptEcho({
|
|
4293
4750
|
transcript: preflightAudioTranscript,
|
|
4294
4751
|
cfg,
|
|
4295
4752
|
accountId: account.accountId,
|
|
4296
|
-
originatingTo:
|
|
4753
|
+
originatingTo: preflightChannelTarget,
|
|
4297
4754
|
messageThreadId: threadContext.messageThreadId
|
|
4298
4755
|
});
|
|
4299
4756
|
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 +4763,7 @@ async function prepareSlackMessage(params) {
|
|
|
4306
4763
|
account,
|
|
4307
4764
|
message,
|
|
4308
4765
|
...opts.relayIdentity ? { relayIdentity: opts.relayIdentity } : {},
|
|
4309
|
-
|
|
4766
|
+
eventScope: opts.eventScope,
|
|
4310
4767
|
route,
|
|
4311
4768
|
channelConfig,
|
|
4312
4769
|
replyTarget,
|
|
@@ -4314,13 +4771,13 @@ async function prepareSlackMessage(params) {
|
|
|
4314
4771
|
turn: {
|
|
4315
4772
|
storePath,
|
|
4316
4773
|
record: {
|
|
4317
|
-
updateLastRoute: isDirectMessage ? {
|
|
4774
|
+
updateLastRoute: isDirectMessage || opts.eventScope ? {
|
|
4318
4775
|
sessionKey: updateLastRouteSessionKey,
|
|
4319
4776
|
channel: "slack",
|
|
4320
|
-
to:
|
|
4777
|
+
to: replyRouteTarget,
|
|
4321
4778
|
accountId: route.accountId,
|
|
4322
4779
|
threadId: effectiveMessageThreadId,
|
|
4323
|
-
mainDmOwnerPin: updateLastRouteSessionKey === route.mainSessionKey && pinnedMainDmOwner && message.user ? {
|
|
4780
|
+
mainDmOwnerPin: isDirectMessage && updateLastRouteSessionKey === route.mainSessionKey && pinnedMainDmOwner && message.user ? {
|
|
4324
4781
|
ownerRecipient: pinnedMainDmOwner,
|
|
4325
4782
|
senderRecipient: normalizeLowercaseStringOrEmpty(message.user),
|
|
4326
4783
|
onSkip: ({ ownerRecipient, senderRecipient }) => {
|