kimaki 0.4.76 → 0.4.78
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/adapter-rest-boundary.test.js +34 -0
- package/dist/agent-model.e2e.test.js +2 -20
- package/dist/cli.js +50 -13
- package/dist/commands/channel-ref.js +16 -0
- package/dist/commands/diff.js +20 -85
- package/dist/commands/merge-worktree.js +5 -17
- package/dist/commands/new-worktree.js +5 -9
- package/dist/commands/permissions.js +77 -11
- package/dist/commands/resume.js +5 -9
- package/dist/commands/screenshare.js +295 -0
- package/dist/commands/session.js +6 -17
- package/dist/critique-utils.js +95 -0
- package/dist/diff-patch-plugin.js +314 -0
- package/dist/discord-bot.js +19 -14
- package/dist/discord-js-import-boundary.test.js +62 -0
- package/dist/discord-utils.js +44 -0
- package/dist/event-stream-real-capture.e2e.test.js +2 -20
- package/dist/gateway-proxy.e2e.test.js +2 -5
- package/dist/generated/cloudflare/browser.js +17 -0
- package/dist/generated/cloudflare/client.js +34 -0
- package/dist/generated/cloudflare/commonInputTypes.js +10 -0
- package/dist/generated/cloudflare/enums.js +48 -0
- package/dist/generated/cloudflare/internal/class.js +47 -0
- package/dist/generated/cloudflare/internal/prismaNamespace.js +252 -0
- package/dist/generated/cloudflare/internal/prismaNamespaceBrowser.js +222 -0
- package/dist/generated/cloudflare/internal/query_compiler_fast_bg.js +135 -0
- package/dist/generated/cloudflare/models/bot_api_keys.js +1 -0
- package/dist/generated/cloudflare/models/bot_tokens.js +1 -0
- package/dist/generated/cloudflare/models/channel_agents.js +1 -0
- package/dist/generated/cloudflare/models/channel_directories.js +1 -0
- package/dist/generated/cloudflare/models/channel_mention_mode.js +1 -0
- package/dist/generated/cloudflare/models/channel_models.js +1 -0
- package/dist/generated/cloudflare/models/channel_verbosity.js +1 -0
- package/dist/generated/cloudflare/models/channel_worktrees.js +1 -0
- package/dist/generated/cloudflare/models/forum_sync_configs.js +1 -0
- package/dist/generated/cloudflare/models/global_models.js +1 -0
- package/dist/generated/cloudflare/models/ipc_requests.js +1 -0
- package/dist/generated/cloudflare/models/part_messages.js +1 -0
- package/dist/generated/cloudflare/models/scheduled_tasks.js +1 -0
- package/dist/generated/cloudflare/models/session_agents.js +1 -0
- package/dist/generated/cloudflare/models/session_events.js +1 -0
- package/dist/generated/cloudflare/models/session_models.js +1 -0
- package/dist/generated/cloudflare/models/session_start_sources.js +1 -0
- package/dist/generated/cloudflare/models/thread_sessions.js +1 -0
- package/dist/generated/cloudflare/models/thread_worktrees.js +1 -0
- package/dist/generated/cloudflare/models.js +1 -0
- package/dist/generated/node/browser.js +17 -0
- package/dist/generated/node/client.js +37 -0
- package/dist/generated/node/commonInputTypes.js +10 -0
- package/dist/generated/node/enums.js +48 -0
- package/dist/generated/node/internal/class.js +49 -0
- package/dist/generated/node/internal/prismaNamespace.js +252 -0
- package/dist/generated/node/internal/prismaNamespaceBrowser.js +222 -0
- package/dist/generated/node/models/bot_api_keys.js +1 -0
- package/dist/generated/node/models/bot_tokens.js +1 -0
- package/dist/generated/node/models/channel_agents.js +1 -0
- package/dist/generated/node/models/channel_directories.js +1 -0
- package/dist/generated/node/models/channel_mention_mode.js +1 -0
- package/dist/generated/node/models/channel_models.js +1 -0
- package/dist/generated/node/models/channel_verbosity.js +1 -0
- package/dist/generated/node/models/channel_worktrees.js +1 -0
- package/dist/generated/node/models/forum_sync_configs.js +1 -0
- package/dist/generated/node/models/global_models.js +1 -0
- package/dist/generated/node/models/ipc_requests.js +1 -0
- package/dist/generated/node/models/part_messages.js +1 -0
- package/dist/generated/node/models/scheduled_tasks.js +1 -0
- package/dist/generated/node/models/session_agents.js +1 -0
- package/dist/generated/node/models/session_events.js +1 -0
- package/dist/generated/node/models/session_models.js +1 -0
- package/dist/generated/node/models/session_start_sources.js +1 -0
- package/dist/generated/node/models/thread_sessions.js +1 -0
- package/dist/generated/node/models/thread_worktrees.js +1 -0
- package/dist/generated/node/models.js +1 -0
- package/dist/interaction-handler.js +10 -0
- package/dist/kimaki-digital-twin.e2e.test.js +2 -20
- package/dist/message-flags-boundary.test.js +54 -0
- package/dist/message-formatting.js +3 -62
- package/dist/onboarding-tutorial-plugin.js +1 -1
- package/dist/opencode-command.js +129 -0
- package/dist/opencode-command.test.js +48 -0
- package/dist/opencode-interrupt-plugin.js +19 -1
- package/dist/opencode-interrupt-plugin.test.js +0 -5
- package/dist/opencode-plugin-loading.e2e.test.js +9 -20
- package/dist/opencode-plugin.js +4 -4
- package/dist/opencode.js +150 -27
- package/dist/patch-text-parser.js +97 -0
- package/dist/platform/components-v2.js +20 -0
- package/dist/platform/discord-adapter.js +1440 -0
- package/dist/platform/discord-routes.js +31 -0
- package/dist/platform/message-flags.js +8 -0
- package/dist/platform/platform-value.js +41 -0
- package/dist/platform/slack-adapter.js +872 -0
- package/dist/platform/slack-markdown.js +169 -0
- package/dist/platform/types.js +4 -0
- package/dist/queue-advanced-e2e-setup.js +265 -0
- package/dist/queue-advanced-footer.e2e.test.js +173 -0
- package/dist/queue-advanced-model-switch.e2e.test.js +299 -0
- package/dist/queue-advanced-permissions-typing.e2e.test.js +73 -1
- package/dist/runtime-lifecycle.e2e.test.js +2 -20
- package/dist/session-handler/event-stream-state.js +5 -0
- package/dist/session-handler/event-stream-state.test.js +6 -2
- package/dist/session-handler/thread-session-runtime.js +32 -2
- package/dist/system-message.js +26 -23
- package/dist/test-utils.js +16 -0
- package/dist/thread-message-queue.e2e.test.js +2 -20
- package/dist/utils.js +3 -1
- package/dist/voice-message.e2e.test.js +2 -20
- package/dist/voice.js +122 -9
- package/dist/voice.test.js +17 -2
- package/dist/websockify.js +69 -0
- package/dist/worktree-lifecycle.e2e.test.js +308 -0
- package/package.json +4 -2
- package/skills/critique/SKILL.md +17 -0
- package/skills/egaki/SKILL.md +35 -0
- package/skills/event-sourcing-state/SKILL.md +252 -0
- package/skills/goke/SKILL.md +1 -0
- package/skills/npm-package/SKILL.md +21 -2
- package/skills/playwriter/SKILL.md +1 -1
- package/skills/x-articles/SKILL.md +554 -0
- package/src/agent-model.e2e.test.ts +4 -19
- package/src/cli.ts +60 -13
- package/src/commands/diff.ts +25 -99
- package/src/commands/merge-worktree.ts +5 -21
- package/src/commands/new-worktree.ts +5 -11
- package/src/commands/permissions.ts +100 -15
- package/src/commands/resume.ts +5 -12
- package/src/commands/screenshare.ts +354 -0
- package/src/commands/session.ts +6 -23
- package/src/critique-utils.ts +139 -0
- package/src/discord-bot.ts +20 -15
- package/src/discord-utils.ts +53 -0
- package/src/event-stream-real-capture.e2e.test.ts +4 -20
- package/src/gateway-proxy.e2e.test.ts +2 -5
- package/src/interaction-handler.ts +15 -0
- package/src/kimaki-digital-twin.e2e.test.ts +2 -21
- package/src/message-formatting.ts +3 -68
- package/src/onboarding-tutorial-plugin.ts +1 -1
- package/src/opencode-command.test.ts +70 -0
- package/src/opencode-command.ts +188 -0
- package/src/opencode-interrupt-plugin.test.ts +0 -5
- package/src/opencode-interrupt-plugin.ts +34 -1
- package/src/opencode-plugin-loading.e2e.test.ts +25 -35
- package/src/opencode-plugin.ts +5 -4
- package/src/opencode.ts +199 -32
- package/src/patch-text-parser.ts +107 -0
- package/src/queue-advanced-e2e-setup.ts +273 -0
- package/src/queue-advanced-footer.e2e.test.ts +211 -0
- package/src/queue-advanced-model-switch.e2e.test.ts +383 -0
- package/src/queue-advanced-permissions-typing.e2e.test.ts +92 -0
- package/src/runtime-lifecycle.e2e.test.ts +4 -19
- package/src/session-handler/event-stream-state.test.ts +6 -2
- package/src/session-handler/event-stream-state.ts +5 -0
- package/src/session-handler/thread-session-runtime.ts +45 -2
- package/src/system-message.ts +26 -23
- package/src/test-utils.ts +17 -0
- package/src/thread-message-queue.e2e.test.ts +2 -20
- package/src/utils.ts +3 -1
- package/src/voice-message.e2e.test.ts +3 -20
- package/src/voice.test.ts +26 -2
- package/src/voice.ts +147 -9
- package/src/websockify.ts +101 -0
- package/src/worktree-lifecycle.e2e.test.ts +391 -0
|
@@ -0,0 +1,872 @@
|
|
|
1
|
+
// Slack adapter for Kimaki's platform interface.
|
|
2
|
+
// Owns Slack Web API message/thread operations and stores webhook-dispatched
|
|
3
|
+
// callback handlers for Slack messages, commands, buttons, selects, and modals.
|
|
4
|
+
import { WebClient } from '@slack/web-api';
|
|
5
|
+
import { renderSlackMessage } from './slack-markdown.js';
|
|
6
|
+
function normalizeReplyText(options) {
|
|
7
|
+
if (typeof options === 'string') {
|
|
8
|
+
return options;
|
|
9
|
+
}
|
|
10
|
+
return options.content || '';
|
|
11
|
+
}
|
|
12
|
+
function wrapSlackUser(user) {
|
|
13
|
+
const username = user.displayName || user.username || user.id;
|
|
14
|
+
return {
|
|
15
|
+
id: user.id,
|
|
16
|
+
username,
|
|
17
|
+
displayName: username,
|
|
18
|
+
globalName: user.displayName || user.username || user.id,
|
|
19
|
+
bot: user.bot ?? false,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function normalizeReactionName(emoji) {
|
|
23
|
+
const trimmed = emoji.trim();
|
|
24
|
+
if (trimmed.startsWith(':') && trimmed.endsWith(':')) {
|
|
25
|
+
return trimmed.slice(1, -1);
|
|
26
|
+
}
|
|
27
|
+
if (/^[a-z0-9_+-]+$/i.test(trimmed)) {
|
|
28
|
+
return trimmed;
|
|
29
|
+
}
|
|
30
|
+
if (trimmed === '🌳') {
|
|
31
|
+
return 'deciduous_tree';
|
|
32
|
+
}
|
|
33
|
+
return trimmed;
|
|
34
|
+
}
|
|
35
|
+
function flattenModalValues(payload) {
|
|
36
|
+
const values = {};
|
|
37
|
+
for (const blockValues of Object.values(payload.view.state.values)) {
|
|
38
|
+
for (const [actionId, input] of Object.entries(blockValues)) {
|
|
39
|
+
const currentValues = [
|
|
40
|
+
...(input.value ? [input.value] : []),
|
|
41
|
+
...(input.selected_option?.value ? [input.selected_option.value] : []),
|
|
42
|
+
...(input.selected_options || []).map((option) => {
|
|
43
|
+
return option.value;
|
|
44
|
+
}),
|
|
45
|
+
];
|
|
46
|
+
values[actionId] = currentValues;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return values;
|
|
50
|
+
}
|
|
51
|
+
function getModalMetadataValue({ privateMetadata, key, }) {
|
|
52
|
+
if (!privateMetadata) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
const params = new URLSearchParams(privateMetadata);
|
|
56
|
+
return params.get(key) || undefined;
|
|
57
|
+
}
|
|
58
|
+
function wrapSlackMessage(message) {
|
|
59
|
+
if (!message.ts || !message.channel) {
|
|
60
|
+
throw new Error('Slack message is missing ts or channel');
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
id: message.ts,
|
|
64
|
+
content: message.text || null,
|
|
65
|
+
channelId: message.channel,
|
|
66
|
+
author: {
|
|
67
|
+
id: message.user || message.bot_id || 'unknown',
|
|
68
|
+
username: message.username || message.user || message.bot_id || 'unknown',
|
|
69
|
+
displayName: message.username || message.user || message.bot_id || 'unknown',
|
|
70
|
+
globalName: message.username || message.user || message.bot_id || 'unknown',
|
|
71
|
+
bot: Boolean(message.bot_id),
|
|
72
|
+
},
|
|
73
|
+
attachments: new Map((message.files || []).flatMap((file) => {
|
|
74
|
+
if (!file.id || !file.url_private) {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
return [
|
|
78
|
+
[
|
|
79
|
+
file.id,
|
|
80
|
+
{
|
|
81
|
+
url: file.url_private,
|
|
82
|
+
contentType: file.mimetype,
|
|
83
|
+
name: file.name,
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
];
|
|
87
|
+
})),
|
|
88
|
+
embeds: [],
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function wrapSlackThread(input) {
|
|
92
|
+
return {
|
|
93
|
+
id: input.threadId,
|
|
94
|
+
name: input.name,
|
|
95
|
+
kind: 'thread',
|
|
96
|
+
type: 'thread',
|
|
97
|
+
parentId: input.channelId,
|
|
98
|
+
guildId: null,
|
|
99
|
+
isThread() {
|
|
100
|
+
return true;
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function createSlackModalView({ channelId, modal, threadId, }) {
|
|
105
|
+
const privateMetadata = new URLSearchParams({
|
|
106
|
+
channelId,
|
|
107
|
+
...(threadId ? { threadId } : {}),
|
|
108
|
+
}).toString();
|
|
109
|
+
return {
|
|
110
|
+
type: 'modal',
|
|
111
|
+
callback_id: modal.id,
|
|
112
|
+
private_metadata: privateMetadata,
|
|
113
|
+
title: {
|
|
114
|
+
type: 'plain_text',
|
|
115
|
+
text: modal.title.slice(0, 24),
|
|
116
|
+
emoji: true,
|
|
117
|
+
},
|
|
118
|
+
submit: {
|
|
119
|
+
type: 'plain_text',
|
|
120
|
+
text: 'Submit',
|
|
121
|
+
emoji: true,
|
|
122
|
+
},
|
|
123
|
+
close: {
|
|
124
|
+
type: 'plain_text',
|
|
125
|
+
text: 'Cancel',
|
|
126
|
+
emoji: true,
|
|
127
|
+
},
|
|
128
|
+
blocks: modal.inputs.map((input) => {
|
|
129
|
+
if (input.type === 'text') {
|
|
130
|
+
return {
|
|
131
|
+
type: 'input',
|
|
132
|
+
block_id: input.id,
|
|
133
|
+
label: {
|
|
134
|
+
type: 'plain_text',
|
|
135
|
+
text: input.label.slice(0, 200),
|
|
136
|
+
emoji: true,
|
|
137
|
+
},
|
|
138
|
+
optional: !(input.required ?? true),
|
|
139
|
+
element: {
|
|
140
|
+
type: input.style === 'paragraph' ? 'plain_text_input' : 'plain_text_input',
|
|
141
|
+
action_id: input.id,
|
|
142
|
+
multiline: input.style === 'paragraph',
|
|
143
|
+
placeholder: input.placeholder
|
|
144
|
+
? {
|
|
145
|
+
type: 'plain_text',
|
|
146
|
+
text: input.placeholder.slice(0, 150),
|
|
147
|
+
emoji: true,
|
|
148
|
+
}
|
|
149
|
+
: undefined,
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
type: 'input',
|
|
155
|
+
block_id: input.id,
|
|
156
|
+
label: {
|
|
157
|
+
type: 'plain_text',
|
|
158
|
+
text: input.label.slice(0, 200),
|
|
159
|
+
emoji: true,
|
|
160
|
+
},
|
|
161
|
+
optional: false,
|
|
162
|
+
hint: input.description
|
|
163
|
+
? {
|
|
164
|
+
type: 'plain_text',
|
|
165
|
+
text: input.description.slice(0, 150),
|
|
166
|
+
emoji: true,
|
|
167
|
+
}
|
|
168
|
+
: undefined,
|
|
169
|
+
element: {
|
|
170
|
+
type: 'file_input',
|
|
171
|
+
action_id: input.id,
|
|
172
|
+
max_files: input.maxFiles,
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
export class SlackAdapter {
|
|
179
|
+
name = 'slack';
|
|
180
|
+
admin = {
|
|
181
|
+
listGuilds: async () => {
|
|
182
|
+
return [];
|
|
183
|
+
},
|
|
184
|
+
resolveGuild: async () => {
|
|
185
|
+
return null;
|
|
186
|
+
},
|
|
187
|
+
registerCommands: async () => {
|
|
188
|
+
throw new Error('Slack adapter does not support Discord slash command registration');
|
|
189
|
+
},
|
|
190
|
+
ensureGuildAccessPolicy: async () => { },
|
|
191
|
+
listChannels: async () => {
|
|
192
|
+
return [];
|
|
193
|
+
},
|
|
194
|
+
createCategory: async () => {
|
|
195
|
+
throw new Error('Slack adapter does not support guild category creation');
|
|
196
|
+
},
|
|
197
|
+
createTextChannel: async () => {
|
|
198
|
+
throw new Error('Slack adapter does not support guild text channel creation');
|
|
199
|
+
},
|
|
200
|
+
fetchChannel: async () => {
|
|
201
|
+
return null;
|
|
202
|
+
},
|
|
203
|
+
fetchChannelById: async () => {
|
|
204
|
+
return null;
|
|
205
|
+
},
|
|
206
|
+
deleteChannel: async () => {
|
|
207
|
+
return 'missing';
|
|
208
|
+
},
|
|
209
|
+
listGuildMembers: async () => {
|
|
210
|
+
return [];
|
|
211
|
+
},
|
|
212
|
+
searchGuildMembers: async () => {
|
|
213
|
+
return [];
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
content = {
|
|
217
|
+
resolveMentions: async (message) => {
|
|
218
|
+
return message.content || '';
|
|
219
|
+
},
|
|
220
|
+
getTextAttachments: async (_message) => {
|
|
221
|
+
return '';
|
|
222
|
+
},
|
|
223
|
+
getFileAttachments: async (_message) => {
|
|
224
|
+
return [];
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
permissions = {
|
|
228
|
+
getMessageAccess: async () => {
|
|
229
|
+
return 'allowed';
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
client;
|
|
233
|
+
appId = 'slack';
|
|
234
|
+
botUserId;
|
|
235
|
+
threadChannelIds = new Map();
|
|
236
|
+
readyHandlers = [];
|
|
237
|
+
messageHandlers = [];
|
|
238
|
+
threadCreateHandlers = [];
|
|
239
|
+
threadDeleteHandlers = [];
|
|
240
|
+
commandHandlers = [];
|
|
241
|
+
autocompleteHandlers = [];
|
|
242
|
+
buttonHandlers = [];
|
|
243
|
+
selectMenuHandlers = [];
|
|
244
|
+
modalSubmitHandlers = [];
|
|
245
|
+
errorHandlers = [];
|
|
246
|
+
constructor({ client } = {}) {
|
|
247
|
+
this.client = client || new WebClient();
|
|
248
|
+
}
|
|
249
|
+
async login(token) {
|
|
250
|
+
this.client = new WebClient(token);
|
|
251
|
+
const auth = await this.client.auth.test();
|
|
252
|
+
this.botUserId = auth.user_id || undefined;
|
|
253
|
+
this.appId = auth.bot_id || auth.team_id || 'slack';
|
|
254
|
+
await this.emitReady();
|
|
255
|
+
}
|
|
256
|
+
destroy() { }
|
|
257
|
+
conversation(target) {
|
|
258
|
+
return {
|
|
259
|
+
target,
|
|
260
|
+
send: async (message) => {
|
|
261
|
+
return this.postUiMessage({
|
|
262
|
+
channelId: target.channelId,
|
|
263
|
+
message,
|
|
264
|
+
threadId: target.threadId || message.replyToMessageId,
|
|
265
|
+
});
|
|
266
|
+
},
|
|
267
|
+
update: async (messageId, message) => {
|
|
268
|
+
await this.updateUiMessage({
|
|
269
|
+
channelId: target.channelId,
|
|
270
|
+
message,
|
|
271
|
+
messageId,
|
|
272
|
+
});
|
|
273
|
+
},
|
|
274
|
+
delete: async (messageId) => {
|
|
275
|
+
await this.client.chat.delete({ channel: target.channelId, ts: messageId });
|
|
276
|
+
},
|
|
277
|
+
message: async (messageId) => {
|
|
278
|
+
const data = await this.fetchMessage(target, messageId);
|
|
279
|
+
return {
|
|
280
|
+
data,
|
|
281
|
+
startThread: async ({ name, autoArchiveDuration, reason, }) => {
|
|
282
|
+
return this.createThreadFromMessage({
|
|
283
|
+
message: data,
|
|
284
|
+
name,
|
|
285
|
+
autoArchiveDuration,
|
|
286
|
+
reason,
|
|
287
|
+
});
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
},
|
|
291
|
+
startTyping: async () => { },
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
async channel(channelId) {
|
|
295
|
+
return {
|
|
296
|
+
data: {
|
|
297
|
+
id: channelId,
|
|
298
|
+
kind: 'text',
|
|
299
|
+
type: 'text',
|
|
300
|
+
parentId: null,
|
|
301
|
+
guildId: null,
|
|
302
|
+
isThread() {
|
|
303
|
+
return false;
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
conversation: () => {
|
|
307
|
+
return this.conversation({ channelId });
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
async thread({ threadId, parentId }) {
|
|
312
|
+
const channelId = parentId || this.threadChannelIds.get(threadId);
|
|
313
|
+
if (!channelId) {
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
const data = wrapSlackThread({ channelId, threadId, name: threadId });
|
|
317
|
+
return {
|
|
318
|
+
data,
|
|
319
|
+
conversation: () => {
|
|
320
|
+
return this.conversation({ channelId, threadId });
|
|
321
|
+
},
|
|
322
|
+
message: async (messageId) => {
|
|
323
|
+
return this.conversation({ channelId, threadId }).message(messageId);
|
|
324
|
+
},
|
|
325
|
+
starterMessage: async () => {
|
|
326
|
+
return this.fetchStarterMessage(threadId);
|
|
327
|
+
},
|
|
328
|
+
rename: async (_name) => { },
|
|
329
|
+
archive: async () => { },
|
|
330
|
+
addMember: async (_userId) => { },
|
|
331
|
+
addStarterReaction: async (emoji) => {
|
|
332
|
+
await this.addThreadStarterReaction({ channelId, threadId }, emoji);
|
|
333
|
+
},
|
|
334
|
+
reference: () => {
|
|
335
|
+
return `#${threadId}`;
|
|
336
|
+
},
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
rememberThread({ channelId, threadId }) {
|
|
340
|
+
this.threadChannelIds.set(threadId, channelId);
|
|
341
|
+
}
|
|
342
|
+
async emitReady() {
|
|
343
|
+
for (const handler of this.readyHandlers) {
|
|
344
|
+
try {
|
|
345
|
+
await handler();
|
|
346
|
+
}
|
|
347
|
+
catch (error) {
|
|
348
|
+
this.emitError(error);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
emitError(error) {
|
|
353
|
+
const normalizedError = error instanceof Error ? error : new Error(String(error));
|
|
354
|
+
for (const handler of this.errorHandlers) {
|
|
355
|
+
handler(normalizedError);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
async postUiMessage({ channelId, message, threadId, }) {
|
|
359
|
+
const rendered = renderSlackMessage(message);
|
|
360
|
+
const response = await this.client.chat.postMessage({
|
|
361
|
+
channel: channelId,
|
|
362
|
+
text: rendered.text,
|
|
363
|
+
blocks: rendered.blocks,
|
|
364
|
+
thread_ts: threadId,
|
|
365
|
+
});
|
|
366
|
+
if (!response.ts) {
|
|
367
|
+
throw new Error('Slack postMessage did not return a timestamp');
|
|
368
|
+
}
|
|
369
|
+
if (threadId) {
|
|
370
|
+
this.rememberThread({ channelId, threadId });
|
|
371
|
+
}
|
|
372
|
+
return { id: response.ts };
|
|
373
|
+
}
|
|
374
|
+
async updateUiMessage({ channelId, message, messageId, }) {
|
|
375
|
+
const rendered = renderSlackMessage(message);
|
|
376
|
+
await this.client.chat.update({
|
|
377
|
+
channel: channelId,
|
|
378
|
+
ts: messageId,
|
|
379
|
+
text: rendered.text,
|
|
380
|
+
blocks: rendered.blocks,
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
async fetchMessage(target, messageId) {
|
|
384
|
+
if (target.threadId && target.threadId !== messageId) {
|
|
385
|
+
const replies = await this.client.conversations.replies({
|
|
386
|
+
channel: target.channelId,
|
|
387
|
+
ts: target.threadId,
|
|
388
|
+
inclusive: true,
|
|
389
|
+
latest: messageId,
|
|
390
|
+
oldest: messageId,
|
|
391
|
+
});
|
|
392
|
+
const message = replies.messages?.find((candidate) => {
|
|
393
|
+
return candidate.ts === messageId;
|
|
394
|
+
});
|
|
395
|
+
if (!message) {
|
|
396
|
+
throw new Error(`Slack message not found: ${messageId}`);
|
|
397
|
+
}
|
|
398
|
+
this.rememberThread({ channelId: target.channelId, threadId: target.threadId });
|
|
399
|
+
return wrapSlackMessage(message);
|
|
400
|
+
}
|
|
401
|
+
const history = await this.client.conversations.history({
|
|
402
|
+
channel: target.channelId,
|
|
403
|
+
inclusive: true,
|
|
404
|
+
latest: messageId,
|
|
405
|
+
oldest: messageId,
|
|
406
|
+
limit: 1,
|
|
407
|
+
});
|
|
408
|
+
const message = history.messages?.[0];
|
|
409
|
+
if (!message) {
|
|
410
|
+
throw new Error(`Slack message not found: ${messageId}`);
|
|
411
|
+
}
|
|
412
|
+
return wrapSlackMessage({
|
|
413
|
+
...message,
|
|
414
|
+
channel: target.channelId,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
async startTyping(_target) { }
|
|
418
|
+
async renameThread(_threadId, _name) { }
|
|
419
|
+
async createThreadFromMessage(input) {
|
|
420
|
+
const rootTs = input.message.id;
|
|
421
|
+
const channelId = input.message.channelId;
|
|
422
|
+
await this.postUiMessage({
|
|
423
|
+
channelId,
|
|
424
|
+
threadId: rootTs,
|
|
425
|
+
message: {
|
|
426
|
+
markdown: input.name,
|
|
427
|
+
},
|
|
428
|
+
});
|
|
429
|
+
this.rememberThread({ channelId, threadId: rootTs });
|
|
430
|
+
return {
|
|
431
|
+
thread: wrapSlackThread({
|
|
432
|
+
channelId,
|
|
433
|
+
threadId: rootTs,
|
|
434
|
+
name: input.name,
|
|
435
|
+
}),
|
|
436
|
+
target: {
|
|
437
|
+
channelId,
|
|
438
|
+
threadId: rootTs,
|
|
439
|
+
},
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
async createThread(input) {
|
|
443
|
+
const message = await this.fetchMessage({ channelId: input.channelId }, input.messageId);
|
|
444
|
+
return this.createThreadFromMessage({
|
|
445
|
+
message,
|
|
446
|
+
name: input.name,
|
|
447
|
+
autoArchiveDuration: input.autoArchiveDuration,
|
|
448
|
+
reason: input.reason,
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
async addThreadMember(_threadId, _userId) { }
|
|
452
|
+
async addThreadStarterReaction(input, emoji) {
|
|
453
|
+
this.rememberThread({ channelId: input.channelId, threadId: input.threadId });
|
|
454
|
+
await this.client.reactions.add({
|
|
455
|
+
channel: input.channelId,
|
|
456
|
+
name: normalizeReactionName(emoji),
|
|
457
|
+
timestamp: input.threadId,
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
async fetchStarterMessage(threadId) {
|
|
461
|
+
const channelId = this.threadChannelIds.get(threadId);
|
|
462
|
+
if (!channelId) {
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
const replies = await this.client.conversations.replies({
|
|
466
|
+
channel: channelId,
|
|
467
|
+
ts: threadId,
|
|
468
|
+
inclusive: true,
|
|
469
|
+
latest: threadId,
|
|
470
|
+
oldest: threadId,
|
|
471
|
+
});
|
|
472
|
+
const starter = replies.messages?.find((message) => {
|
|
473
|
+
return message.ts === threadId;
|
|
474
|
+
});
|
|
475
|
+
if (!starter) {
|
|
476
|
+
return null;
|
|
477
|
+
}
|
|
478
|
+
return wrapSlackMessage({
|
|
479
|
+
...starter,
|
|
480
|
+
channel: channelId,
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
onReady(handler) {
|
|
484
|
+
this.readyHandlers.push(handler);
|
|
485
|
+
}
|
|
486
|
+
onMessage(handler) {
|
|
487
|
+
this.messageHandlers.push(handler);
|
|
488
|
+
}
|
|
489
|
+
onThreadCreate(handler) {
|
|
490
|
+
this.threadCreateHandlers.push(handler);
|
|
491
|
+
}
|
|
492
|
+
onThreadDelete(handler) {
|
|
493
|
+
this.threadDeleteHandlers.push(handler);
|
|
494
|
+
}
|
|
495
|
+
onCommand(handler) {
|
|
496
|
+
this.commandHandlers.push(handler);
|
|
497
|
+
}
|
|
498
|
+
onAutocomplete(handler) {
|
|
499
|
+
this.autocompleteHandlers.push(handler);
|
|
500
|
+
}
|
|
501
|
+
onButton(handler) {
|
|
502
|
+
this.buttonHandlers.push(handler);
|
|
503
|
+
}
|
|
504
|
+
onSelectMenu(handler) {
|
|
505
|
+
this.selectMenuHandlers.push(handler);
|
|
506
|
+
}
|
|
507
|
+
onModalSubmit(handler) {
|
|
508
|
+
this.modalSubmitHandlers.push(handler);
|
|
509
|
+
}
|
|
510
|
+
onError(handler) {
|
|
511
|
+
this.errorHandlers.push(handler);
|
|
512
|
+
}
|
|
513
|
+
// TODO: Verify forwarded Slack webhook requests with the planned JWT-like
|
|
514
|
+
// gateway token before dispatching any stored handlers.
|
|
515
|
+
async dispatchIncomingMessage(payload) {
|
|
516
|
+
if (payload.thread_ts) {
|
|
517
|
+
this.rememberThread({ channelId: payload.channel, threadId: payload.thread_ts });
|
|
518
|
+
}
|
|
519
|
+
const target = payload.thread_ts
|
|
520
|
+
? { channelId: payload.channel, threadId: payload.thread_ts }
|
|
521
|
+
: { channelId: payload.channel };
|
|
522
|
+
const isMention = payload.type === 'app_mention' ||
|
|
523
|
+
Boolean(this.botUserId && payload.text?.includes(`<@${this.botUserId}>`));
|
|
524
|
+
const event = {
|
|
525
|
+
message: wrapSlackMessage({
|
|
526
|
+
...payload,
|
|
527
|
+
channel: payload.channel,
|
|
528
|
+
}),
|
|
529
|
+
conversation: this.conversation(target),
|
|
530
|
+
thread: payload.thread_ts
|
|
531
|
+
? wrapSlackThread({
|
|
532
|
+
channelId: payload.channel,
|
|
533
|
+
threadId: payload.thread_ts,
|
|
534
|
+
name: payload.thread_ts,
|
|
535
|
+
})
|
|
536
|
+
: undefined,
|
|
537
|
+
kind: payload.thread_ts ? 'thread' : 'channel',
|
|
538
|
+
isMention,
|
|
539
|
+
};
|
|
540
|
+
for (const handler of this.messageHandlers) {
|
|
541
|
+
try {
|
|
542
|
+
await handler(event);
|
|
543
|
+
}
|
|
544
|
+
catch (error) {
|
|
545
|
+
this.emitError(error);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
async dispatchThreadCreate(payload) {
|
|
550
|
+
this.rememberThread({ channelId: payload.channelId, threadId: payload.threadId });
|
|
551
|
+
const thread = wrapSlackThread({
|
|
552
|
+
channelId: payload.channelId,
|
|
553
|
+
threadId: payload.threadId,
|
|
554
|
+
name: payload.threadName || payload.threadId,
|
|
555
|
+
});
|
|
556
|
+
const threadHandle = await this.thread({
|
|
557
|
+
threadId: payload.threadId,
|
|
558
|
+
parentId: payload.channelId,
|
|
559
|
+
});
|
|
560
|
+
if (!threadHandle) {
|
|
561
|
+
throw new Error(`Slack thread not found: ${payload.threadId}`);
|
|
562
|
+
}
|
|
563
|
+
const event = {
|
|
564
|
+
thread,
|
|
565
|
+
threadHandle,
|
|
566
|
+
conversation: this.conversation({
|
|
567
|
+
channelId: payload.channelId,
|
|
568
|
+
threadId: payload.threadId,
|
|
569
|
+
}),
|
|
570
|
+
newlyCreated: payload.newlyCreated,
|
|
571
|
+
};
|
|
572
|
+
for (const handler of this.threadCreateHandlers) {
|
|
573
|
+
try {
|
|
574
|
+
await handler(event);
|
|
575
|
+
}
|
|
576
|
+
catch (error) {
|
|
577
|
+
this.emitError(error);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
dispatchThreadDelete(threadId) {
|
|
582
|
+
this.threadChannelIds.delete(threadId);
|
|
583
|
+
for (const handler of this.threadDeleteHandlers) {
|
|
584
|
+
try {
|
|
585
|
+
handler(threadId);
|
|
586
|
+
}
|
|
587
|
+
catch (error) {
|
|
588
|
+
this.emitError(error);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
async replyFromContext({ context, options, }) {
|
|
593
|
+
await this.postUiMessage({
|
|
594
|
+
channelId: context.channelId,
|
|
595
|
+
threadId: context.threadId,
|
|
596
|
+
message: {
|
|
597
|
+
markdown: normalizeReplyText(options),
|
|
598
|
+
},
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
async editFromContext({ context, options, }) {
|
|
602
|
+
if (!context.messageTs) {
|
|
603
|
+
await this.replyFromContext({ context, options });
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
await this.client.chat.update({
|
|
607
|
+
channel: context.channelId,
|
|
608
|
+
ts: context.messageTs,
|
|
609
|
+
text: normalizeReplyText(options),
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
async showModalFromContext({ context, modal, }) {
|
|
613
|
+
if (!context.triggerId) {
|
|
614
|
+
throw new Error('Slack modal requires a trigger ID');
|
|
615
|
+
}
|
|
616
|
+
await this.client.views.open({
|
|
617
|
+
trigger_id: context.triggerId,
|
|
618
|
+
view: createSlackModalView({
|
|
619
|
+
channelId: context.channelId,
|
|
620
|
+
modal,
|
|
621
|
+
threadId: context.threadId,
|
|
622
|
+
}),
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
createInteractionContextFromActions(payload) {
|
|
626
|
+
const channelId = payload.channel?.id || payload.container?.channel_id;
|
|
627
|
+
if (!channelId) {
|
|
628
|
+
throw new Error('Slack block action is missing a channel ID');
|
|
629
|
+
}
|
|
630
|
+
const threadId = payload.message?.thread_ts || payload.container?.thread_ts;
|
|
631
|
+
if (threadId) {
|
|
632
|
+
this.rememberThread({ channelId, threadId });
|
|
633
|
+
}
|
|
634
|
+
return {
|
|
635
|
+
channelId,
|
|
636
|
+
messageTs: payload.message?.ts || payload.container?.message_ts,
|
|
637
|
+
responseUrl: payload.response_url,
|
|
638
|
+
threadId,
|
|
639
|
+
triggerId: payload.trigger_id,
|
|
640
|
+
user: payload.user,
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
async dispatchBlockActions(payload) {
|
|
644
|
+
const context = this.createInteractionContextFromActions(payload);
|
|
645
|
+
for (const action of payload.actions) {
|
|
646
|
+
const shared = {
|
|
647
|
+
raw: payload,
|
|
648
|
+
appId: this.appId,
|
|
649
|
+
channelId: context.channelId,
|
|
650
|
+
messageTs: context.messageTs,
|
|
651
|
+
responseUrl: context.responseUrl,
|
|
652
|
+
threadId: context.threadId,
|
|
653
|
+
triggerId: context.triggerId,
|
|
654
|
+
user: wrapSlackUser({
|
|
655
|
+
id: payload.user.id,
|
|
656
|
+
username: payload.user.username || payload.user.name || payload.user.id,
|
|
657
|
+
displayName: payload.user.name || payload.user.username || payload.user.id,
|
|
658
|
+
}),
|
|
659
|
+
reply: async (options) => {
|
|
660
|
+
await this.replyFromContext({ context, options });
|
|
661
|
+
},
|
|
662
|
+
replyUi: async (message) => {
|
|
663
|
+
await this.postUiMessage({
|
|
664
|
+
channelId: context.channelId,
|
|
665
|
+
threadId: context.threadId,
|
|
666
|
+
message,
|
|
667
|
+
});
|
|
668
|
+
},
|
|
669
|
+
editReply: async (options) => {
|
|
670
|
+
await this.editFromContext({ context, options });
|
|
671
|
+
},
|
|
672
|
+
editUiReply: async (message) => {
|
|
673
|
+
if (!context.messageTs) {
|
|
674
|
+
await this.postUiMessage({
|
|
675
|
+
channelId: context.channelId,
|
|
676
|
+
threadId: context.threadId,
|
|
677
|
+
message,
|
|
678
|
+
});
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
await this.updateUiMessage({
|
|
682
|
+
channelId: context.channelId,
|
|
683
|
+
message,
|
|
684
|
+
messageId: context.messageTs,
|
|
685
|
+
});
|
|
686
|
+
},
|
|
687
|
+
followUp: async (options) => {
|
|
688
|
+
await this.replyFromContext({ context, options });
|
|
689
|
+
},
|
|
690
|
+
update: async (options) => {
|
|
691
|
+
await this.editFromContext({ context, options });
|
|
692
|
+
},
|
|
693
|
+
updateUi: async (message) => {
|
|
694
|
+
if (!context.messageTs) {
|
|
695
|
+
await this.postUiMessage({
|
|
696
|
+
channelId: context.channelId,
|
|
697
|
+
threadId: context.threadId,
|
|
698
|
+
message,
|
|
699
|
+
});
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
await this.updateUiMessage({
|
|
703
|
+
channelId: context.channelId,
|
|
704
|
+
message,
|
|
705
|
+
messageId: context.messageTs,
|
|
706
|
+
});
|
|
707
|
+
},
|
|
708
|
+
showModal: async (modal) => {
|
|
709
|
+
await this.showModalFromContext({ context, modal });
|
|
710
|
+
},
|
|
711
|
+
};
|
|
712
|
+
if (action.type === 'static_select' || action.type === 'multi_static_select') {
|
|
713
|
+
const event = {
|
|
714
|
+
...shared,
|
|
715
|
+
customId: action.action_id,
|
|
716
|
+
values: [
|
|
717
|
+
...(action.selected_option?.value ? [action.selected_option.value] : []),
|
|
718
|
+
...(action.selected_options || []).map((option) => {
|
|
719
|
+
return option.value;
|
|
720
|
+
}),
|
|
721
|
+
],
|
|
722
|
+
};
|
|
723
|
+
for (const handler of this.selectMenuHandlers) {
|
|
724
|
+
try {
|
|
725
|
+
await handler(event);
|
|
726
|
+
}
|
|
727
|
+
catch (error) {
|
|
728
|
+
this.emitError(error);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
continue;
|
|
732
|
+
}
|
|
733
|
+
const event = {
|
|
734
|
+
...shared,
|
|
735
|
+
customId: action.action_id,
|
|
736
|
+
};
|
|
737
|
+
for (const handler of this.buttonHandlers) {
|
|
738
|
+
try {
|
|
739
|
+
await handler(event);
|
|
740
|
+
}
|
|
741
|
+
catch (error) {
|
|
742
|
+
this.emitError(error);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
async dispatchModalSubmit(payload) {
|
|
748
|
+
const values = flattenModalValues(payload);
|
|
749
|
+
const channelId = getModalMetadataValue({
|
|
750
|
+
privateMetadata: payload.view.private_metadata,
|
|
751
|
+
key: 'channelId',
|
|
752
|
+
});
|
|
753
|
+
const threadId = getModalMetadataValue({
|
|
754
|
+
privateMetadata: payload.view.private_metadata,
|
|
755
|
+
key: 'threadId',
|
|
756
|
+
});
|
|
757
|
+
if (channelId && threadId) {
|
|
758
|
+
this.rememberThread({ channelId, threadId });
|
|
759
|
+
}
|
|
760
|
+
const context = {
|
|
761
|
+
channelId: channelId || payload.user.id,
|
|
762
|
+
threadId,
|
|
763
|
+
triggerId: payload.trigger_id,
|
|
764
|
+
user: payload.user,
|
|
765
|
+
};
|
|
766
|
+
const event = {
|
|
767
|
+
raw: payload,
|
|
768
|
+
appId: this.appId,
|
|
769
|
+
channelId: channelId || null,
|
|
770
|
+
customId: payload.view.callback_id,
|
|
771
|
+
triggerId: payload.trigger_id,
|
|
772
|
+
user: wrapSlackUser({
|
|
773
|
+
id: payload.user.id,
|
|
774
|
+
username: payload.user.username || payload.user.name || payload.user.id,
|
|
775
|
+
displayName: payload.user.name || payload.user.username || payload.user.id,
|
|
776
|
+
}),
|
|
777
|
+
values,
|
|
778
|
+
fields: {
|
|
779
|
+
getTextInputValue: (id) => {
|
|
780
|
+
return values[id]?.[0] || '';
|
|
781
|
+
},
|
|
782
|
+
},
|
|
783
|
+
reply: async (options) => {
|
|
784
|
+
await this.replyFromContext({ context, options });
|
|
785
|
+
},
|
|
786
|
+
replyUi: async (message) => {
|
|
787
|
+
await this.postUiMessage({
|
|
788
|
+
channelId: context.channelId,
|
|
789
|
+
threadId: context.threadId,
|
|
790
|
+
message,
|
|
791
|
+
});
|
|
792
|
+
},
|
|
793
|
+
editReply: async (options) => {
|
|
794
|
+
await this.replyFromContext({ context, options });
|
|
795
|
+
},
|
|
796
|
+
editUiReply: async (message) => {
|
|
797
|
+
await this.postUiMessage({
|
|
798
|
+
channelId: context.channelId,
|
|
799
|
+
threadId: context.threadId,
|
|
800
|
+
message,
|
|
801
|
+
});
|
|
802
|
+
},
|
|
803
|
+
};
|
|
804
|
+
for (const handler of this.modalSubmitHandlers) {
|
|
805
|
+
try {
|
|
806
|
+
await handler(event);
|
|
807
|
+
}
|
|
808
|
+
catch (error) {
|
|
809
|
+
this.emitError(error);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
async dispatchCommand(payload) {
|
|
814
|
+
const context = {
|
|
815
|
+
channelId: payload.channel_id,
|
|
816
|
+
responseUrl: payload.response_url,
|
|
817
|
+
triggerId: payload.trigger_id,
|
|
818
|
+
user: {
|
|
819
|
+
id: payload.user_id,
|
|
820
|
+
username: payload.user_name || payload.user_id,
|
|
821
|
+
name: payload.user_name || payload.user_id,
|
|
822
|
+
},
|
|
823
|
+
};
|
|
824
|
+
const commandName = payload.command.replace(/^\//, '');
|
|
825
|
+
const event = {
|
|
826
|
+
raw: payload,
|
|
827
|
+
appId: this.appId,
|
|
828
|
+
commandName,
|
|
829
|
+
channelId: payload.channel_id,
|
|
830
|
+
responseUrl: payload.response_url,
|
|
831
|
+
text: payload.text || '',
|
|
832
|
+
triggerId: payload.trigger_id,
|
|
833
|
+
user: wrapSlackUser({
|
|
834
|
+
id: payload.user_id,
|
|
835
|
+
username: payload.user_name || payload.user_id,
|
|
836
|
+
displayName: payload.user_name || payload.user_id,
|
|
837
|
+
}),
|
|
838
|
+
reply: async (options) => {
|
|
839
|
+
await this.replyFromContext({ context, options });
|
|
840
|
+
},
|
|
841
|
+
replyUi: async (message) => {
|
|
842
|
+
await this.postUiMessage({
|
|
843
|
+
channelId: context.channelId,
|
|
844
|
+
message,
|
|
845
|
+
});
|
|
846
|
+
},
|
|
847
|
+
editReply: async (options) => {
|
|
848
|
+
await this.replyFromContext({ context, options });
|
|
849
|
+
},
|
|
850
|
+
editUiReply: async (message) => {
|
|
851
|
+
await this.postUiMessage({
|
|
852
|
+
channelId: context.channelId,
|
|
853
|
+
message,
|
|
854
|
+
});
|
|
855
|
+
},
|
|
856
|
+
showModal: async (modal) => {
|
|
857
|
+
await this.showModalFromContext({ context, modal });
|
|
858
|
+
},
|
|
859
|
+
};
|
|
860
|
+
for (const handler of this.commandHandlers) {
|
|
861
|
+
try {
|
|
862
|
+
await handler(event);
|
|
863
|
+
}
|
|
864
|
+
catch (error) {
|
|
865
|
+
this.emitError(error);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
export async function createSlackAdapter({ client, } = {}) {
|
|
871
|
+
return new SlackAdapter({ client });
|
|
872
|
+
}
|