@openclaw/slack 2026.5.27 → 2026.5.28-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{account-inspect-vVg3pT03.js → account-inspect-CdGk6R7l.js} +1 -1
- package/dist/account-inspect-api.js +1 -1
- package/dist/{accounts-BnLQ3fe2.js → accounts-f6Xcv9Vi.js} +42 -1
- package/dist/accounts.runtime-BJt1IxOS.js +2 -0
- package/dist/{action-runtime-JVb7KyQs.js → action-runtime-BOEgcnv6.js} +7 -8
- package/dist/action-runtime.runtime-BXQYV0yA.js +2 -0
- package/dist/{actions-1o9nMIY8.js → actions-zfVWcIY6.js} +4 -4
- package/dist/{actions.runtime-qQtdNww-.js → actions.runtime-CoijPN8g.js} +1 -1
- package/dist/api.js +14 -17
- package/dist/{approval-handler.runtime-Ba8nwnYi.js → approval-handler.runtime-CWz3XLfN.js} +12 -69
- package/dist/{channel-Bd7eept6.js → channel-D8p_1twn.js} +25 -28
- package/dist/channel-config-api.js +1 -1
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.setup-WNNVmCm0.js → channel.setup-oGp4gSTP.js} +4 -4
- package/dist/{client-Dc2Ji2YN.js → client-DowBk5k0.js} +6 -24
- package/dist/{config-schema-CUiDK8HV.js → config-schema-C0RewpJQ.js} +4 -0
- package/dist/contract-api.js +1 -1
- package/dist/{directory-config-9_djLTOK.js → directory-config-8UPAEyNg.js} +1 -1
- package/dist/directory-contract-api.js +1 -1
- package/dist/{directory-live-Cl83cGLZ.js → directory-live-BFB1pSax.js} +2 -2
- package/dist/http-routes-api.js +1 -1
- package/dist/index.js +3 -7
- package/dist/{interactive-replies-DtYu4Sf5.js → interactive-replies-DrBq4Mld.js} +1 -1
- package/dist/interactive-replies-api.js +1 -1
- package/dist/{message-tool-api-BieXX5lk.js → message-tool-api-B9M0zzlQ.js} +2 -2
- package/dist/message-tool-api.js +1 -1
- package/dist/{monitor-Cn7LUDFL.js → monitor-D7jGKmQk.js} +3 -4
- package/dist/{outbound-adapter-ChuR4_0v.js → outbound-adapter-BHZMgblN.js} +4 -7
- package/dist/pipeline.runtime-xM6ppqQZ.js +3492 -0
- package/dist/plugin-routes-B9PvcDQJ.js +22 -0
- package/dist/{policy-pu8tsF8_.js → policy-BBDU-PQK.js} +1 -1
- package/dist/{probe-cBVPhCKZ.js → probe-Djes9Fy6.js} +1 -1
- package/dist/{provider-Bccng2Wp.js → provider-CxMP_s2o.js} +828 -28
- package/dist/{replies-jiEDV6lH.js → replies-DkmWK7JW.js} +2 -4
- package/dist/{resolve-channels-CT4oiz_9.js → resolve-channels-zXt5f47h.js} +1 -1
- package/dist/{resolve-users-o5S-Ijks.js → resolve-users-BLfGAz1v.js} +1 -1
- package/dist/{runtime-api-CyPE-EvY.js → runtime-api-DvpUD2hw.js} +2 -2
- package/dist/runtime-api.js +12 -12
- package/dist/{scopes-CnyhLIPT.js → scopes-DiiHsqh1.js} +1 -1
- package/dist/{send-DvfE8LVm.js → send-BURYyCXI.js} +4 -4
- package/dist/send.runtime-CKaMG3s-.js +2 -0
- package/dist/{setup-core-DKe7Ug3V.js → setup-core-POfI_bgP.js} +2 -2
- package/dist/setup-entry.js +8 -1
- package/dist/setup-plugin-api.js +1 -1
- package/dist/{setup-surface-gjRthuZA.js → setup-surface-DJTHAguz.js} +5 -5
- package/dist/{shared-D8A7iVVH.js → shared-D9WMYymo.js} +5 -5
- package/dist/{slash-dispatch.runtime-BBhdJHb8.js → slash-dispatch.runtime-lsyTm_q5.js} +1 -1
- package/dist/thread-ts-NSVqWybn.js +646 -0
- package/node_modules/semver/classes/range.js +7 -0
- package/node_modules/semver/package.json +1 -1
- package/node_modules/semver/ranges/subset.js +2 -2
- package/npm-shrinkwrap.json +6 -29
- package/openclaw.plugin.json +10 -0
- package/package.json +8 -7
- package/dist/accounts.runtime-BVdtQeuq.js +0 -2
- package/dist/action-runtime.runtime-BZa5VDjs.js +0 -2
- package/dist/allow-list-B1lkGjwl.js +0 -82
- package/dist/blocks-render-CNC4vQnd.js +0 -232
- package/dist/inbound-contract-test-api.js +0 -3
- package/dist/magic-string.es-BPXBBMwL.js +0 -1011
- package/dist/outbound-payload-test-api.js +0 -2
- package/dist/outbound-payload.test-harness-8MnHFgR1.js +0 -13551
- package/dist/pipeline.runtime-BVK8yXw_.js +0 -1473
- package/dist/plugin-routes-DRR3ijKM.js +0 -20
- package/dist/prepare-Bl5WcC3f.js +0 -1713
- package/dist/prepare.test-helpers-BcAo4KMw.js +0 -49
- package/dist/reply-blocks-BlOURkUm.js +0 -290
- package/dist/room-context-BmNTBiw5.js +0 -816
- package/dist/send.runtime-Dv6ajTGK.js +0 -2
- package/dist/send.runtime-v3TSw9xY.js +0 -2
- package/dist/test-api.js +0 -8
- package/dist/thread-ts-ks-O8cEG.js +0 -52
- package/node_modules/agent-base/LICENSE +0 -22
- package/node_modules/agent-base/README.md +0 -69
- package/node_modules/agent-base/dist/helpers.d.ts +0 -10
- package/node_modules/agent-base/dist/helpers.d.ts.map +0 -1
- package/node_modules/agent-base/dist/helpers.js +0 -37
- package/node_modules/agent-base/dist/helpers.js.map +0 -1
- package/node_modules/agent-base/dist/index.d.ts +0 -37
- package/node_modules/agent-base/dist/index.d.ts.map +0 -1
- package/node_modules/agent-base/dist/index.js +0 -146
- package/node_modules/agent-base/dist/index.js.map +0 -1
- package/node_modules/agent-base/package.json +0 -46
- package/node_modules/https-proxy-agent/LICENSE +0 -22
- package/node_modules/https-proxy-agent/README.md +0 -70
- package/node_modules/https-proxy-agent/dist/index.d.ts +0 -43
- package/node_modules/https-proxy-agent/dist/index.d.ts.map +0 -1
- package/node_modules/https-proxy-agent/dist/index.js +0 -150
- package/node_modules/https-proxy-agent/dist/index.js.map +0 -1
- package/node_modules/https-proxy-agent/dist/parse-proxy-response.d.ts +0 -12
- package/node_modules/https-proxy-agent/dist/parse-proxy-response.d.ts.map +0 -1
- package/node_modules/https-proxy-agent/dist/parse-proxy-response.js +0 -94
- package/node_modules/https-proxy-agent/dist/parse-proxy-response.js.map +0 -1
- package/node_modules/https-proxy-agent/package.json +0 -50
|
@@ -1,1473 +0,0 @@
|
|
|
1
|
-
import { o as SLACK_TEXT_LIMIT, s as truncateSlackText } from "./thread-ts-ks-O8cEG.js";
|
|
2
|
-
import { n as isSlackInteractiveRepliesEnabled, t as compileSlackInteractiveReplies } from "./interactive-replies-DtYu4Sf5.js";
|
|
3
|
-
import { i as normalizeSlackAllowOwnerEntry } from "./allow-list-B1lkGjwl.js";
|
|
4
|
-
import { n as resolveSlackNativeStreaming, r as resolveSlackStreamingMode, t as mapStreamingModeToSlackLegacyDraftStreamMode } from "./streaming-compat-DjlgH-Be.js";
|
|
5
|
-
import { a as recordSlackThreadParticipation, s as normalizeSlackOutboundText, t as sendMessageSlack } from "./send-DvfE8LVm.js";
|
|
6
|
-
import { b as buildSlackEditTextPayload, f as removeSlackReaction, l as reactSlackMessage, r as editSlackMessage, t as deleteSlackMessage } from "./actions-1o9nMIY8.js";
|
|
7
|
-
import { t as formatSlackError } from "./errors-CZtmv-h0.js";
|
|
8
|
-
import { C as resolveStorePath, a as recordInboundSession, w as updateLastRoute } from "./room-context-BmNTBiw5.js";
|
|
9
|
-
import { r as escapeSlackMrkdwn } from "./provider-Bccng2Wp.js";
|
|
10
|
-
import { n as resolveSlackThreadTargets, t as prepareSlackMessage } from "./prepare-Bl5WcC3f.js";
|
|
11
|
-
import { a as resolveDeliveredSlackReplyThreadTs, i as readSlackReplyBlocks, n as deliverReplies, o as resolveSlackThreadTs, t as createSlackReplyDeliveryPlan } from "./replies-jiEDV6lH.js";
|
|
12
|
-
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
13
|
-
import { resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
|
|
14
|
-
import { buildChannelProgressDraftLine, buildChannelProgressDraftLineForEntry, createChannelMessageReplyPipeline, createChannelProgressDraftGate, createDraftStreamLoop, defineFinalizableLivePreviewAdapter, deliverWithFinalizableLivePreviewAdapter, formatChannelProgressDraftText, isChannelProgressDraftWorkToolName, mergeChannelProgressDraftLine, resolveAgentOutboundIdentity, resolveChannelMessageSourceReplyDeliveryMode, resolveChannelProgressDraftLabel, resolveChannelProgressDraftMaxLineChars, resolveChannelProgressDraftMaxLines, resolveChannelProgressDraftRender, resolveChannelStreamingBlockEnabled, resolveChannelStreamingNativeTransport, resolveChannelStreamingPreviewToolProgress, resolveChannelStreamingSuppressDefaultToolProgressMessages } from "openclaw/plugin-sdk/channel-outbound";
|
|
15
|
-
import { danger, logVerbose, shouldLogVerbose, sleep } from "openclaw/plugin-sdk/runtime-env";
|
|
16
|
-
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
17
|
-
import { buildTtsSupplementMediaPayload, getReplyPayloadTtsSupplement, resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
|
|
18
|
-
import { resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
|
|
19
|
-
import { mergePairLoopGuardConfig } from "openclaw/plugin-sdk/pair-loop-guard-runtime";
|
|
20
|
-
import { dispatchChannelInboundReply, hasVisibleInboundReplyDispatch } from "openclaw/plugin-sdk/channel-inbound";
|
|
21
|
-
import { resolvePinnedMainDmOwnerFromAllowlist } from "openclaw/plugin-sdk/security-runtime";
|
|
22
|
-
import { DEFAULT_TIMING, createStatusReactionController, logAckFailure, logTypingFailure, removeAckReactionAfterReply } from "openclaw/plugin-sdk/channel-feedback";
|
|
23
|
-
import { dispatchReplyWithBufferedBlockDispatcher } from "openclaw/plugin-sdk/reply-runtime";
|
|
24
|
-
//#region extensions/slack/src/draft-stream.ts
|
|
25
|
-
const DEFAULT_THROTTLE_MS = 1e3;
|
|
26
|
-
function createSlackDraftStream(params) {
|
|
27
|
-
const maxChars = Math.min(params.maxChars ?? 8e3, SLACK_TEXT_LIMIT);
|
|
28
|
-
const throttleMs = Math.max(250, params.throttleMs ?? DEFAULT_THROTTLE_MS);
|
|
29
|
-
const send = params.send ?? sendMessageSlack;
|
|
30
|
-
const edit = params.edit ?? editSlackMessage;
|
|
31
|
-
const remove = params.remove ?? deleteSlackMessage;
|
|
32
|
-
let streamMessageId;
|
|
33
|
-
let streamChannelId;
|
|
34
|
-
let lastSentKey = "";
|
|
35
|
-
let pendingUpdate;
|
|
36
|
-
let stopped = false;
|
|
37
|
-
const normalizeUpdate = (update) => typeof update === "string" ? { text: update } : update;
|
|
38
|
-
const sendOrEditStreamMessage = async (text) => {
|
|
39
|
-
if (stopped) return;
|
|
40
|
-
const trimmed = text.trimEnd();
|
|
41
|
-
if (!trimmed) return;
|
|
42
|
-
if (trimmed.length > maxChars) {
|
|
43
|
-
stopped = true;
|
|
44
|
-
params.warn?.(`slack stream preview stopped (text length ${trimmed.length} > ${maxChars})`);
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
const update = normalizeUpdate(pendingUpdate ?? text);
|
|
48
|
-
const blocks = update.text === text ? update.blocks : void 0;
|
|
49
|
-
const sentKey = `${trimmed}\n${blocks ? JSON.stringify(blocks) : ""}`;
|
|
50
|
-
if (sentKey === lastSentKey) return;
|
|
51
|
-
lastSentKey = sentKey;
|
|
52
|
-
try {
|
|
53
|
-
if (streamChannelId && streamMessageId) {
|
|
54
|
-
await edit(streamChannelId, streamMessageId, trimmed, {
|
|
55
|
-
cfg: params.cfg,
|
|
56
|
-
token: params.token,
|
|
57
|
-
accountId: params.accountId,
|
|
58
|
-
...blocks ? { blocks } : {}
|
|
59
|
-
});
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
const sent = await send(params.target, trimmed, {
|
|
63
|
-
cfg: params.cfg,
|
|
64
|
-
token: params.token,
|
|
65
|
-
accountId: params.accountId,
|
|
66
|
-
threadTs: params.resolveThreadTs?.(),
|
|
67
|
-
identity: params.identity,
|
|
68
|
-
...params.metadata ? { metadata: params.metadata } : {},
|
|
69
|
-
...blocks ? { blocks } : {}
|
|
70
|
-
});
|
|
71
|
-
streamChannelId = sent.channelId || streamChannelId;
|
|
72
|
-
streamMessageId = sent.messageId || streamMessageId;
|
|
73
|
-
if (!streamChannelId || !streamMessageId) {
|
|
74
|
-
stopped = true;
|
|
75
|
-
params.warn?.("slack stream preview stopped (missing identifiers from sendMessage)");
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
params.onMessageSent?.();
|
|
79
|
-
} catch (err) {
|
|
80
|
-
stopped = true;
|
|
81
|
-
params.warn?.(`slack stream preview failed: ${formatSlackError(err)}`);
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
const loop = createDraftStreamLoop({
|
|
85
|
-
throttleMs,
|
|
86
|
-
isStopped: () => stopped,
|
|
87
|
-
sendOrEditStreamMessage
|
|
88
|
-
});
|
|
89
|
-
const stop = () => {
|
|
90
|
-
stopped = true;
|
|
91
|
-
loop.stop();
|
|
92
|
-
};
|
|
93
|
-
const discardPending = async () => {
|
|
94
|
-
stop();
|
|
95
|
-
await loop.waitForInFlight();
|
|
96
|
-
};
|
|
97
|
-
const clear = async () => {
|
|
98
|
-
await discardPending();
|
|
99
|
-
const channelId = streamChannelId;
|
|
100
|
-
const messageId = streamMessageId;
|
|
101
|
-
streamChannelId = void 0;
|
|
102
|
-
streamMessageId = void 0;
|
|
103
|
-
lastSentKey = "";
|
|
104
|
-
pendingUpdate = void 0;
|
|
105
|
-
if (!channelId || !messageId) return;
|
|
106
|
-
try {
|
|
107
|
-
await remove(channelId, messageId, {
|
|
108
|
-
token: params.token,
|
|
109
|
-
accountId: params.accountId
|
|
110
|
-
});
|
|
111
|
-
} catch (err) {
|
|
112
|
-
params.warn?.(`slack stream preview cleanup failed: ${formatSlackError(err)}`);
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
const forceNewMessage = () => {
|
|
116
|
-
streamMessageId = void 0;
|
|
117
|
-
streamChannelId = void 0;
|
|
118
|
-
lastSentKey = "";
|
|
119
|
-
pendingUpdate = void 0;
|
|
120
|
-
loop.resetPending();
|
|
121
|
-
};
|
|
122
|
-
params.log?.(`slack stream preview ready (maxChars=${maxChars}, throttleMs=${throttleMs})`);
|
|
123
|
-
return {
|
|
124
|
-
update: (update) => {
|
|
125
|
-
const normalized = normalizeUpdate(update);
|
|
126
|
-
pendingUpdate = update;
|
|
127
|
-
loop.update(normalized.text);
|
|
128
|
-
},
|
|
129
|
-
flush: loop.flush,
|
|
130
|
-
clear,
|
|
131
|
-
discardPending,
|
|
132
|
-
seal: discardPending,
|
|
133
|
-
stop,
|
|
134
|
-
forceNewMessage,
|
|
135
|
-
messageId: () => streamMessageId,
|
|
136
|
-
channelId: () => streamChannelId
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
//#endregion
|
|
140
|
-
//#region extensions/slack/src/progress-blocks.ts
|
|
141
|
-
const SLACK_PROGRESS_FIELD_MAX = 1800;
|
|
142
|
-
const DEFAULT_SLACK_PROGRESS_DETAIL_MAX_CHARS = 120;
|
|
143
|
-
function field(text) {
|
|
144
|
-
return {
|
|
145
|
-
type: "mrkdwn",
|
|
146
|
-
text: truncateSlackText(text, SLACK_PROGRESS_FIELD_MAX)
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
function lineTitle(line) {
|
|
150
|
-
return `${line.icon ?? "•"} *${escapeSlackMrkdwn(line.label)}*`;
|
|
151
|
-
}
|
|
152
|
-
function compactDetail(value, maxChars) {
|
|
153
|
-
const normalized = value.replace(/\s+/g, " ").trim();
|
|
154
|
-
const chars = Array.from(normalized);
|
|
155
|
-
if (chars.length <= maxChars) return normalized;
|
|
156
|
-
if (maxChars <= 1) return "…";
|
|
157
|
-
const keepStart = Math.max(1, Math.ceil((maxChars - 1) * .45));
|
|
158
|
-
const keepEnd = Math.max(1, maxChars - keepStart - 1);
|
|
159
|
-
return `${chars.slice(0, keepStart).join("").trimEnd()}…${chars.slice(-keepEnd).join("").trimStart()}`;
|
|
160
|
-
}
|
|
161
|
-
function lineDetail(line, maxChars) {
|
|
162
|
-
const parts = [line.detail, line.status && !line.detail?.includes(line.status) ? line.status : void 0].map((part) => part?.trim()).filter((part) => Boolean(part));
|
|
163
|
-
return parts.length ? escapeSlackMrkdwn(compactDetail(parts.join(" · "), maxChars)) : " ";
|
|
164
|
-
}
|
|
165
|
-
function buildSlackProgressDraftBlocks(params) {
|
|
166
|
-
const label = params.label?.trim();
|
|
167
|
-
const maxLineChars = params.maxLineChars && params.maxLineChars > 0 ? Math.floor(params.maxLineChars) : DEFAULT_SLACK_PROGRESS_DETAIL_MAX_CHARS;
|
|
168
|
-
const blocks = [...label ? [{
|
|
169
|
-
type: "section",
|
|
170
|
-
text: field(`*${escapeSlackMrkdwn(label)}*`)
|
|
171
|
-
}] : [], ...params.lines.map((line) => ({
|
|
172
|
-
type: "section",
|
|
173
|
-
fields: [field(lineTitle(line)), field(lineDetail(line, maxLineChars))]
|
|
174
|
-
}))].slice(-50);
|
|
175
|
-
return blocks.length ? blocks : void 0;
|
|
176
|
-
}
|
|
177
|
-
//#endregion
|
|
178
|
-
//#region extensions/slack/src/stream-mode.ts
|
|
179
|
-
function resolveSlackStreamingConfig(params) {
|
|
180
|
-
const mode = resolveSlackStreamingMode(params);
|
|
181
|
-
return {
|
|
182
|
-
mode,
|
|
183
|
-
nativeStreaming: resolveSlackNativeStreaming(params),
|
|
184
|
-
draftMode: mapStreamingModeToSlackLegacyDraftStreamMode(mode)
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
function applyAppendOnlyStreamUpdate(params) {
|
|
188
|
-
const incoming = params.incoming.trimEnd();
|
|
189
|
-
if (!incoming) return {
|
|
190
|
-
rendered: params.rendered,
|
|
191
|
-
source: params.source,
|
|
192
|
-
changed: false
|
|
193
|
-
};
|
|
194
|
-
if (!params.rendered) return {
|
|
195
|
-
rendered: incoming,
|
|
196
|
-
source: incoming,
|
|
197
|
-
changed: true
|
|
198
|
-
};
|
|
199
|
-
if (incoming === params.source) return {
|
|
200
|
-
rendered: params.rendered,
|
|
201
|
-
source: params.source,
|
|
202
|
-
changed: false
|
|
203
|
-
};
|
|
204
|
-
if (incoming.startsWith(params.source) || incoming.startsWith(params.rendered)) return {
|
|
205
|
-
rendered: incoming,
|
|
206
|
-
source: incoming,
|
|
207
|
-
changed: incoming !== params.rendered
|
|
208
|
-
};
|
|
209
|
-
if (params.source.startsWith(incoming)) return {
|
|
210
|
-
rendered: params.rendered,
|
|
211
|
-
source: params.source,
|
|
212
|
-
changed: false
|
|
213
|
-
};
|
|
214
|
-
const separator = params.rendered.endsWith("\n") ? "" : "\n";
|
|
215
|
-
return {
|
|
216
|
-
rendered: `${params.rendered}${separator}${incoming}`,
|
|
217
|
-
source: incoming,
|
|
218
|
-
changed: true
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
//#endregion
|
|
222
|
-
//#region extensions/slack/src/streaming.ts
|
|
223
|
-
/**
|
|
224
|
-
* Thrown when Slack rejects a stream flush/finalize with a recipient-resolution
|
|
225
|
-
* error (see {@link BENIGN_SLACK_FINALIZE_ERROR_CODES}) while text is still
|
|
226
|
-
* only buffered locally by the Slack SDK. Carries the pending text so the
|
|
227
|
-
* caller can deliver it via the normal Slack reply path.
|
|
228
|
-
*/
|
|
229
|
-
var SlackStreamNotDeliveredError = class extends Error {
|
|
230
|
-
constructor(pendingText, slackCode) {
|
|
231
|
-
super(`slack-stream: finalize failed with ${slackCode} before any text reached Slack (${pendingText.length} chars pending)`);
|
|
232
|
-
this.name = "SlackStreamNotDeliveredError";
|
|
233
|
-
this.pendingText = pendingText;
|
|
234
|
-
this.slackCode = slackCode;
|
|
235
|
-
}
|
|
236
|
-
};
|
|
237
|
-
/**
|
|
238
|
-
* Start a new Slack text stream.
|
|
239
|
-
*
|
|
240
|
-
* Returns a {@link SlackStreamSession} that should be passed to
|
|
241
|
-
* {@link appendSlackStream} and {@link stopSlackStream}.
|
|
242
|
-
*
|
|
243
|
-
* The first chunk of text can optionally be included via `text`.
|
|
244
|
-
*/
|
|
245
|
-
async function startSlackStream(params) {
|
|
246
|
-
const { client, channel, threadTs, text, teamId, userId } = params;
|
|
247
|
-
logVerbose(`slack-stream: starting stream in ${channel} thread=${threadTs}${teamId ? ` team=${teamId}` : ""}${userId ? ` user=${userId}` : ""}`);
|
|
248
|
-
const streamer = client.chatStream({
|
|
249
|
-
channel,
|
|
250
|
-
thread_ts: threadTs,
|
|
251
|
-
...teamId ? { recipient_team_id: teamId } : {},
|
|
252
|
-
...userId ? { recipient_user_id: userId } : {}
|
|
253
|
-
});
|
|
254
|
-
const session = {
|
|
255
|
-
streamer,
|
|
256
|
-
channel,
|
|
257
|
-
threadTs,
|
|
258
|
-
stopped: false,
|
|
259
|
-
delivered: false,
|
|
260
|
-
pendingText: ""
|
|
261
|
-
};
|
|
262
|
-
if (text) {
|
|
263
|
-
session.pendingText += text;
|
|
264
|
-
try {
|
|
265
|
-
const result = await streamer.append({ markdown_text: text });
|
|
266
|
-
if (result) {
|
|
267
|
-
session.delivered = true;
|
|
268
|
-
session.pendingText = "";
|
|
269
|
-
}
|
|
270
|
-
logVerbose(`slack-stream: appended initial text (${text.length} chars, ${result ? "flushed" : "buffered"})`);
|
|
271
|
-
} catch (err) {
|
|
272
|
-
if (isBenignSlackFinalizeError(err) && session.pendingText) throw new SlackStreamNotDeliveredError(session.pendingText, extractSlackErrorCode(err) ?? "unknown");
|
|
273
|
-
throw err;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
return session;
|
|
277
|
-
}
|
|
278
|
-
/**
|
|
279
|
-
* Append markdown text to an active Slack stream.
|
|
280
|
-
*/
|
|
281
|
-
async function appendSlackStream(params) {
|
|
282
|
-
const { session, text } = params;
|
|
283
|
-
if (session.stopped) {
|
|
284
|
-
logVerbose("slack-stream: attempted to append to a stopped stream, ignoring");
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
if (!text) return;
|
|
288
|
-
session.pendingText += text;
|
|
289
|
-
try {
|
|
290
|
-
const result = await session.streamer.append({ markdown_text: text });
|
|
291
|
-
if (result) {
|
|
292
|
-
session.delivered = true;
|
|
293
|
-
session.pendingText = "";
|
|
294
|
-
}
|
|
295
|
-
logVerbose(`slack-stream: appended ${text.length} chars (${result ? "flushed" : "buffered"})`);
|
|
296
|
-
} catch (err) {
|
|
297
|
-
if (isBenignSlackFinalizeError(err) && session.pendingText) throw new SlackStreamNotDeliveredError(session.pendingText, extractSlackErrorCode(err) ?? "unknown");
|
|
298
|
-
throw err;
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
/**
|
|
302
|
-
* Stop (finalize) a Slack stream.
|
|
303
|
-
*
|
|
304
|
-
* After calling this the stream message becomes a normal Slack message.
|
|
305
|
-
* Optionally include final text to append before stopping.
|
|
306
|
-
*
|
|
307
|
-
* If Slack's `chat.stopStream` responds with a known benign finalize error
|
|
308
|
-
* (see {@link BENIGN_SLACK_FINALIZE_ERROR_CODES}) AND any prior `append`
|
|
309
|
-
* has already landed on Slack, the error is swallowed and the session is
|
|
310
|
-
* marked stopped - the already-delivered text stays visible.
|
|
311
|
-
*
|
|
312
|
-
* If the same benign error fires while text is still only buffered locally
|
|
313
|
-
* (e.g. short replies that never exceeded the SDK's buffer_size), this
|
|
314
|
-
* function throws a {@link SlackStreamNotDeliveredError} carrying that pending
|
|
315
|
-
* text so the caller can deliver it through the normal Slack reply path.
|
|
316
|
-
*
|
|
317
|
-
* All other errors propagate unchanged.
|
|
318
|
-
*/
|
|
319
|
-
async function stopSlackStream(params) {
|
|
320
|
-
const { session, text, metadata } = params;
|
|
321
|
-
if (session.stopped) {
|
|
322
|
-
logVerbose("slack-stream: stream already stopped, ignoring duplicate stop");
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
session.stopped = true;
|
|
326
|
-
if (text) session.pendingText += text;
|
|
327
|
-
logVerbose(`slack-stream: stopping stream in ${session.channel} thread=${session.threadTs}${text ? ` (final text: ${text.length} chars)` : ""}`);
|
|
328
|
-
try {
|
|
329
|
-
await session.streamer.stop({
|
|
330
|
-
...text ? { markdown_text: text } : {},
|
|
331
|
-
...metadata ? { metadata } : {}
|
|
332
|
-
});
|
|
333
|
-
session.delivered = true;
|
|
334
|
-
session.pendingText = "";
|
|
335
|
-
} catch (err) {
|
|
336
|
-
if (isBenignSlackFinalizeError(err)) {
|
|
337
|
-
const code = extractSlackErrorCode(err) ?? "unknown";
|
|
338
|
-
if (session.pendingText) throw new SlackStreamNotDeliveredError(session.pendingText, code);
|
|
339
|
-
if (session.delivered) {
|
|
340
|
-
logVerbose(`slack-stream: finalize rejected by Slack (${code}); prior appends delivered, treating stream as stopped`);
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
throw err;
|
|
345
|
-
}
|
|
346
|
-
logVerbose("slack-stream: stream stopped");
|
|
347
|
-
}
|
|
348
|
-
/**
|
|
349
|
-
* Slack API error codes that indicate `chat.stopStream` (or the
|
|
350
|
-
* `chat.startStream` call the SDK issues inside `stop()` when the buffer
|
|
351
|
-
* never flushed) cannot finalize the stream for the current recipient or
|
|
352
|
-
* team. Either the caller falls back to a normal message (see
|
|
353
|
-
* {@link SlackStreamNotDeliveredError}) or, if prior appends already
|
|
354
|
-
* delivered text, the error is logged verbosely and swallowed.
|
|
355
|
-
*/
|
|
356
|
-
const BENIGN_SLACK_FINALIZE_ERROR_CODES = new Set([
|
|
357
|
-
"user_not_found",
|
|
358
|
-
"team_not_found",
|
|
359
|
-
"missing_recipient_user_id"
|
|
360
|
-
]);
|
|
361
|
-
function isBenignSlackFinalizeError(err) {
|
|
362
|
-
const code = extractSlackErrorCode(err);
|
|
363
|
-
return code !== void 0 && BENIGN_SLACK_FINALIZE_ERROR_CODES.has(code);
|
|
364
|
-
}
|
|
365
|
-
function extractSlackErrorCode(err) {
|
|
366
|
-
if (!err || typeof err !== "object") return;
|
|
367
|
-
const record = err;
|
|
368
|
-
if (record.data && typeof record.data === "object") {
|
|
369
|
-
const inner = record.data.error;
|
|
370
|
-
if (typeof inner === "string") return inner;
|
|
371
|
-
}
|
|
372
|
-
return (typeof record.message === "string" ? record.message : "").match(/An API error occurred:\s*([a-z_][a-z0-9_]*)/i)?.[1];
|
|
373
|
-
}
|
|
374
|
-
function markSlackStreamFallbackDelivered(session) {
|
|
375
|
-
const hadNativeDelivery = session.delivered;
|
|
376
|
-
session.pendingText = "";
|
|
377
|
-
session.delivered = true;
|
|
378
|
-
if (!hadNativeDelivery) session.stopped = true;
|
|
379
|
-
}
|
|
380
|
-
//#endregion
|
|
381
|
-
//#region extensions/slack/src/monitor/message-handler/preview-finalize.ts
|
|
382
|
-
function buildExpectedSlackEditText(params) {
|
|
383
|
-
return buildSlackEditTextPayload(params.text, params.blocks);
|
|
384
|
-
}
|
|
385
|
-
function blocksMatch(expected, actual) {
|
|
386
|
-
if (!expected?.length) return !actual?.length;
|
|
387
|
-
if (!actual?.length) return false;
|
|
388
|
-
return JSON.stringify(expected) === JSON.stringify(actual);
|
|
389
|
-
}
|
|
390
|
-
async function readSlackMessageAfterEditError(params) {
|
|
391
|
-
if (params.threadTs) return ((await params.client.conversations.replies({
|
|
392
|
-
token: params.token,
|
|
393
|
-
channel: params.channelId,
|
|
394
|
-
ts: params.threadTs,
|
|
395
|
-
latest: params.messageId,
|
|
396
|
-
inclusive: true,
|
|
397
|
-
limit: 100
|
|
398
|
-
})).messages ?? []).find((message) => message?.ts === params.messageId) ?? null;
|
|
399
|
-
const message = (await params.client.conversations.history({
|
|
400
|
-
token: params.token,
|
|
401
|
-
channel: params.channelId,
|
|
402
|
-
latest: params.messageId,
|
|
403
|
-
oldest: params.messageId,
|
|
404
|
-
inclusive: true,
|
|
405
|
-
limit: 1
|
|
406
|
-
})).messages?.[0];
|
|
407
|
-
if (!message?.ts || message.ts !== params.messageId) return null;
|
|
408
|
-
return message;
|
|
409
|
-
}
|
|
410
|
-
async function didSlackPreviewEditApplyAfterError(params) {
|
|
411
|
-
const readback = await readSlackMessageAfterEditError(params);
|
|
412
|
-
if (!readback) return false;
|
|
413
|
-
const expectedText = buildExpectedSlackEditText({
|
|
414
|
-
text: params.text,
|
|
415
|
-
blocks: params.blocks
|
|
416
|
-
});
|
|
417
|
-
const actualText = normalizeSlackOutboundText((readback.text ?? "").trim());
|
|
418
|
-
if (params.blocks?.length) return actualText === expectedText && blocksMatch(params.blocks, readback.blocks);
|
|
419
|
-
return actualText === expectedText;
|
|
420
|
-
}
|
|
421
|
-
async function finalizeSlackPreviewEdit(params) {
|
|
422
|
-
try {
|
|
423
|
-
await editSlackMessage(params.channelId, params.messageId, params.text, {
|
|
424
|
-
token: params.token,
|
|
425
|
-
accountId: params.accountId,
|
|
426
|
-
client: params.client,
|
|
427
|
-
...params.blocks?.length ? { blocks: params.blocks } : {}
|
|
428
|
-
});
|
|
429
|
-
return;
|
|
430
|
-
} catch (err) {
|
|
431
|
-
try {
|
|
432
|
-
if (await didSlackPreviewEditApplyAfterError({
|
|
433
|
-
client: params.client,
|
|
434
|
-
token: params.token,
|
|
435
|
-
channelId: params.channelId,
|
|
436
|
-
messageId: params.messageId,
|
|
437
|
-
text: params.text,
|
|
438
|
-
blocks: params.blocks,
|
|
439
|
-
threadTs: params.threadTs
|
|
440
|
-
})) {
|
|
441
|
-
logVerbose(`slack: preview final edit response failed but readback matched message ${params.channelId}/${params.messageId}; suppressing duplicate fallback send`);
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
444
|
-
} catch (readbackErr) {
|
|
445
|
-
logVerbose(`slack: preview final edit readback failed (${String(readbackErr)})`);
|
|
446
|
-
}
|
|
447
|
-
throw err;
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
//#endregion
|
|
451
|
-
//#region extensions/slack/src/monitor/message-handler/dispatch.ts
|
|
452
|
-
const UNICODE_TO_SLACK = {
|
|
453
|
-
"👀": "eyes",
|
|
454
|
-
"🤔": "thinking_face",
|
|
455
|
-
"🔥": "fire",
|
|
456
|
-
"👨💻": "male-technologist",
|
|
457
|
-
"👨💻": "male-technologist",
|
|
458
|
-
"👩💻": "female-technologist",
|
|
459
|
-
"⚡": "zap",
|
|
460
|
-
"🌐": "globe_with_meridians",
|
|
461
|
-
"✅": "white_check_mark",
|
|
462
|
-
"👍": "thumbsup",
|
|
463
|
-
"❌": "x",
|
|
464
|
-
"😱": "scream",
|
|
465
|
-
"🥱": "yawning_face",
|
|
466
|
-
"😨": "fearful",
|
|
467
|
-
"⏳": "hourglass_flowing_sand",
|
|
468
|
-
"⚠️": "warning",
|
|
469
|
-
"✍": "writing_hand",
|
|
470
|
-
"🗜️": "compression",
|
|
471
|
-
"🗜": "compression",
|
|
472
|
-
"🧠": "brain",
|
|
473
|
-
"🛠️": "hammer_and_wrench",
|
|
474
|
-
"💻": "computer"
|
|
475
|
-
};
|
|
476
|
-
function resolveSlackMessageTimestampMs(message) {
|
|
477
|
-
const ts = message.event_ts ?? message.ts;
|
|
478
|
-
if (!ts) return;
|
|
479
|
-
const parsed = Number(ts);
|
|
480
|
-
return Number.isFinite(parsed) ? Math.trunc(parsed * 1e3) : void 0;
|
|
481
|
-
}
|
|
482
|
-
function resolveSlackBotLoopProtection(prepared) {
|
|
483
|
-
const senderBotId = prepared.message.bot_id;
|
|
484
|
-
if (!senderBotId) return;
|
|
485
|
-
const receiverBotId = prepared.ctx.botId || prepared.ctx.botUserId;
|
|
486
|
-
if (!receiverBotId || senderBotId === prepared.ctx.botId || prepared.message.user === prepared.ctx.botUserId) return;
|
|
487
|
-
return {
|
|
488
|
-
scopeId: prepared.route.accountId,
|
|
489
|
-
conversationId: prepared.message.channel,
|
|
490
|
-
senderId: senderBotId,
|
|
491
|
-
receiverId: receiverBotId,
|
|
492
|
-
config: mergePairLoopGuardConfig(prepared.account.config.botLoopProtection, prepared.channelConfig?.botLoopProtection),
|
|
493
|
-
defaultsConfig: prepared.ctx.cfg.channels?.defaults?.botLoopProtection,
|
|
494
|
-
defaultEnabled: true,
|
|
495
|
-
nowMs: resolveSlackMessageTimestampMs(prepared.message)
|
|
496
|
-
};
|
|
497
|
-
}
|
|
498
|
-
function toSlackEmojiName(emoji) {
|
|
499
|
-
let trimmed = emoji.trim();
|
|
500
|
-
while (trimmed.startsWith(":")) trimmed = trimmed.slice(1);
|
|
501
|
-
while (trimmed.endsWith(":")) trimmed = trimmed.slice(0, -1);
|
|
502
|
-
return UNICODE_TO_SLACK[trimmed] ?? trimmed;
|
|
503
|
-
}
|
|
504
|
-
function isSlackStreamingEnabled(params) {
|
|
505
|
-
if (params.mode !== "partial") return false;
|
|
506
|
-
return params.nativeStreaming;
|
|
507
|
-
}
|
|
508
|
-
function shouldEnableSlackPreviewStreaming(params) {
|
|
509
|
-
return params.mode !== "off";
|
|
510
|
-
}
|
|
511
|
-
function shouldInitializeSlackDraftStream(params) {
|
|
512
|
-
return params.previewStreamingEnabled && !params.useStreaming;
|
|
513
|
-
}
|
|
514
|
-
function resolveSlackDisableBlockStreaming(params) {
|
|
515
|
-
if (params.useStreaming || params.shouldUseDraftStream) return true;
|
|
516
|
-
return typeof params.blockStreamingEnabled === "boolean" ? !params.blockStreamingEnabled : void 0;
|
|
517
|
-
}
|
|
518
|
-
function resolveSlackStreamingThreadHint(params) {
|
|
519
|
-
return resolveSlackThreadTs({
|
|
520
|
-
replyToMode: params.replyToMode,
|
|
521
|
-
incomingThreadTs: params.incomingThreadTs,
|
|
522
|
-
messageTs: params.messageTs,
|
|
523
|
-
hasReplied: false,
|
|
524
|
-
isThreadReply: params.isThreadReply
|
|
525
|
-
});
|
|
526
|
-
}
|
|
527
|
-
const SLACK_STREAM_RECIPIENT_TEAM_CACHE_MAX = 2e3;
|
|
528
|
-
const slackStreamRecipientTeamCache = /* @__PURE__ */ new Map();
|
|
529
|
-
function buildSlackEventDeliveryKey(params) {
|
|
530
|
-
const reply = resolveSendableOutboundReplyParts(params.payload, { text: params.textOverride });
|
|
531
|
-
const slackBlocks = readSlackReplyBlocks(params.payload);
|
|
532
|
-
if (!reply.hasContent && !slackBlocks?.length) return null;
|
|
533
|
-
return JSON.stringify({
|
|
534
|
-
kind: params.kind,
|
|
535
|
-
threadTs: params.threadTs ?? "",
|
|
536
|
-
replyToId: params.payload.replyToId ?? null,
|
|
537
|
-
text: reply.trimmedText,
|
|
538
|
-
mediaUrls: reply.mediaUrls,
|
|
539
|
-
blocks: slackBlocks ?? null
|
|
540
|
-
});
|
|
541
|
-
}
|
|
542
|
-
function readSlackStreamRecipientTeamCache(params) {
|
|
543
|
-
if (!params.fallbackTeamId || !params.userId) return;
|
|
544
|
-
const cacheKey = `${params.fallbackTeamId}:${params.userId}`;
|
|
545
|
-
const cached = slackStreamRecipientTeamCache.get(cacheKey);
|
|
546
|
-
if (!cached) return;
|
|
547
|
-
slackStreamRecipientTeamCache.delete(cacheKey);
|
|
548
|
-
slackStreamRecipientTeamCache.set(cacheKey, cached);
|
|
549
|
-
return cached;
|
|
550
|
-
}
|
|
551
|
-
function rememberSlackStreamRecipientTeam(params) {
|
|
552
|
-
if (!params.fallbackTeamId || !params.userId) return;
|
|
553
|
-
const cacheKey = `${params.fallbackTeamId}:${params.userId}`;
|
|
554
|
-
if (slackStreamRecipientTeamCache.has(cacheKey)) slackStreamRecipientTeamCache.delete(cacheKey);
|
|
555
|
-
slackStreamRecipientTeamCache.set(cacheKey, params.teamId);
|
|
556
|
-
if (slackStreamRecipientTeamCache.size > SLACK_STREAM_RECIPIENT_TEAM_CACHE_MAX) {
|
|
557
|
-
const oldest = slackStreamRecipientTeamCache.keys().next().value;
|
|
558
|
-
if (oldest) slackStreamRecipientTeamCache.delete(oldest);
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
function createSlackEventDeliveryTracker() {
|
|
562
|
-
const deliveredKeys = /* @__PURE__ */ new Set();
|
|
563
|
-
return {
|
|
564
|
-
hasDelivered(params) {
|
|
565
|
-
const key = buildSlackEventDeliveryKey(params);
|
|
566
|
-
return key ? deliveredKeys.has(key) : false;
|
|
567
|
-
},
|
|
568
|
-
markDelivered(params) {
|
|
569
|
-
const key = buildSlackEventDeliveryKey(params);
|
|
570
|
-
if (key) deliveredKeys.add(key);
|
|
571
|
-
}
|
|
572
|
-
};
|
|
573
|
-
}
|
|
574
|
-
function shouldUseStreaming(params) {
|
|
575
|
-
if (!params.streamingEnabled) return false;
|
|
576
|
-
if (!params.threadTs) {
|
|
577
|
-
logVerbose("slack-stream: streaming disabled — no reply thread target available");
|
|
578
|
-
return false;
|
|
579
|
-
}
|
|
580
|
-
return true;
|
|
581
|
-
}
|
|
582
|
-
async function resolveSlackStreamRecipientTeamId(params) {
|
|
583
|
-
const cachedTeamId = readSlackStreamRecipientTeamCache(params);
|
|
584
|
-
if (cachedTeamId) return cachedTeamId;
|
|
585
|
-
if (params.userId) try {
|
|
586
|
-
const info = await params.client.users.info({
|
|
587
|
-
token: params.token,
|
|
588
|
-
user: params.userId
|
|
589
|
-
});
|
|
590
|
-
const teamId = info.user?.team_id ?? info.user?.profile?.team;
|
|
591
|
-
if (teamId) {
|
|
592
|
-
rememberSlackStreamRecipientTeam({
|
|
593
|
-
...params,
|
|
594
|
-
teamId
|
|
595
|
-
});
|
|
596
|
-
return teamId;
|
|
597
|
-
}
|
|
598
|
-
} catch (err) {
|
|
599
|
-
logVerbose(`slack-stream: users.info team lookup failed (${formatErrorMessage(err)})`);
|
|
600
|
-
}
|
|
601
|
-
return params.fallbackTeamId;
|
|
602
|
-
}
|
|
603
|
-
async function dispatchPreparedSlackMessage(prepared) {
|
|
604
|
-
const { ctx, account, message, route } = prepared;
|
|
605
|
-
const cfg = ctx.cfg;
|
|
606
|
-
const runtime = ctx.runtime;
|
|
607
|
-
const outboundIdentity = resolveAgentOutboundIdentity(cfg, route.agentId);
|
|
608
|
-
const slackIdentity = outboundIdentity ? {
|
|
609
|
-
username: outboundIdentity.name,
|
|
610
|
-
iconUrl: outboundIdentity.avatarUrl,
|
|
611
|
-
iconEmoji: outboundIdentity.emoji
|
|
612
|
-
} : void 0;
|
|
613
|
-
if (prepared.isDirectMessage) {
|
|
614
|
-
const sessionCfg = cfg.session;
|
|
615
|
-
const storePath = resolveStorePath(sessionCfg?.store, { agentId: route.agentId });
|
|
616
|
-
const pinnedMainDmOwner = resolvePinnedMainDmOwnerFromAllowlist({
|
|
617
|
-
dmScope: cfg.session?.dmScope,
|
|
618
|
-
allowFrom: ctx.allowFrom,
|
|
619
|
-
normalizeEntry: normalizeSlackAllowOwnerEntry
|
|
620
|
-
});
|
|
621
|
-
const senderRecipient = normalizeOptionalLowercaseString(message.user);
|
|
622
|
-
const inboundLastRouteSessionKey = resolveInboundLastRouteSessionKey({
|
|
623
|
-
route,
|
|
624
|
-
sessionKey: prepared.ctxPayload.SessionKey ?? route.sessionKey
|
|
625
|
-
});
|
|
626
|
-
if (inboundLastRouteSessionKey === route.mainSessionKey && pinnedMainDmOwner && senderRecipient && normalizeOptionalLowercaseString(pinnedMainDmOwner) !== senderRecipient) logVerbose(`slack: skip main-session last route for ${senderRecipient} (pinned owner ${pinnedMainDmOwner})`);
|
|
627
|
-
else await updateLastRoute({
|
|
628
|
-
storePath,
|
|
629
|
-
sessionKey: inboundLastRouteSessionKey,
|
|
630
|
-
deliveryContext: {
|
|
631
|
-
channel: "slack",
|
|
632
|
-
to: `user:${message.user}`,
|
|
633
|
-
accountId: route.accountId,
|
|
634
|
-
threadId: prepared.ctxPayload.MessageThreadId ?? prepared.ctxPayload.TransportThreadId
|
|
635
|
-
},
|
|
636
|
-
ctx: prepared.ctxPayload
|
|
637
|
-
});
|
|
638
|
-
}
|
|
639
|
-
const threadTargets = resolveSlackThreadTargets({
|
|
640
|
-
message,
|
|
641
|
-
replyToMode: prepared.replyToMode
|
|
642
|
-
});
|
|
643
|
-
const forcedReplyThreadTs = prepared.forcedReplyThreadTs;
|
|
644
|
-
const slackMessageMetadata = prepared.slackMessageMetadata;
|
|
645
|
-
const statusThreadTs = forcedReplyThreadTs ?? threadTargets.statusThreadTs;
|
|
646
|
-
const isThreadReply = threadTargets.isThreadReply;
|
|
647
|
-
const replyDeliveryMode = forcedReplyThreadTs ? "off" : prepared.replyToMode;
|
|
648
|
-
const sourceReplyDeliveryMode = resolveChannelMessageSourceReplyDeliveryMode({
|
|
649
|
-
cfg,
|
|
650
|
-
ctx: prepared.ctxPayload
|
|
651
|
-
});
|
|
652
|
-
const sourceRepliesAreToolOnly = sourceReplyDeliveryMode === "message_tool_only";
|
|
653
|
-
const reactionMessageTs = prepared.ackReactionMessageTs;
|
|
654
|
-
const messageTs = message.ts ?? message.event_ts;
|
|
655
|
-
const incomingThreadTs = message.thread_ts;
|
|
656
|
-
let didSetStatus = false;
|
|
657
|
-
const statusReactionsEnabled = Boolean(prepared.ackReactionPromise) && Boolean(reactionMessageTs) && cfg.messages?.statusReactions?.enabled !== false;
|
|
658
|
-
const slackStatusAdapter = {
|
|
659
|
-
setReaction: async (emoji) => {
|
|
660
|
-
await reactSlackMessage(message.channel, reactionMessageTs ?? "", toSlackEmojiName(emoji), {
|
|
661
|
-
token: ctx.botToken,
|
|
662
|
-
client: ctx.app.client
|
|
663
|
-
}).catch((err) => {
|
|
664
|
-
if (formatErrorMessage(err).includes("already_reacted")) return;
|
|
665
|
-
throw err;
|
|
666
|
-
});
|
|
667
|
-
},
|
|
668
|
-
removeReaction: async (emoji) => {
|
|
669
|
-
await removeSlackReaction(message.channel, reactionMessageTs ?? "", toSlackEmojiName(emoji), {
|
|
670
|
-
token: ctx.botToken,
|
|
671
|
-
client: ctx.app.client
|
|
672
|
-
}).catch((err) => {
|
|
673
|
-
if (formatErrorMessage(err).includes("no_reaction")) return;
|
|
674
|
-
throw err;
|
|
675
|
-
});
|
|
676
|
-
}
|
|
677
|
-
};
|
|
678
|
-
const statusReactionTiming = {
|
|
679
|
-
...DEFAULT_TIMING,
|
|
680
|
-
...cfg.messages?.statusReactions?.timing
|
|
681
|
-
};
|
|
682
|
-
const statusReactions = createStatusReactionController({
|
|
683
|
-
enabled: statusReactionsEnabled,
|
|
684
|
-
adapter: slackStatusAdapter,
|
|
685
|
-
initialEmoji: prepared.ackReactionValue || "eyes",
|
|
686
|
-
emojis: cfg.messages?.statusReactions?.emojis,
|
|
687
|
-
timing: cfg.messages?.statusReactions?.timing,
|
|
688
|
-
onError: (err) => {
|
|
689
|
-
logAckFailure({
|
|
690
|
-
log: logVerbose,
|
|
691
|
-
channel: "slack",
|
|
692
|
-
target: `${message.channel}/${message.ts}`,
|
|
693
|
-
error: err
|
|
694
|
-
});
|
|
695
|
-
}
|
|
696
|
-
});
|
|
697
|
-
if (statusReactionsEnabled) statusReactions.setQueued();
|
|
698
|
-
const hasRepliedRef = { value: false };
|
|
699
|
-
const replyPlan = createSlackReplyDeliveryPlan({
|
|
700
|
-
replyToMode: replyDeliveryMode,
|
|
701
|
-
incomingThreadTs: forcedReplyThreadTs ?? incomingThreadTs,
|
|
702
|
-
messageTs,
|
|
703
|
-
hasRepliedRef,
|
|
704
|
-
isThreadReply: Boolean(forcedReplyThreadTs) || isThreadReply
|
|
705
|
-
});
|
|
706
|
-
const typingTarget = statusThreadTs ? `${message.channel}/${statusThreadTs}` : message.channel;
|
|
707
|
-
const typingReaction = ctx.typingReaction;
|
|
708
|
-
const { onModelSelected, ...replyPipeline } = createChannelMessageReplyPipeline({
|
|
709
|
-
cfg,
|
|
710
|
-
agentId: route.agentId,
|
|
711
|
-
channel: "slack",
|
|
712
|
-
accountId: route.accountId,
|
|
713
|
-
transformReplyPayload: (payload) => {
|
|
714
|
-
if (payload.isReasoning === true) return null;
|
|
715
|
-
return isSlackInteractiveRepliesEnabled({
|
|
716
|
-
cfg,
|
|
717
|
-
accountId: route.accountId
|
|
718
|
-
}) ? compileSlackInteractiveReplies(payload) : payload;
|
|
719
|
-
},
|
|
720
|
-
typing: {
|
|
721
|
-
start: async () => {
|
|
722
|
-
didSetStatus = true;
|
|
723
|
-
await ctx.setSlackThreadStatus({
|
|
724
|
-
channelId: message.channel,
|
|
725
|
-
threadTs: statusThreadTs,
|
|
726
|
-
status: "is typing..."
|
|
727
|
-
});
|
|
728
|
-
if (typingReaction && message.ts) await reactSlackMessage(message.channel, message.ts, typingReaction, {
|
|
729
|
-
token: ctx.botToken,
|
|
730
|
-
client: ctx.app.client
|
|
731
|
-
}).catch(() => {});
|
|
732
|
-
},
|
|
733
|
-
stop: async () => {
|
|
734
|
-
if (!didSetStatus) return;
|
|
735
|
-
didSetStatus = false;
|
|
736
|
-
await ctx.setSlackThreadStatus({
|
|
737
|
-
channelId: message.channel,
|
|
738
|
-
threadTs: statusThreadTs,
|
|
739
|
-
status: ""
|
|
740
|
-
});
|
|
741
|
-
if (typingReaction && message.ts) await removeSlackReaction(message.channel, message.ts, typingReaction, {
|
|
742
|
-
token: ctx.botToken,
|
|
743
|
-
client: ctx.app.client
|
|
744
|
-
}).catch(() => {});
|
|
745
|
-
},
|
|
746
|
-
onStartError: (err) => {
|
|
747
|
-
logTypingFailure({
|
|
748
|
-
log: (message) => runtime.error?.(danger(message)),
|
|
749
|
-
channel: "slack",
|
|
750
|
-
action: "start",
|
|
751
|
-
target: typingTarget,
|
|
752
|
-
error: err
|
|
753
|
-
});
|
|
754
|
-
},
|
|
755
|
-
onStopError: (err) => {
|
|
756
|
-
logTypingFailure({
|
|
757
|
-
log: (message) => runtime.error?.(danger(message)),
|
|
758
|
-
channel: "slack",
|
|
759
|
-
action: "stop",
|
|
760
|
-
target: typingTarget,
|
|
761
|
-
error: err
|
|
762
|
-
});
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
});
|
|
766
|
-
const slackStreaming = resolveSlackStreamingConfig({
|
|
767
|
-
streaming: account.config.streaming,
|
|
768
|
-
nativeStreaming: resolveChannelStreamingNativeTransport(account.config)
|
|
769
|
-
});
|
|
770
|
-
const streamThreadHint = forcedReplyThreadTs ?? resolveSlackStreamingThreadHint({
|
|
771
|
-
replyToMode: replyDeliveryMode,
|
|
772
|
-
incomingThreadTs,
|
|
773
|
-
messageTs,
|
|
774
|
-
isThreadReply
|
|
775
|
-
});
|
|
776
|
-
const previewStreamingEnabled = !sourceRepliesAreToolOnly && shouldEnableSlackPreviewStreaming({ mode: slackStreaming.mode });
|
|
777
|
-
const useStreaming = shouldUseStreaming({
|
|
778
|
-
streamingEnabled: !sourceRepliesAreToolOnly && isSlackStreamingEnabled({
|
|
779
|
-
mode: slackStreaming.mode,
|
|
780
|
-
nativeStreaming: slackStreaming.nativeStreaming
|
|
781
|
-
}),
|
|
782
|
-
threadTs: streamThreadHint
|
|
783
|
-
});
|
|
784
|
-
const shouldUseDraftStream = shouldInitializeSlackDraftStream({
|
|
785
|
-
previewStreamingEnabled,
|
|
786
|
-
useStreaming
|
|
787
|
-
});
|
|
788
|
-
const blockStreamingEnabled = resolveChannelStreamingBlockEnabled(account.config);
|
|
789
|
-
const disableBlockStreaming = sourceRepliesAreToolOnly ? true : resolveSlackDisableBlockStreaming({
|
|
790
|
-
useStreaming,
|
|
791
|
-
shouldUseDraftStream,
|
|
792
|
-
blockStreamingEnabled
|
|
793
|
-
});
|
|
794
|
-
let streamSession = null;
|
|
795
|
-
let streamFailed = false;
|
|
796
|
-
let usedReplyThreadTs;
|
|
797
|
-
let usedBlockReplyThreadTs;
|
|
798
|
-
let observedReplyDelivery = false;
|
|
799
|
-
let observedFinalReplyDelivery = false;
|
|
800
|
-
const deliveryTracker = createSlackEventDeliveryTracker();
|
|
801
|
-
const resolveDeliveryThreadTs = (params) => {
|
|
802
|
-
const plannedThreadTs = params.forcedThreadTs ? void 0 : replyPlan.nextThreadTs();
|
|
803
|
-
return params.forcedThreadTs ?? plannedThreadTs ?? (params.kind === "block" ? usedBlockReplyThreadTs : void 0);
|
|
804
|
-
};
|
|
805
|
-
const rememberDeliveredThreadTs = (kind, deliveredThreadTs) => {
|
|
806
|
-
if (!deliveredThreadTs) return;
|
|
807
|
-
usedReplyThreadTs ??= deliveredThreadTs;
|
|
808
|
-
if (kind === "block") usedBlockReplyThreadTs = deliveredThreadTs;
|
|
809
|
-
};
|
|
810
|
-
const deliverPendingStreamFallback = async (session, err) => {
|
|
811
|
-
const fallbackText = err.pendingText.trim();
|
|
812
|
-
if (!fallbackText) return false;
|
|
813
|
-
try {
|
|
814
|
-
await deliverReplies({
|
|
815
|
-
cfg: ctx.cfg,
|
|
816
|
-
replies: [{ text: fallbackText }],
|
|
817
|
-
target: prepared.replyTarget,
|
|
818
|
-
token: ctx.botToken,
|
|
819
|
-
accountId: account.accountId,
|
|
820
|
-
runtime,
|
|
821
|
-
textLimit: ctx.textLimit,
|
|
822
|
-
replyThreadTs: session.threadTs,
|
|
823
|
-
replyToMode: replyDeliveryMode,
|
|
824
|
-
...slackIdentity ? { identity: slackIdentity } : {},
|
|
825
|
-
...slackMessageMetadata ? { metadata: slackMessageMetadata } : {}
|
|
826
|
-
});
|
|
827
|
-
markSlackStreamFallbackDelivered(session);
|
|
828
|
-
observedReplyDelivery = true;
|
|
829
|
-
usedReplyThreadTs ??= session.threadTs;
|
|
830
|
-
logVerbose(`slack-stream: streamed delivery failed (${err.slackCode}); delivered ${fallbackText.length} chars via deliverReplies fallback`);
|
|
831
|
-
return true;
|
|
832
|
-
} catch (postErr) {
|
|
833
|
-
runtime.error?.(danger(`slack-stream: fallback deliverReplies failed after ${err.slackCode}: ${formatErrorMessage(postErr)}`));
|
|
834
|
-
return false;
|
|
835
|
-
}
|
|
836
|
-
};
|
|
837
|
-
const deliverNormally = async (params) => {
|
|
838
|
-
if (params.payload.isReasoning === true) return;
|
|
839
|
-
const replyThreadTs = resolveDeliveryThreadTs(params);
|
|
840
|
-
if (deliveryTracker.hasDelivered({
|
|
841
|
-
kind: params.kind,
|
|
842
|
-
payload: params.payload,
|
|
843
|
-
threadTs: replyThreadTs
|
|
844
|
-
})) {
|
|
845
|
-
logVerbose("slack: suppressed duplicate normal delivery within the same turn");
|
|
846
|
-
return;
|
|
847
|
-
}
|
|
848
|
-
await deliverReplies({
|
|
849
|
-
cfg: ctx.cfg,
|
|
850
|
-
replies: [params.payload],
|
|
851
|
-
target: prepared.replyTarget,
|
|
852
|
-
token: ctx.botToken,
|
|
853
|
-
accountId: account.accountId,
|
|
854
|
-
runtime,
|
|
855
|
-
textLimit: ctx.textLimit,
|
|
856
|
-
replyThreadTs,
|
|
857
|
-
replyToMode: replyDeliveryMode,
|
|
858
|
-
...slackIdentity ? { identity: slackIdentity } : {},
|
|
859
|
-
...slackMessageMetadata ? { metadata: slackMessageMetadata } : {}
|
|
860
|
-
});
|
|
861
|
-
observedReplyDelivery = true;
|
|
862
|
-
if (params.kind === "final") observedFinalReplyDelivery = true;
|
|
863
|
-
const deliveredThreadTs = resolveDeliveredSlackReplyThreadTs({
|
|
864
|
-
replyToMode: replyDeliveryMode,
|
|
865
|
-
payloadReplyToId: params.payload.replyToId,
|
|
866
|
-
replyThreadTs
|
|
867
|
-
});
|
|
868
|
-
rememberDeliveredThreadTs(params.kind, deliveredThreadTs);
|
|
869
|
-
replyPlan.markSent();
|
|
870
|
-
deliveryTracker.markDelivered({
|
|
871
|
-
kind: params.kind,
|
|
872
|
-
payload: params.payload,
|
|
873
|
-
threadTs: replyThreadTs
|
|
874
|
-
});
|
|
875
|
-
};
|
|
876
|
-
const deliverBufferedStreamFallback = async (params) => {
|
|
877
|
-
if (!await deliverPendingStreamFallback(params.session, params.err)) return false;
|
|
878
|
-
replyPlan.markSent();
|
|
879
|
-
if (params.kind === "final") observedFinalReplyDelivery = true;
|
|
880
|
-
deliveryTracker.markDelivered({
|
|
881
|
-
kind: params.kind,
|
|
882
|
-
payload: params.payload,
|
|
883
|
-
threadTs: params.session.threadTs,
|
|
884
|
-
textOverride: params.textOverride
|
|
885
|
-
});
|
|
886
|
-
rememberDeliveredThreadTs(params.kind, params.session.threadTs);
|
|
887
|
-
return true;
|
|
888
|
-
};
|
|
889
|
-
const deliverWithStreaming = async (params) => {
|
|
890
|
-
if (params.payload.isReasoning === true) return;
|
|
891
|
-
const reply = resolveSendableOutboundReplyParts(params.payload);
|
|
892
|
-
if (streamFailed || reply.hasMedia || readSlackReplyBlocks(params.payload)?.length || !reply.hasText) {
|
|
893
|
-
await deliverNormally({
|
|
894
|
-
payload: params.payload,
|
|
895
|
-
kind: params.kind,
|
|
896
|
-
forcedThreadTs: streamSession?.threadTs
|
|
897
|
-
});
|
|
898
|
-
return;
|
|
899
|
-
}
|
|
900
|
-
const text = reply.trimmedText;
|
|
901
|
-
let plannedThreadTs;
|
|
902
|
-
try {
|
|
903
|
-
if (!streamSession) {
|
|
904
|
-
const streamThreadTs = replyPlan.nextThreadTs();
|
|
905
|
-
plannedThreadTs = streamThreadTs;
|
|
906
|
-
if (!streamThreadTs) {
|
|
907
|
-
logVerbose("slack-stream: no reply thread target for stream start, falling back to normal delivery");
|
|
908
|
-
streamFailed = true;
|
|
909
|
-
await deliverNormally({
|
|
910
|
-
payload: params.payload,
|
|
911
|
-
kind: params.kind
|
|
912
|
-
});
|
|
913
|
-
return;
|
|
914
|
-
}
|
|
915
|
-
if (deliveryTracker.hasDelivered({
|
|
916
|
-
kind: params.kind,
|
|
917
|
-
payload: params.payload,
|
|
918
|
-
threadTs: streamThreadTs,
|
|
919
|
-
textOverride: text
|
|
920
|
-
})) {
|
|
921
|
-
logVerbose("slack-stream: suppressed duplicate stream start payload");
|
|
922
|
-
return;
|
|
923
|
-
}
|
|
924
|
-
streamSession = await startSlackStream({
|
|
925
|
-
client: ctx.app.client,
|
|
926
|
-
channel: message.channel,
|
|
927
|
-
threadTs: streamThreadTs,
|
|
928
|
-
text,
|
|
929
|
-
teamId: await resolveSlackStreamRecipientTeamId({
|
|
930
|
-
client: ctx.app.client,
|
|
931
|
-
token: ctx.botToken,
|
|
932
|
-
userId: message.user,
|
|
933
|
-
fallbackTeamId: ctx.teamId
|
|
934
|
-
}),
|
|
935
|
-
userId: message.user
|
|
936
|
-
});
|
|
937
|
-
if (streamSession.delivered) {
|
|
938
|
-
observedReplyDelivery = true;
|
|
939
|
-
if (params.kind === "final") observedFinalReplyDelivery = true;
|
|
940
|
-
}
|
|
941
|
-
rememberDeliveredThreadTs(params.kind, streamThreadTs);
|
|
942
|
-
replyPlan.markSent();
|
|
943
|
-
deliveryTracker.markDelivered({
|
|
944
|
-
kind: params.kind,
|
|
945
|
-
payload: params.payload,
|
|
946
|
-
threadTs: streamThreadTs,
|
|
947
|
-
textOverride: text
|
|
948
|
-
});
|
|
949
|
-
return;
|
|
950
|
-
}
|
|
951
|
-
if (deliveryTracker.hasDelivered({
|
|
952
|
-
kind: params.kind,
|
|
953
|
-
payload: params.payload,
|
|
954
|
-
threadTs: streamSession.threadTs,
|
|
955
|
-
textOverride: text
|
|
956
|
-
})) {
|
|
957
|
-
logVerbose("slack-stream: suppressed duplicate append payload");
|
|
958
|
-
return;
|
|
959
|
-
}
|
|
960
|
-
await appendSlackStream({
|
|
961
|
-
session: streamSession,
|
|
962
|
-
text: "\n" + text
|
|
963
|
-
});
|
|
964
|
-
if (streamSession.delivered) {
|
|
965
|
-
observedReplyDelivery = true;
|
|
966
|
-
if (params.kind === "final") observedFinalReplyDelivery = true;
|
|
967
|
-
}
|
|
968
|
-
deliveryTracker.markDelivered({
|
|
969
|
-
kind: params.kind,
|
|
970
|
-
payload: params.payload,
|
|
971
|
-
threadTs: streamSession.threadTs,
|
|
972
|
-
textOverride: text
|
|
973
|
-
});
|
|
974
|
-
} catch (err) {
|
|
975
|
-
if (err instanceof SlackStreamNotDeliveredError) {
|
|
976
|
-
streamFailed = true;
|
|
977
|
-
if (streamSession) {
|
|
978
|
-
if (await deliverBufferedStreamFallback({
|
|
979
|
-
session: streamSession,
|
|
980
|
-
err,
|
|
981
|
-
payload: params.payload,
|
|
982
|
-
kind: params.kind,
|
|
983
|
-
textOverride: text
|
|
984
|
-
})) return;
|
|
985
|
-
throw err;
|
|
986
|
-
}
|
|
987
|
-
await deliverNormally({
|
|
988
|
-
payload: params.payload,
|
|
989
|
-
kind: params.kind,
|
|
990
|
-
forcedThreadTs: plannedThreadTs
|
|
991
|
-
});
|
|
992
|
-
return;
|
|
993
|
-
}
|
|
994
|
-
runtime.error?.(danger(`slack-stream: streaming API call failed: ${formatSlackError(err)}, falling back`));
|
|
995
|
-
streamFailed = true;
|
|
996
|
-
if (streamSession && streamSession.pendingText) {
|
|
997
|
-
const bufferedFallbackErr = new SlackStreamNotDeliveredError(streamSession.pendingText, "unknown");
|
|
998
|
-
if (await deliverBufferedStreamFallback({
|
|
999
|
-
session: streamSession,
|
|
1000
|
-
err: bufferedFallbackErr,
|
|
1001
|
-
payload: params.payload,
|
|
1002
|
-
kind: params.kind,
|
|
1003
|
-
textOverride: text
|
|
1004
|
-
})) return;
|
|
1005
|
-
}
|
|
1006
|
-
await deliverNormally({
|
|
1007
|
-
payload: params.payload,
|
|
1008
|
-
kind: params.kind,
|
|
1009
|
-
forcedThreadTs: streamSession?.threadTs ?? plannedThreadTs
|
|
1010
|
-
});
|
|
1011
|
-
}
|
|
1012
|
-
};
|
|
1013
|
-
let draftPreviewCommitted = false;
|
|
1014
|
-
const deliverSlackPayload = async (payload, info) => {
|
|
1015
|
-
if (payload.isReasoning === true) return { visibleReplySent: false };
|
|
1016
|
-
if (useStreaming) {
|
|
1017
|
-
await deliverWithStreaming({
|
|
1018
|
-
payload,
|
|
1019
|
-
kind: info.kind
|
|
1020
|
-
});
|
|
1021
|
-
return;
|
|
1022
|
-
}
|
|
1023
|
-
const reply = resolveSendableOutboundReplyParts(payload);
|
|
1024
|
-
const slackBlocks = readSlackReplyBlocks(payload);
|
|
1025
|
-
const ttsSupplement = getReplyPayloadTtsSupplement(payload);
|
|
1026
|
-
const trimmedFinalText = (payload.text ?? ttsSupplement?.spokenText ?? "").trim();
|
|
1027
|
-
const shouldRestoreTtsSupplementTextForPreviewFallback = Boolean(ttsSupplement) && ttsSupplement?.visibleTextAlreadyDelivered !== true && Boolean(draftStream) && !draftPreviewCommitted && !observedFinalReplyDelivery && previewStreamingEnabled && !payload.text?.trim();
|
|
1028
|
-
if (info.kind === "final" && ttsSupplement && draftStream && !draftPreviewCommitted && !observedFinalReplyDelivery && previewStreamingEnabled && !payload.isError && trimmedFinalText.length > 0) {
|
|
1029
|
-
const channelId = draftStream.channelId();
|
|
1030
|
-
const messageId = draftStream.messageId();
|
|
1031
|
-
if (channelId && messageId) {
|
|
1032
|
-
const finalThreadTs = usedReplyThreadTs ?? statusThreadTs;
|
|
1033
|
-
await draftStream.flush();
|
|
1034
|
-
await draftStream.seal();
|
|
1035
|
-
try {
|
|
1036
|
-
await finalizeSlackPreviewEdit({
|
|
1037
|
-
client: ctx.app.client,
|
|
1038
|
-
token: ctx.botToken,
|
|
1039
|
-
accountId: account.accountId,
|
|
1040
|
-
channelId,
|
|
1041
|
-
messageId,
|
|
1042
|
-
text: normalizeSlackOutboundText(trimmedFinalText),
|
|
1043
|
-
...slackBlocks?.length ? { blocks: slackBlocks } : {},
|
|
1044
|
-
threadTs: finalThreadTs
|
|
1045
|
-
});
|
|
1046
|
-
} catch (err) {
|
|
1047
|
-
logVerbose(`slack: preview final edit failed; falling back to standard send (${formatSlackError(err)})`);
|
|
1048
|
-
await draftStream.discardPending();
|
|
1049
|
-
let delivered = false;
|
|
1050
|
-
try {
|
|
1051
|
-
await deliverNormally({
|
|
1052
|
-
payload: payload.text?.trim() ? payload : {
|
|
1053
|
-
...payload,
|
|
1054
|
-
text: trimmedFinalText
|
|
1055
|
-
},
|
|
1056
|
-
kind: info.kind,
|
|
1057
|
-
forcedThreadTs: finalThreadTs
|
|
1058
|
-
});
|
|
1059
|
-
delivered = true;
|
|
1060
|
-
} finally {
|
|
1061
|
-
if (delivered) await draftStream.clear();
|
|
1062
|
-
}
|
|
1063
|
-
return;
|
|
1064
|
-
}
|
|
1065
|
-
draftPreviewCommitted = true;
|
|
1066
|
-
observedFinalReplyDelivery = true;
|
|
1067
|
-
observedReplyDelivery = true;
|
|
1068
|
-
replyPlan.markSent();
|
|
1069
|
-
await deliverNormally({
|
|
1070
|
-
payload: buildTtsSupplementMediaPayload(payload),
|
|
1071
|
-
kind: info.kind,
|
|
1072
|
-
forcedThreadTs: finalThreadTs
|
|
1073
|
-
});
|
|
1074
|
-
deliveryTracker.markDelivered({
|
|
1075
|
-
kind: info.kind,
|
|
1076
|
-
payload,
|
|
1077
|
-
threadTs: finalThreadTs
|
|
1078
|
-
});
|
|
1079
|
-
return;
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
if ((await deliverWithFinalizableLivePreviewAdapter({
|
|
1083
|
-
kind: info.kind,
|
|
1084
|
-
payload,
|
|
1085
|
-
adapter: defineFinalizableLivePreviewAdapter({
|
|
1086
|
-
draft: draftStream && !draftPreviewCommitted && !observedFinalReplyDelivery ? {
|
|
1087
|
-
flush: draftStream.flush,
|
|
1088
|
-
clear: draftStream.clear,
|
|
1089
|
-
discardPending: draftStream.discardPending,
|
|
1090
|
-
seal: draftStream.seal,
|
|
1091
|
-
id: () => {
|
|
1092
|
-
const channelId = draftStream.channelId();
|
|
1093
|
-
const messageId = draftStream.messageId();
|
|
1094
|
-
return channelId && messageId ? {
|
|
1095
|
-
channelId,
|
|
1096
|
-
messageId
|
|
1097
|
-
} : void 0;
|
|
1098
|
-
}
|
|
1099
|
-
} : void 0,
|
|
1100
|
-
buildFinalEdit: () => {
|
|
1101
|
-
if (!previewStreamingEnabled || reply.hasMedia && !ttsSupplement || payload.isError || trimmedFinalText.length === 0 && !slackBlocks?.length) return;
|
|
1102
|
-
return {
|
|
1103
|
-
text: normalizeSlackOutboundText(trimmedFinalText),
|
|
1104
|
-
blocks: slackBlocks,
|
|
1105
|
-
threadTs: usedReplyThreadTs ?? statusThreadTs
|
|
1106
|
-
};
|
|
1107
|
-
},
|
|
1108
|
-
editFinal: async (preview, edit) => {
|
|
1109
|
-
if (deliveryTracker.hasDelivered({
|
|
1110
|
-
kind: info.kind,
|
|
1111
|
-
payload,
|
|
1112
|
-
threadTs: edit.threadTs
|
|
1113
|
-
})) return;
|
|
1114
|
-
await finalizeSlackPreviewEdit({
|
|
1115
|
-
client: ctx.app.client,
|
|
1116
|
-
token: ctx.botToken,
|
|
1117
|
-
accountId: account.accountId,
|
|
1118
|
-
channelId: preview.channelId,
|
|
1119
|
-
messageId: preview.messageId,
|
|
1120
|
-
text: edit.text,
|
|
1121
|
-
...edit.blocks?.length ? { blocks: edit.blocks } : {},
|
|
1122
|
-
threadTs: edit.threadTs
|
|
1123
|
-
});
|
|
1124
|
-
draftPreviewCommitted = true;
|
|
1125
|
-
observedFinalReplyDelivery = true;
|
|
1126
|
-
},
|
|
1127
|
-
onPreviewFinalized: (_preview) => {
|
|
1128
|
-
draftPreviewCommitted = true;
|
|
1129
|
-
observedFinalReplyDelivery = true;
|
|
1130
|
-
const finalThreadTs = usedReplyThreadTs ?? statusThreadTs;
|
|
1131
|
-
observedReplyDelivery = true;
|
|
1132
|
-
replyPlan.markSent();
|
|
1133
|
-
deliveryTracker.markDelivered({
|
|
1134
|
-
kind: info.kind,
|
|
1135
|
-
payload,
|
|
1136
|
-
threadTs: finalThreadTs
|
|
1137
|
-
});
|
|
1138
|
-
},
|
|
1139
|
-
buildSupplementalPayload: () => ttsSupplement ? buildTtsSupplementMediaPayload(payload) : void 0,
|
|
1140
|
-
deliverSupplemental: async (supplementalPayload) => {
|
|
1141
|
-
await deliverNormally({
|
|
1142
|
-
payload: supplementalPayload,
|
|
1143
|
-
kind: info.kind
|
|
1144
|
-
});
|
|
1145
|
-
},
|
|
1146
|
-
logPreviewEditFailure: (err) => {
|
|
1147
|
-
logVerbose(`slack: preview final edit failed; falling back to standard send (${formatSlackError(err)})`);
|
|
1148
|
-
}
|
|
1149
|
-
}),
|
|
1150
|
-
deliverNormally: async () => {
|
|
1151
|
-
await deliverNormally({
|
|
1152
|
-
payload: shouldRestoreTtsSupplementTextForPreviewFallback ? {
|
|
1153
|
-
...payload,
|
|
1154
|
-
text: ttsSupplement?.spokenText
|
|
1155
|
-
} : payload,
|
|
1156
|
-
kind: info.kind
|
|
1157
|
-
});
|
|
1158
|
-
}
|
|
1159
|
-
})).kind === "preview-finalized") return;
|
|
1160
|
-
};
|
|
1161
|
-
const onSlackDeliveryError = (err, info) => {
|
|
1162
|
-
runtime.error?.(danger(`slack ${info.kind} reply failed: ${formatSlackError(err)}`));
|
|
1163
|
-
replyPipeline.typingCallbacks?.onIdle?.();
|
|
1164
|
-
};
|
|
1165
|
-
const draftStream = shouldUseDraftStream ? createSlackDraftStream({
|
|
1166
|
-
target: prepared.replyTarget,
|
|
1167
|
-
cfg,
|
|
1168
|
-
token: ctx.botToken,
|
|
1169
|
-
accountId: account.accountId,
|
|
1170
|
-
identity: slackIdentity,
|
|
1171
|
-
...slackMessageMetadata ? { metadata: slackMessageMetadata } : {},
|
|
1172
|
-
maxChars: Math.min(ctx.textLimit, SLACK_TEXT_LIMIT),
|
|
1173
|
-
resolveThreadTs: () => {
|
|
1174
|
-
const ts = replyPlan.peekThreadTs();
|
|
1175
|
-
if (ts) usedReplyThreadTs ??= ts;
|
|
1176
|
-
return ts;
|
|
1177
|
-
},
|
|
1178
|
-
log: logVerbose,
|
|
1179
|
-
warn: logVerbose
|
|
1180
|
-
}) : void 0;
|
|
1181
|
-
let hasStreamedMessage = false;
|
|
1182
|
-
const streamMode = slackStreaming.draftMode;
|
|
1183
|
-
const previewToolProgressEnabled = Boolean(draftStream) && resolveChannelStreamingPreviewToolProgress(account.config);
|
|
1184
|
-
const suppressDefaultToolProgressMessages = resolveChannelStreamingSuppressDefaultToolProgressMessages(account.config, {
|
|
1185
|
-
draftStreamActive: Boolean(draftStream),
|
|
1186
|
-
previewToolProgressEnabled,
|
|
1187
|
-
previewStreamingEnabled
|
|
1188
|
-
});
|
|
1189
|
-
let previewToolProgressSuppressed = false;
|
|
1190
|
-
let previewToolProgressLines = [];
|
|
1191
|
-
let appendRenderedText = "";
|
|
1192
|
-
let appendSourceText = "";
|
|
1193
|
-
let statusUpdateCount = 0;
|
|
1194
|
-
const progressSeed = `${account.accountId}:${message.channel}`;
|
|
1195
|
-
const useRichProgressDraft = streamMode === "status_final" && resolveChannelProgressDraftRender(account.config) === "rich";
|
|
1196
|
-
const renderProgressDraft = () => {
|
|
1197
|
-
if (!draftStream || streamMode !== "status_final") return;
|
|
1198
|
-
const previewText = formatChannelProgressDraftText({
|
|
1199
|
-
entry: account.config,
|
|
1200
|
-
lines: previewToolProgressLines,
|
|
1201
|
-
seed: progressSeed,
|
|
1202
|
-
formatLine: escapeSlackMrkdwn
|
|
1203
|
-
});
|
|
1204
|
-
if (!previewText) return;
|
|
1205
|
-
draftStream.update(useRichProgressDraft ? {
|
|
1206
|
-
text: previewText,
|
|
1207
|
-
blocks: buildSlackProgressDraftBlocks({
|
|
1208
|
-
label: resolveChannelProgressDraftLabel({
|
|
1209
|
-
entry: account.config,
|
|
1210
|
-
seed: progressSeed
|
|
1211
|
-
}),
|
|
1212
|
-
lines: previewToolProgressLines,
|
|
1213
|
-
maxLineChars: resolveChannelProgressDraftMaxLineChars(account.config)
|
|
1214
|
-
})
|
|
1215
|
-
} : previewText);
|
|
1216
|
-
hasStreamedMessage = true;
|
|
1217
|
-
};
|
|
1218
|
-
const progressDraftGate = createChannelProgressDraftGate({ onStart: renderProgressDraft });
|
|
1219
|
-
const pushPreviewToolProgress = async (line, options) => {
|
|
1220
|
-
if (!draftStream) return;
|
|
1221
|
-
if (options?.toolName !== void 0 && !isChannelProgressDraftWorkToolName(options.toolName)) return;
|
|
1222
|
-
const normalized = line?.text.replace(/\s+/g, " ").trim();
|
|
1223
|
-
if (!line || !normalized) {
|
|
1224
|
-
if (streamMode !== "status_final") return;
|
|
1225
|
-
const alreadyStarted = progressDraftGate.hasStarted;
|
|
1226
|
-
await progressDraftGate.noteWork();
|
|
1227
|
-
if (alreadyStarted && progressDraftGate.hasStarted) renderProgressDraft();
|
|
1228
|
-
return;
|
|
1229
|
-
}
|
|
1230
|
-
if (streamMode !== "status_final") {
|
|
1231
|
-
if (!previewToolProgressEnabled || previewToolProgressSuppressed) return;
|
|
1232
|
-
const nextLines = mergeChannelProgressDraftLine(previewToolProgressLines, line, { maxLines: resolveChannelProgressDraftMaxLines(account.config) });
|
|
1233
|
-
if (nextLines === previewToolProgressLines) return;
|
|
1234
|
-
previewToolProgressLines = nextLines;
|
|
1235
|
-
draftStream.update(formatChannelProgressDraftText({
|
|
1236
|
-
entry: account.config,
|
|
1237
|
-
lines: previewToolProgressLines,
|
|
1238
|
-
seed: progressSeed,
|
|
1239
|
-
formatLine: escapeSlackMrkdwn
|
|
1240
|
-
}));
|
|
1241
|
-
hasStreamedMessage = true;
|
|
1242
|
-
return;
|
|
1243
|
-
}
|
|
1244
|
-
if (previewToolProgressEnabled && !previewToolProgressSuppressed) previewToolProgressLines = mergeChannelProgressDraftLine(previewToolProgressLines, line, { maxLines: resolveChannelProgressDraftMaxLines(account.config) });
|
|
1245
|
-
const alreadyStarted = progressDraftGate.hasStarted;
|
|
1246
|
-
await progressDraftGate.noteWork();
|
|
1247
|
-
if (alreadyStarted && progressDraftGate.hasStarted) renderProgressDraft();
|
|
1248
|
-
};
|
|
1249
|
-
const updateDraftFromPartial = (text) => {
|
|
1250
|
-
const trimmed = text?.trimEnd();
|
|
1251
|
-
if (!trimmed) return;
|
|
1252
|
-
if (streamMode === "append") {
|
|
1253
|
-
previewToolProgressSuppressed = true;
|
|
1254
|
-
previewToolProgressLines = [];
|
|
1255
|
-
const next = applyAppendOnlyStreamUpdate({
|
|
1256
|
-
incoming: trimmed,
|
|
1257
|
-
rendered: appendRenderedText,
|
|
1258
|
-
source: appendSourceText
|
|
1259
|
-
});
|
|
1260
|
-
appendRenderedText = next.rendered;
|
|
1261
|
-
appendSourceText = next.source;
|
|
1262
|
-
if (!next.changed) return;
|
|
1263
|
-
draftStream?.update(next.rendered);
|
|
1264
|
-
hasStreamedMessage = true;
|
|
1265
|
-
return;
|
|
1266
|
-
}
|
|
1267
|
-
if (streamMode === "status_final") {
|
|
1268
|
-
if (!progressDraftGate.hasStarted) return;
|
|
1269
|
-
statusUpdateCount += 1;
|
|
1270
|
-
if (statusUpdateCount > 1 && statusUpdateCount % 4 !== 0) return;
|
|
1271
|
-
renderProgressDraft();
|
|
1272
|
-
return;
|
|
1273
|
-
}
|
|
1274
|
-
previewToolProgressSuppressed = true;
|
|
1275
|
-
previewToolProgressLines = [];
|
|
1276
|
-
draftStream?.update(trimmed);
|
|
1277
|
-
hasStreamedMessage = true;
|
|
1278
|
-
};
|
|
1279
|
-
const onDraftBoundary = !shouldUseDraftStream ? void 0 : async () => {
|
|
1280
|
-
if (hasStreamedMessage) {
|
|
1281
|
-
draftStream?.forceNewMessage();
|
|
1282
|
-
hasStreamedMessage = false;
|
|
1283
|
-
appendRenderedText = "";
|
|
1284
|
-
appendSourceText = "";
|
|
1285
|
-
statusUpdateCount = 0;
|
|
1286
|
-
}
|
|
1287
|
-
previewToolProgressSuppressed = false;
|
|
1288
|
-
previewToolProgressLines = [];
|
|
1289
|
-
};
|
|
1290
|
-
let dispatchError;
|
|
1291
|
-
let queuedFinal = false;
|
|
1292
|
-
let counts = {};
|
|
1293
|
-
try {
|
|
1294
|
-
const turnResult = await dispatchChannelInboundReply({
|
|
1295
|
-
cfg,
|
|
1296
|
-
channel: "slack",
|
|
1297
|
-
accountId: route.accountId,
|
|
1298
|
-
agentId: route.agentId,
|
|
1299
|
-
routeSessionKey: route.sessionKey,
|
|
1300
|
-
storePath: prepared.turn.storePath,
|
|
1301
|
-
ctxPayload: prepared.ctxPayload,
|
|
1302
|
-
recordInboundSession,
|
|
1303
|
-
dispatchReplyWithBufferedBlockDispatcher,
|
|
1304
|
-
dispatcherOptions: {
|
|
1305
|
-
...replyPipeline,
|
|
1306
|
-
humanDelay: resolveHumanDelayConfig(cfg, route.agentId)
|
|
1307
|
-
},
|
|
1308
|
-
delivery: {
|
|
1309
|
-
deliver: deliverSlackPayload,
|
|
1310
|
-
onError: onSlackDeliveryError
|
|
1311
|
-
},
|
|
1312
|
-
record: prepared.turn.record,
|
|
1313
|
-
history: prepared.turn.history,
|
|
1314
|
-
botLoopProtection: resolveSlackBotLoopProtection(prepared),
|
|
1315
|
-
replyOptions: {
|
|
1316
|
-
skillFilter: prepared.channelConfig?.skills,
|
|
1317
|
-
sourceReplyDeliveryMode,
|
|
1318
|
-
hasRepliedRef,
|
|
1319
|
-
disableBlockStreaming,
|
|
1320
|
-
onModelSelected,
|
|
1321
|
-
suppressDefaultToolProgressMessages: suppressDefaultToolProgressMessages ? true : void 0,
|
|
1322
|
-
onPartialReply: useStreaming ? void 0 : !previewStreamingEnabled ? void 0 : async (payload) => {
|
|
1323
|
-
updateDraftFromPartial(payload.text);
|
|
1324
|
-
},
|
|
1325
|
-
onAssistantMessageStart: onDraftBoundary,
|
|
1326
|
-
onReasoningEnd: onDraftBoundary,
|
|
1327
|
-
onReasoningStream: statusReactionsEnabled ? async () => {
|
|
1328
|
-
await statusReactions.setThinking();
|
|
1329
|
-
} : void 0,
|
|
1330
|
-
onToolStart: async (payload) => {
|
|
1331
|
-
if (statusReactionsEnabled) await statusReactions.setTool(payload.name);
|
|
1332
|
-
await pushPreviewToolProgress(buildChannelProgressDraftLineForEntry(account.config, {
|
|
1333
|
-
event: "tool",
|
|
1334
|
-
name: payload.name,
|
|
1335
|
-
phase: payload.phase,
|
|
1336
|
-
args: payload.args
|
|
1337
|
-
}, payload.detailMode ? { detailMode: payload.detailMode } : void 0), { toolName: payload.name });
|
|
1338
|
-
},
|
|
1339
|
-
onItemEvent: async (payload) => {
|
|
1340
|
-
await pushPreviewToolProgress(buildChannelProgressDraftLineForEntry(account.config, {
|
|
1341
|
-
event: "item",
|
|
1342
|
-
itemId: payload.itemId,
|
|
1343
|
-
itemKind: payload.kind,
|
|
1344
|
-
title: payload.title,
|
|
1345
|
-
name: payload.name,
|
|
1346
|
-
phase: payload.phase,
|
|
1347
|
-
status: payload.status,
|
|
1348
|
-
summary: payload.summary,
|
|
1349
|
-
progressText: payload.progressText,
|
|
1350
|
-
meta: payload.meta
|
|
1351
|
-
}));
|
|
1352
|
-
},
|
|
1353
|
-
onPlanUpdate: async (payload) => {
|
|
1354
|
-
if (payload.phase !== "update") return;
|
|
1355
|
-
await pushPreviewToolProgress(buildChannelProgressDraftLine({
|
|
1356
|
-
event: "plan",
|
|
1357
|
-
phase: payload.phase,
|
|
1358
|
-
title: payload.title,
|
|
1359
|
-
explanation: payload.explanation,
|
|
1360
|
-
steps: payload.steps
|
|
1361
|
-
}));
|
|
1362
|
-
},
|
|
1363
|
-
onApprovalEvent: async (payload) => {
|
|
1364
|
-
if (payload.phase !== "requested") return;
|
|
1365
|
-
await pushPreviewToolProgress(buildChannelProgressDraftLine({
|
|
1366
|
-
event: "approval",
|
|
1367
|
-
phase: payload.phase,
|
|
1368
|
-
title: payload.title,
|
|
1369
|
-
command: payload.command,
|
|
1370
|
-
reason: payload.reason,
|
|
1371
|
-
message: payload.message
|
|
1372
|
-
}));
|
|
1373
|
-
},
|
|
1374
|
-
onCommandOutput: async (payload) => {
|
|
1375
|
-
if (payload.phase !== "end") return;
|
|
1376
|
-
await pushPreviewToolProgress(buildChannelProgressDraftLine({
|
|
1377
|
-
event: "command-output",
|
|
1378
|
-
phase: payload.phase,
|
|
1379
|
-
title: payload.title,
|
|
1380
|
-
name: payload.name,
|
|
1381
|
-
status: payload.status,
|
|
1382
|
-
exitCode: payload.exitCode
|
|
1383
|
-
}));
|
|
1384
|
-
},
|
|
1385
|
-
onPatchSummary: async (payload) => {
|
|
1386
|
-
if (payload.phase !== "end") return;
|
|
1387
|
-
await pushPreviewToolProgress(buildChannelProgressDraftLine({
|
|
1388
|
-
event: "patch",
|
|
1389
|
-
phase: payload.phase,
|
|
1390
|
-
title: payload.title,
|
|
1391
|
-
name: payload.name,
|
|
1392
|
-
added: payload.added,
|
|
1393
|
-
modified: payload.modified,
|
|
1394
|
-
deleted: payload.deleted,
|
|
1395
|
-
summary: payload.summary
|
|
1396
|
-
}));
|
|
1397
|
-
}
|
|
1398
|
-
}
|
|
1399
|
-
});
|
|
1400
|
-
if (turnResult.dispatched) {
|
|
1401
|
-
const result = turnResult.dispatchResult;
|
|
1402
|
-
queuedFinal = result.queuedFinal;
|
|
1403
|
-
counts = result.counts;
|
|
1404
|
-
}
|
|
1405
|
-
} catch (err) {
|
|
1406
|
-
dispatchError = err;
|
|
1407
|
-
} finally {
|
|
1408
|
-
progressDraftGate.cancel();
|
|
1409
|
-
await draftStream?.discardPending();
|
|
1410
|
-
}
|
|
1411
|
-
let streamFallbackDelivered = false;
|
|
1412
|
-
const finalStream = streamSession;
|
|
1413
|
-
if (finalStream && !finalStream.stopped) try {
|
|
1414
|
-
await stopSlackStream({
|
|
1415
|
-
session: finalStream,
|
|
1416
|
-
...slackMessageMetadata ? { metadata: slackMessageMetadata } : {}
|
|
1417
|
-
});
|
|
1418
|
-
} catch (err) {
|
|
1419
|
-
if (err instanceof SlackStreamNotDeliveredError) streamFallbackDelivered = await deliverPendingStreamFallback(finalStream, err);
|
|
1420
|
-
else runtime.error?.(danger(`slack-stream: failed to stop stream: ${formatSlackError(err)}`));
|
|
1421
|
-
}
|
|
1422
|
-
const anyReplyDelivered = hasVisibleInboundReplyDispatch({
|
|
1423
|
-
queuedFinal,
|
|
1424
|
-
counts
|
|
1425
|
-
}, {
|
|
1426
|
-
observedReplyDelivery,
|
|
1427
|
-
fallbackDelivered: streamFallbackDelivered
|
|
1428
|
-
});
|
|
1429
|
-
if (statusReactionsEnabled) if (dispatchError) {
|
|
1430
|
-
await statusReactions.setError();
|
|
1431
|
-
if (ctx.removeAckAfterReply) (async () => {
|
|
1432
|
-
await sleep(statusReactionTiming.errorHoldMs);
|
|
1433
|
-
if (anyReplyDelivered) await statusReactions.clear();
|
|
1434
|
-
})();
|
|
1435
|
-
} else if (anyReplyDelivered) {
|
|
1436
|
-
await statusReactions.setDone();
|
|
1437
|
-
if (ctx.removeAckAfterReply) (async () => {
|
|
1438
|
-
await sleep(statusReactionTiming.doneHoldMs);
|
|
1439
|
-
await statusReactions.clear();
|
|
1440
|
-
})();
|
|
1441
|
-
else statusReactions.restoreInitial();
|
|
1442
|
-
} else await statusReactions.restoreInitial();
|
|
1443
|
-
if (dispatchError) throw dispatchError;
|
|
1444
|
-
const participationThreadTs = usedReplyThreadTs ?? statusThreadTs;
|
|
1445
|
-
if (anyReplyDelivered && participationThreadTs) recordSlackThreadParticipation(account.accountId, message.channel, participationThreadTs, { agentId: route.agentId });
|
|
1446
|
-
if (!anyReplyDelivered && !draftPreviewCommitted) {
|
|
1447
|
-
await draftStream?.clear();
|
|
1448
|
-
return;
|
|
1449
|
-
}
|
|
1450
|
-
if (shouldLogVerbose()) {
|
|
1451
|
-
const finalCount = counts.final;
|
|
1452
|
-
logVerbose(`slack: delivered ${finalCount} reply${finalCount === 1 ? "" : "ies"} to ${prepared.replyTarget}`);
|
|
1453
|
-
}
|
|
1454
|
-
if (!statusReactionsEnabled) removeAckReactionAfterReply({
|
|
1455
|
-
removeAfterReply: ctx.removeAckAfterReply && anyReplyDelivered,
|
|
1456
|
-
ackReactionPromise: prepared.ackReactionPromise,
|
|
1457
|
-
ackReactionValue: prepared.ackReactionValue,
|
|
1458
|
-
remove: () => removeSlackReaction(message.channel, prepared.ackReactionMessageTs ?? "", prepared.ackReactionValue, {
|
|
1459
|
-
token: ctx.botToken,
|
|
1460
|
-
client: ctx.app.client
|
|
1461
|
-
}),
|
|
1462
|
-
onError: (err) => {
|
|
1463
|
-
logAckFailure({
|
|
1464
|
-
log: logVerbose,
|
|
1465
|
-
channel: "slack",
|
|
1466
|
-
target: `${message.channel}/${message.ts}`,
|
|
1467
|
-
error: err
|
|
1468
|
-
});
|
|
1469
|
-
}
|
|
1470
|
-
});
|
|
1471
|
-
}
|
|
1472
|
-
//#endregion
|
|
1473
|
-
export { dispatchPreparedSlackMessage, prepareSlackMessage };
|