@render-harness/cap-slack 0.4.1 → 0.5.0
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/README.md +15 -0
- package/dist/index.js +152 -24
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -59,6 +59,21 @@ Read tools are available when `SLACK_BOT_TOKEN` is set:
|
|
|
59
59
|
|
|
60
60
|
User and channel lookups are cached for the lifetime of the agent process to keep enrichment cheap across turns.
|
|
61
61
|
|
|
62
|
+
### Channel and user inputs
|
|
63
|
+
|
|
64
|
+
Every tool that takes a `channel` parameter (`slack.send_message`, `slack.get_thread`, `slack.get_channel_history`, `slack.get_channel_info`, `slack.add_reaction`, `slack.update_message`) accepts any of:
|
|
65
|
+
|
|
66
|
+
- A Slack channel ID — `C0AQHA6M3PS`, `G0…`, `D0…`. Used verbatim.
|
|
67
|
+
- A `#channel-name` — resolved to a channel ID via `conversations.list` (cached for the agent process). Requires `channels:read` and/or `groups:read` scope on the bot token. The bot must also be a **member** of the channel for write actions.
|
|
68
|
+
- An `@user-handle` — resolved to a user ID via `users.list` (cached) and opened as a DM channel via `conversations.open`. Requires `users:read` scope plus `im:write` (and `chat:write` for sending).
|
|
69
|
+
- A Slack mention literal — `<#C0AQHA6M3PS|name>` or `<@U0B4357MH7H>`. The wrapping is stripped and the inner ID is used.
|
|
70
|
+
|
|
71
|
+
`slack.get_user_info` accepts a user ID (`U0B4357MH7H`), an `@handle`, or a bare handle (`ada.lovelace`); the latter two require `users:read`.
|
|
72
|
+
|
|
73
|
+
`allowedChannels` is enforced **after** resolution, against the canonical channel ID. So `allowedChannels: ["C0AQ…"]` correctly accepts `slack.send_message({ channel: "#that-channels-name" })` because the resolver returns `C0AQ…` before the gate check fires.
|
|
74
|
+
|
|
75
|
+
If the bot lacks the lookup scope, the tool returns a clear error naming the scope (`Slack channel "#general" not found. Check the name, or ensure the bot has 'channels:read' / 'groups:read' scope and is a member of the channel.`) instead of failing silently. Agents that only ever address channels by ID don't need the read scopes; the resolver fast-paths `C…` / `G…` / `D…` / `U…` inputs without any API call.
|
|
76
|
+
|
|
62
77
|
Set `accessMode: read_write` to enable write tools:
|
|
63
78
|
|
|
64
79
|
- `slack.send_message`
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { WebClient } from '@slack/web-api';
|
|
|
6
6
|
|
|
7
7
|
// package.json
|
|
8
8
|
var package_default = {
|
|
9
|
-
version: "0.
|
|
9
|
+
version: "0.5.0"};
|
|
10
10
|
function slackConversationId(args) {
|
|
11
11
|
const digest = createHash("sha256").update(`${args.teamId}:${args.channel}:${args.threadTs}`).digest("hex").slice(0, 24);
|
|
12
12
|
return `slack-${digest}`;
|
|
@@ -67,6 +67,8 @@ var BOUNDED_RETRY_CONFIG = {
|
|
|
67
67
|
minTimeout: 500,
|
|
68
68
|
maxTimeout: 3e3
|
|
69
69
|
};
|
|
70
|
+
var CHANNEL_ID_PATTERN = /^[CGD][A-Z0-9]{2,}$/;
|
|
71
|
+
var USER_ID_PATTERN = /^U[A-Z0-9]{2,}$/;
|
|
70
72
|
function slackTools(args) {
|
|
71
73
|
const client = new WebClient(args.botToken, {
|
|
72
74
|
timeout: SLACK_REQUEST_TIMEOUT_MS,
|
|
@@ -76,53 +78,60 @@ function slackTools(args) {
|
|
|
76
78
|
const tools = [
|
|
77
79
|
jsonTool(
|
|
78
80
|
"slack.get_thread",
|
|
79
|
-
"Read a Slack thread's messages. Responses include resolved user display names and a rewritten text_resolved field.",
|
|
81
|
+
"Read a Slack thread's messages. `channel` accepts a Slack ID (C\u2026/G\u2026/D\u2026), a `#channel-name`, or an `@user-handle` (opens a DM). Responses include resolved user display names and a rewritten text_resolved field.",
|
|
80
82
|
objectSchema({
|
|
81
83
|
channel: { type: "string" },
|
|
82
84
|
thread_ts: { type: "string" }
|
|
83
85
|
}),
|
|
84
86
|
async (input) => {
|
|
85
87
|
const { channel, thread_ts } = input;
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
const channelId = await resolver.channelInput(channel);
|
|
89
|
+
assertAllowedChannel(channelId, args.allowedChannels);
|
|
90
|
+
const resp = await client.conversations.replies({ channel: channelId, ts: thread_ts });
|
|
91
|
+
return enrichConversationResponse(resp, channelId, resolver);
|
|
89
92
|
}
|
|
90
93
|
),
|
|
91
94
|
jsonTool(
|
|
92
95
|
"slack.get_channel_history",
|
|
93
|
-
"Read recent Slack channel messages. Responses include resolved user display names and a rewritten text_resolved field.",
|
|
96
|
+
"Read recent Slack channel messages. `channel` accepts a Slack ID (C\u2026/G\u2026/D\u2026), a `#channel-name`, or an `@user-handle` (opens a DM). Responses include resolved user display names and a rewritten text_resolved field.",
|
|
94
97
|
objectSchema({
|
|
95
98
|
channel: { type: "string" },
|
|
96
99
|
limit: { type: "number", optional: true }
|
|
97
100
|
}),
|
|
98
101
|
async (input) => {
|
|
99
102
|
const { channel, limit } = input;
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
+
const channelId = await resolver.channelInput(channel);
|
|
104
|
+
assertAllowedChannel(channelId, args.allowedChannels);
|
|
105
|
+
const resp = await client.conversations.history({
|
|
106
|
+
channel: channelId,
|
|
107
|
+
limit: limit ?? 20
|
|
108
|
+
});
|
|
109
|
+
return enrichConversationResponse(resp, channelId, resolver);
|
|
103
110
|
}
|
|
104
111
|
),
|
|
105
112
|
jsonTool(
|
|
106
113
|
"slack.get_user_info",
|
|
107
|
-
"Resolve a Slack user
|
|
114
|
+
"Resolve a Slack user to display name, real name, and handle. Accepts a user ID (U0B4357MH7H), an `@handle`, or a bare handle.",
|
|
108
115
|
objectSchema({
|
|
109
116
|
user: { type: "string" }
|
|
110
117
|
}),
|
|
111
118
|
async (input) => {
|
|
112
119
|
const { user } = input;
|
|
113
|
-
|
|
120
|
+
const userId = await resolver.userInput(user);
|
|
121
|
+
return resolver.user(userId);
|
|
114
122
|
}
|
|
115
123
|
),
|
|
116
124
|
jsonTool(
|
|
117
125
|
"slack.get_channel_info",
|
|
118
|
-
"Resolve a Slack channel ID (
|
|
126
|
+
"Resolve a Slack channel to name and metadata. Accepts a channel ID (C0AQHA6M3PS), a `#channel-name`, or an `@user-handle` (opens a DM).",
|
|
119
127
|
objectSchema({
|
|
120
128
|
channel: { type: "string" }
|
|
121
129
|
}),
|
|
122
130
|
async (input) => {
|
|
123
131
|
const { channel } = input;
|
|
124
|
-
|
|
125
|
-
|
|
132
|
+
const channelId = await resolver.channelInput(channel);
|
|
133
|
+
assertAllowedChannel(channelId, args.allowedChannels);
|
|
134
|
+
return resolver.channel(channelId);
|
|
126
135
|
}
|
|
127
136
|
)
|
|
128
137
|
];
|
|
@@ -130,7 +139,7 @@ function slackTools(args) {
|
|
|
130
139
|
tools.push(
|
|
131
140
|
jsonTool(
|
|
132
141
|
"slack.send_message",
|
|
133
|
-
"Send a Slack message, optionally as a thread reply.",
|
|
142
|
+
"Send a Slack message, optionally as a thread reply. `channel` accepts a Slack ID (C\u2026/G\u2026/D\u2026), a `#channel-name`, or an `@user-handle` (opens a DM).",
|
|
134
143
|
objectSchema({
|
|
135
144
|
channel: { type: "string" },
|
|
136
145
|
text: { type: "string" },
|
|
@@ -138,9 +147,10 @@ function slackTools(args) {
|
|
|
138
147
|
}),
|
|
139
148
|
async (input) => {
|
|
140
149
|
const { channel, text, thread_ts } = input;
|
|
141
|
-
|
|
150
|
+
const channelId = await resolver.channelInput(channel);
|
|
151
|
+
assertAllowedChannel(channelId, args.allowedChannels);
|
|
142
152
|
return client.chat.postMessage({
|
|
143
|
-
channel,
|
|
153
|
+
channel: channelId,
|
|
144
154
|
text,
|
|
145
155
|
...thread_ts ? { thread_ts } : {}
|
|
146
156
|
});
|
|
@@ -148,7 +158,7 @@ function slackTools(args) {
|
|
|
148
158
|
),
|
|
149
159
|
jsonTool(
|
|
150
160
|
"slack.add_reaction",
|
|
151
|
-
"Add a reaction to a Slack message.",
|
|
161
|
+
"Add a reaction to a Slack message. `channel` accepts a Slack ID, a `#channel-name`, or an `@user-handle`.",
|
|
152
162
|
objectSchema({
|
|
153
163
|
channel: { type: "string" },
|
|
154
164
|
ts: { type: "string" },
|
|
@@ -156,13 +166,14 @@ function slackTools(args) {
|
|
|
156
166
|
}),
|
|
157
167
|
async (input) => {
|
|
158
168
|
const { channel, ts, name } = input;
|
|
159
|
-
|
|
160
|
-
|
|
169
|
+
const channelId = await resolver.channelInput(channel);
|
|
170
|
+
assertAllowedChannel(channelId, args.allowedChannels);
|
|
171
|
+
return client.reactions.add({ channel: channelId, timestamp: ts, name });
|
|
161
172
|
}
|
|
162
173
|
),
|
|
163
174
|
jsonTool(
|
|
164
175
|
"slack.update_message",
|
|
165
|
-
"Update a Slack message.",
|
|
176
|
+
"Update a Slack message. `channel` accepts a Slack ID, a `#channel-name`, or an `@user-handle`.",
|
|
166
177
|
objectSchema({
|
|
167
178
|
channel: { type: "string" },
|
|
168
179
|
ts: { type: "string" },
|
|
@@ -170,8 +181,9 @@ function slackTools(args) {
|
|
|
170
181
|
}),
|
|
171
182
|
async (input) => {
|
|
172
183
|
const { channel, ts, text } = input;
|
|
173
|
-
|
|
174
|
-
|
|
184
|
+
const channelId = await resolver.channelInput(channel);
|
|
185
|
+
assertAllowedChannel(channelId, args.allowedChannels);
|
|
186
|
+
return client.chat.update({ channel: channelId, ts, text });
|
|
175
187
|
}
|
|
176
188
|
)
|
|
177
189
|
);
|
|
@@ -208,6 +220,120 @@ function createResolver(client) {
|
|
|
208
220
|
const channels = /* @__PURE__ */ new Map();
|
|
209
221
|
const pendingUsers = /* @__PURE__ */ new Map();
|
|
210
222
|
const pendingChannels = /* @__PURE__ */ new Map();
|
|
223
|
+
let channelsIndex = null;
|
|
224
|
+
let usersIndex = null;
|
|
225
|
+
const dmByUserId = /* @__PURE__ */ new Map();
|
|
226
|
+
const ensureChannelsIndex = () => {
|
|
227
|
+
if (channelsIndex) return channelsIndex;
|
|
228
|
+
channelsIndex = (async () => {
|
|
229
|
+
const byName = /* @__PURE__ */ new Map();
|
|
230
|
+
let cursor;
|
|
231
|
+
do {
|
|
232
|
+
const resp = await client.conversations.list({
|
|
233
|
+
limit: 1e3,
|
|
234
|
+
types: "public_channel,private_channel",
|
|
235
|
+
exclude_archived: true,
|
|
236
|
+
...cursor ? { cursor } : {}
|
|
237
|
+
});
|
|
238
|
+
for (const c of resp.channels ?? []) {
|
|
239
|
+
if (typeof c.id === "string" && typeof c.name === "string") {
|
|
240
|
+
byName.set(c.name.toLowerCase(), c.id);
|
|
241
|
+
if (!channels.has(c.id)) channels.set(c.id, { id: c.id, name: c.name });
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
cursor = typeof resp.response_metadata?.next_cursor === "string" && resp.response_metadata.next_cursor.length > 0 ? resp.response_metadata.next_cursor : void 0;
|
|
245
|
+
} while (cursor);
|
|
246
|
+
return byName;
|
|
247
|
+
})().catch((err) => {
|
|
248
|
+
channelsIndex = null;
|
|
249
|
+
throw err;
|
|
250
|
+
});
|
|
251
|
+
return channelsIndex;
|
|
252
|
+
};
|
|
253
|
+
const ensureUsersIndex = () => {
|
|
254
|
+
if (usersIndex) return usersIndex;
|
|
255
|
+
usersIndex = (async () => {
|
|
256
|
+
const byHandle = /* @__PURE__ */ new Map();
|
|
257
|
+
let cursor;
|
|
258
|
+
do {
|
|
259
|
+
const resp = await client.users.list({
|
|
260
|
+
limit: 200,
|
|
261
|
+
...cursor ? { cursor } : {}
|
|
262
|
+
});
|
|
263
|
+
for (const u of resp.members ?? []) {
|
|
264
|
+
if (typeof u.id !== "string" || u.deleted === true) continue;
|
|
265
|
+
for (const candidate of [
|
|
266
|
+
u.name,
|
|
267
|
+
u.real_name,
|
|
268
|
+
u.profile?.display_name,
|
|
269
|
+
u.profile?.display_name_normalized
|
|
270
|
+
]) {
|
|
271
|
+
if (typeof candidate === "string" && candidate.length > 0) {
|
|
272
|
+
byHandle.set(candidate.toLowerCase(), u.id);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (!users.has(u.id)) {
|
|
276
|
+
users.set(u.id, buildResolvedUser(u.id, u));
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
cursor = typeof resp.response_metadata?.next_cursor === "string" && resp.response_metadata.next_cursor.length > 0 ? resp.response_metadata.next_cursor : void 0;
|
|
280
|
+
} while (cursor);
|
|
281
|
+
return byHandle;
|
|
282
|
+
})().catch((err) => {
|
|
283
|
+
usersIndex = null;
|
|
284
|
+
throw err;
|
|
285
|
+
});
|
|
286
|
+
return usersIndex;
|
|
287
|
+
};
|
|
288
|
+
const openDmFor = async (userId) => {
|
|
289
|
+
const cached = dmByUserId.get(userId);
|
|
290
|
+
if (cached) return cached;
|
|
291
|
+
const resp = await client.conversations.open({ users: userId });
|
|
292
|
+
const channelId = resp.channel?.id;
|
|
293
|
+
if (typeof channelId !== "string") {
|
|
294
|
+
throw new Error(`conversations.open for ${userId} returned no channel id`);
|
|
295
|
+
}
|
|
296
|
+
dmByUserId.set(userId, channelId);
|
|
297
|
+
return channelId;
|
|
298
|
+
};
|
|
299
|
+
const resolveUserInput = async (input) => {
|
|
300
|
+
const trimmed = input.trim();
|
|
301
|
+
if (trimmed.length === 0) throw new Error("Slack user input is empty");
|
|
302
|
+
const mention = /^<@(U[A-Z0-9]+)(?:\|[^>]+)?>$/.exec(trimmed);
|
|
303
|
+
if (mention?.[1]) return mention[1];
|
|
304
|
+
if (USER_ID_PATTERN.test(trimmed)) return trimmed;
|
|
305
|
+
const handle = trimmed.startsWith("@") ? trimmed.slice(1) : trimmed;
|
|
306
|
+
if (handle.length === 0) throw new Error("Slack user input has no handle after '@'");
|
|
307
|
+
const index = await ensureUsersIndex();
|
|
308
|
+
const id = index.get(handle.toLowerCase());
|
|
309
|
+
if (!id) {
|
|
310
|
+
throw new Error(
|
|
311
|
+
`Slack user "${input}" not found. Check the handle, or ensure the bot has 'users:read' scope.`
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
return id;
|
|
315
|
+
};
|
|
316
|
+
const resolveChannelInput = async (input) => {
|
|
317
|
+
const trimmed = input.trim();
|
|
318
|
+
if (trimmed.length === 0) throw new Error("Slack channel input is empty");
|
|
319
|
+
const mention = /^<#([CGD][A-Z0-9]+)(?:\|[^>]+)?>$/.exec(trimmed);
|
|
320
|
+
if (mention?.[1]) return mention[1];
|
|
321
|
+
if (CHANNEL_ID_PATTERN.test(trimmed)) return trimmed;
|
|
322
|
+
if (trimmed.startsWith("@")) {
|
|
323
|
+
const userId = await resolveUserInput(trimmed);
|
|
324
|
+
return openDmFor(userId);
|
|
325
|
+
}
|
|
326
|
+
const name = trimmed.startsWith("#") ? trimmed.slice(1) : trimmed;
|
|
327
|
+
if (name.length === 0) throw new Error("Slack channel input has no name after '#'");
|
|
328
|
+
const index = await ensureChannelsIndex();
|
|
329
|
+
const id = index.get(name.toLowerCase());
|
|
330
|
+
if (!id) {
|
|
331
|
+
throw new Error(
|
|
332
|
+
`Slack channel "${input}" not found. Check the name, or ensure the bot has 'channels:read' / 'groups:read' scope and is a member of the channel.`
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
return id;
|
|
336
|
+
};
|
|
211
337
|
return {
|
|
212
338
|
async user(id) {
|
|
213
339
|
const cached = users.get(id);
|
|
@@ -254,7 +380,9 @@ function createResolver(client) {
|
|
|
254
380
|
})();
|
|
255
381
|
pendingChannels.set(id, fetchOne);
|
|
256
382
|
return fetchOne;
|
|
257
|
-
}
|
|
383
|
+
},
|
|
384
|
+
channelInput: resolveChannelInput,
|
|
385
|
+
userInput: resolveUserInput
|
|
258
386
|
};
|
|
259
387
|
}
|
|
260
388
|
function buildResolvedUser(id, raw) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../package.json","../src/convid.ts","../src/normalize.ts","../src/tools.ts","../src/verify.ts","../src/index.ts"],"names":["stringValue","createHash"],"mappings":"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","file":"index.js","sourcesContent":["{\n \"name\": \"@render-harness/cap-slack\",\n \"version\": \"0.4.1\",\n \"description\": \"Slack Events and Web API capability pack for the Render agent harness.\",\n \"type\": \"module\",\n \"license\": \"MIT\",\n \"main\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\"\n },\n \"./package.json\": \"./package.json\"\n },\n \"files\": [\n \"dist\"\n ],\n \"keywords\": [\n \"render-harness-cap\",\n \"render-harness\",\n \"slack\"\n ],\n \"renderHarness\": {\n \"gallery\": {\n \"label\": \"Slack\",\n \"envHint\": \"SLACK_BOT_TOKEN\"\n }\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"typecheck\": \"tsc --noEmit\",\n \"test\": \"vitest run --passWithNoTests\"\n },\n \"dependencies\": {\n \"@render-harness/registry\": \"workspace:*\",\n \"@slack/web-api\": \"^7.12.0\"\n },\n \"devDependencies\": {\n \"@render-harness/core\": \"workspace:*\",\n \"@types/node\": \"^25.6.2\",\n \"tsup\": \"^8.5.1\",\n \"typescript\": \"^6.0.3\",\n \"vitest\": \"^4.1.5\"\n },\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/render-lab/render-agent-harness.git\",\n \"directory\": \"packages/capabilities/cap-slack\"\n }\n}\n","import { createHash } from \"node:crypto\";\n\nexport function slackConversationId(args: {\n teamId: string;\n channel: string;\n threadTs: string;\n}): string {\n const digest = createHash(\"sha256\")\n .update(`${args.teamId}:${args.channel}:${args.threadTs}`)\n .digest(\"hex\")\n .slice(0, 24);\n return `slack-${digest}`;\n}\n","export type SlackNormalizedEvent =\n | { kind: \"challenge\"; challenge: string }\n | { kind: \"noop\"; reason: string }\n | {\n kind: \"message\";\n text: string;\n teamId: string;\n channel: string;\n ts: string;\n threadTs: string;\n eventId: string;\n userId?: string;\n };\n\nexport interface SlackNormalizeConfig {\n includeEdits?: boolean;\n allowedChannels?: string[];\n}\n\nexport function normalizeSlackEvent(\n body: unknown,\n cfg: SlackNormalizeConfig = {},\n): SlackNormalizedEvent {\n if (!body || typeof body !== \"object\") return { kind: \"noop\", reason: \"invalid_body\" };\n const payload = body as Record<string, unknown>;\n if (payload.type === \"url_verification\") {\n const challenge = stringValue(payload.challenge);\n return challenge\n ? { kind: \"challenge\", challenge }\n : { kind: \"noop\", reason: \"missing_challenge\" };\n }\n if (payload.type !== \"event_callback\") return { kind: \"noop\", reason: \"unsupported_type\" };\n\n const event = objectValue(payload.event);\n if (!event) return { kind: \"noop\", reason: \"missing_event\" };\n const eventType = stringValue(event.type);\n if (eventType !== \"app_mention\" && eventType !== \"message\") {\n return { kind: \"noop\", reason: \"unsupported_event\" };\n }\n if (event.bot_id || event.subtype === \"bot_message\")\n return { kind: \"noop\", reason: \"bot_message\" };\n if (event.subtype === \"message_changed\" && !cfg.includeEdits) {\n return { kind: \"noop\", reason: \"edit_ignored\" };\n }\n\n const channel = stringValue(event.channel);\n if (!channel) return { kind: \"noop\", reason: \"missing_channel\" };\n if (cfg.allowedChannels?.length && !cfg.allowedChannels.includes(channel)) {\n return { kind: \"noop\", reason: \"channel_not_allowed\" };\n }\n\n const teamId = stringValue(payload.team_id) ?? stringValue(event.team);\n const ts = stringValue(event.ts);\n const text = stringValue(event.text) ?? \"\";\n const eventId =\n stringValue(payload.event_id) ?? `${teamId ?? \"team\"}-${channel}-${ts ?? \"event\"}`;\n if (!teamId || !ts || text.trim().length === 0) return { kind: \"noop\", reason: \"missing_fields\" };\n const userId = stringValue(event.user);\n\n return {\n kind: \"message\",\n text,\n teamId,\n channel,\n ts,\n threadTs: stringValue(event.thread_ts) ?? ts,\n eventId,\n ...(userId ? { userId } : {}),\n };\n}\n\nfunction objectValue(value: unknown): Record<string, unknown> | null {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : null;\n}\n\nfunction stringValue(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n","import type { LocalToolHandler } from \"@render-harness/core\";\nimport { type RetryOptions, WebClient } from \"@slack/web-api\";\n\nexport type SlackAccessMode = \"read\" | \"read_write\";\n\n// @slack/web-api defaults to `tenRetriesInAboutThirtyMinutes` and no per-request\n// timeout, which means a single rate-limited or transient-failure response can\n// hang a tool call for up to 30 minutes with no surfaced progress. We replace\n// that with a bounded retry policy + 15s per-request timeout so the agent gets a\n// clear error within ~70s worst case instead of appearing stuck forever.\nconst SLACK_REQUEST_TIMEOUT_MS = 15_000;\nconst BOUNDED_RETRY_CONFIG: RetryOptions = {\n retries: 3,\n factor: 2,\n minTimeout: 500,\n maxTimeout: 3_000,\n};\n\ninterface ResolvedUser {\n id: string;\n name?: string;\n real_name?: string;\n display_name?: string;\n is_bot?: boolean;\n}\n\ninterface ResolvedChannel {\n id: string;\n name?: string;\n is_private?: boolean;\n is_archived?: boolean;\n}\n\ninterface SlackResolver {\n user: (id: string) => Promise<ResolvedUser>;\n channel: (id: string) => Promise<ResolvedChannel>;\n}\n\nexport function slackTools(args: {\n botToken: string;\n accessMode: SlackAccessMode;\n allowedChannels?: string[];\n}): LocalToolHandler[] {\n const client = new WebClient(args.botToken, {\n timeout: SLACK_REQUEST_TIMEOUT_MS,\n retryConfig: BOUNDED_RETRY_CONFIG,\n });\n const resolver = createResolver(client);\n\n const tools: LocalToolHandler[] = [\n jsonTool(\n \"slack.get_thread\",\n \"Read a Slack thread's messages. Responses include resolved user display names and a rewritten text_resolved field.\",\n objectSchema({\n channel: { type: \"string\" },\n thread_ts: { type: \"string\" },\n }),\n async (input) => {\n const { channel, thread_ts } = input as { channel: string; thread_ts: string };\n assertAllowedChannel(channel, args.allowedChannels);\n const resp = await client.conversations.replies({ channel, ts: thread_ts });\n return enrichConversationResponse(resp, channel, resolver);\n },\n ),\n jsonTool(\n \"slack.get_channel_history\",\n \"Read recent Slack channel messages. Responses include resolved user display names and a rewritten text_resolved field.\",\n objectSchema({\n channel: { type: \"string\" },\n limit: { type: \"number\", optional: true },\n }),\n async (input) => {\n const { channel, limit } = input as { channel: string; limit?: number };\n assertAllowedChannel(channel, args.allowedChannels);\n const resp = await client.conversations.history({ channel, limit: limit ?? 20 });\n return enrichConversationResponse(resp, channel, resolver);\n },\n ),\n jsonTool(\n \"slack.get_user_info\",\n \"Resolve a Slack user ID (e.g. U0B4357MH7H) to a display name, real name, and handle.\",\n objectSchema({\n user: { type: \"string\" },\n }),\n async (input) => {\n const { user } = input as { user: string };\n return resolver.user(user);\n },\n ),\n jsonTool(\n \"slack.get_channel_info\",\n \"Resolve a Slack channel ID (e.g. C0AQHA6M3PS) to a channel name and metadata.\",\n objectSchema({\n channel: { type: \"string\" },\n }),\n async (input) => {\n const { channel } = input as { channel: string };\n assertAllowedChannel(channel, args.allowedChannels);\n return resolver.channel(channel);\n },\n ),\n ];\n\n if (args.accessMode === \"read_write\") {\n tools.push(\n jsonTool(\n \"slack.send_message\",\n \"Send a Slack message, optionally as a thread reply.\",\n objectSchema({\n channel: { type: \"string\" },\n text: { type: \"string\" },\n thread_ts: { type: \"string\", optional: true },\n }),\n async (input) => {\n const { channel, text, thread_ts } = input as {\n channel: string;\n text: string;\n thread_ts?: string;\n };\n assertAllowedChannel(channel, args.allowedChannels);\n return client.chat.postMessage({\n channel,\n text,\n ...(thread_ts ? { thread_ts } : {}),\n });\n },\n ),\n jsonTool(\n \"slack.add_reaction\",\n \"Add a reaction to a Slack message.\",\n objectSchema({\n channel: { type: \"string\" },\n ts: { type: \"string\" },\n name: { type: \"string\" },\n }),\n async (input) => {\n const { channel, ts, name } = input as { channel: string; ts: string; name: string };\n assertAllowedChannel(channel, args.allowedChannels);\n return client.reactions.add({ channel, timestamp: ts, name });\n },\n ),\n jsonTool(\n \"slack.update_message\",\n \"Update a Slack message.\",\n objectSchema({\n channel: { type: \"string\" },\n ts: { type: \"string\" },\n text: { type: \"string\" },\n }),\n async (input) => {\n const { channel, ts, text } = input as { channel: string; ts: string; text: string };\n assertAllowedChannel(channel, args.allowedChannels);\n return client.chat.update({ channel, ts, text });\n },\n ),\n );\n }\n\n return tools;\n}\n\nfunction jsonTool(\n name: string,\n description: string,\n inputSchema: Record<string, unknown>,\n call: (input: unknown) => Promise<unknown>,\n): LocalToolHandler {\n return {\n definition: { name, description, inputSchema, source: \"pack:cap-slack\" },\n handler: async ({ input }) => {\n try {\n return { content: JSON.stringify(await call(input), null, 2) };\n } catch (err) {\n return { content: err instanceof Error ? err.message : String(err), isError: true };\n }\n },\n };\n}\n\nfunction assertAllowedChannel(channel: string, allowedChannels: string[] | undefined): void {\n if (allowedChannels?.length && !allowedChannels.includes(channel)) {\n throw new Error(`Slack channel \"${channel}\" is not allowed by cap-slack config`);\n }\n}\n\nfunction objectSchema(properties: Record<string, unknown>) {\n return {\n type: \"object\",\n additionalProperties: false,\n properties,\n required: Object.entries(properties)\n .filter(([, value]) => !(value as { optional?: boolean }).optional)\n .map(([key]) => key),\n };\n}\n\nfunction createResolver(client: WebClient): SlackResolver {\n const users = new Map<string, ResolvedUser>();\n const channels = new Map<string, ResolvedChannel>();\n const pendingUsers = new Map<string, Promise<ResolvedUser>>();\n const pendingChannels = new Map<string, Promise<ResolvedChannel>>();\n\n return {\n async user(id: string): Promise<ResolvedUser> {\n const cached = users.get(id);\n if (cached) return cached;\n const pending = pendingUsers.get(id);\n if (pending) return pending;\n const fetchOne = (async (): Promise<ResolvedUser> => {\n try {\n const resp = await client.users.info({ user: id });\n const raw = (resp as unknown as { user?: Record<string, unknown> }).user;\n const info = buildResolvedUser(id, raw);\n users.set(id, info);\n return info;\n } catch {\n const info: ResolvedUser = { id };\n users.set(id, info);\n return info;\n } finally {\n pendingUsers.delete(id);\n }\n })();\n pendingUsers.set(id, fetchOne);\n return fetchOne;\n },\n async channel(id: string): Promise<ResolvedChannel> {\n const cached = channels.get(id);\n if (cached) return cached;\n const pending = pendingChannels.get(id);\n if (pending) return pending;\n const fetchOne = (async (): Promise<ResolvedChannel> => {\n try {\n const resp = await client.conversations.info({ channel: id });\n const raw = (resp as unknown as { channel?: Record<string, unknown> }).channel;\n const info = buildResolvedChannel(id, raw);\n channels.set(id, info);\n return info;\n } catch {\n const info: ResolvedChannel = { id };\n channels.set(id, info);\n return info;\n } finally {\n pendingChannels.delete(id);\n }\n })();\n pendingChannels.set(id, fetchOne);\n return fetchOne;\n },\n };\n}\n\nfunction buildResolvedUser(id: string, raw: Record<string, unknown> | undefined): ResolvedUser {\n if (!raw) return { id };\n const profile = (raw.profile as Record<string, unknown> | undefined) ?? undefined;\n const info: ResolvedUser = { id };\n const name = typeof raw.name === \"string\" ? raw.name : undefined;\n if (name) info.name = name;\n const realName =\n typeof raw.real_name === \"string\"\n ? raw.real_name\n : typeof profile?.real_name === \"string\"\n ? (profile.real_name as string)\n : undefined;\n if (realName) info.real_name = realName;\n const displayName =\n typeof profile?.display_name === \"string\" && (profile.display_name as string).length > 0\n ? (profile.display_name as string)\n : undefined;\n if (displayName) info.display_name = displayName;\n if (raw.is_bot === true) info.is_bot = true;\n return info;\n}\n\nfunction buildResolvedChannel(\n id: string,\n raw: Record<string, unknown> | undefined,\n): ResolvedChannel {\n if (!raw) return { id };\n const info: ResolvedChannel = { id };\n if (typeof raw.name === \"string\") info.name = raw.name;\n if (raw.is_private === true) info.is_private = true;\n if (raw.is_archived === true) info.is_archived = true;\n return info;\n}\n\nfunction displayLabel(user: ResolvedUser | undefined): string | undefined {\n return user?.display_name ?? user?.real_name ?? user?.name;\n}\n\nconst USER_MENTION = /<@(U[A-Z0-9]+)(?:\\|[^>]+)?>/g;\nconst CHANNEL_MENTION = /<#(C[A-Z0-9]+)(?:\\|([^>]+))?>/g;\n\nasync function enrichConversationResponse(\n resp: unknown,\n channelId: string,\n resolver: SlackResolver,\n): Promise<unknown> {\n if (!resp || typeof resp !== \"object\") return resp;\n const body = resp as Record<string, unknown>;\n const rawMessages = Array.isArray(body.messages) ? (body.messages as unknown[]) : [];\n const userIds = new Set<string>();\n const channelIds = new Set<string>([channelId]);\n for (const msg of rawMessages) {\n if (!msg || typeof msg !== \"object\") continue;\n const m = msg as Record<string, unknown>;\n if (typeof m.user === \"string\") userIds.add(m.user);\n if (typeof m.text === \"string\") collectMentions(m.text, userIds, channelIds);\n }\n\n const [resolvedUserList, resolvedChannelList] = await Promise.all([\n Promise.all([...userIds].map(async (id) => [id, await resolver.user(id)] as const)),\n Promise.all([...channelIds].map(async (id) => [id, await resolver.channel(id)] as const)),\n ]);\n const usersById = new Map<string, ResolvedUser>(resolvedUserList);\n const channelsById = new Map<string, ResolvedChannel>(resolvedChannelList);\n\n const enrichedMessages = rawMessages.map((msg) => {\n if (!msg || typeof msg !== \"object\") return msg;\n const m = msg as Record<string, unknown>;\n const out: Record<string, unknown> = { ...m };\n if (typeof m.user === \"string\") {\n const label = displayLabel(usersById.get(m.user));\n if (label) out.user_display_name = label;\n }\n if (typeof m.text === \"string\") {\n out.text_resolved = rewriteMentions(m.text, usersById, channelsById);\n }\n return out;\n });\n\n const resolvedUsers: Record<string, ResolvedUser> = {};\n for (const [id, info] of usersById) resolvedUsers[id] = info;\n const channelInfo = channelsById.get(channelId);\n\n return {\n ...body,\n messages: enrichedMessages,\n resolved_users: resolvedUsers,\n ...(channelInfo ? { resolved_channel: channelInfo } : {}),\n };\n}\n\nfunction collectMentions(text: string, userIds: Set<string>, channelIds: Set<string>): void {\n for (const match of text.matchAll(USER_MENTION)) {\n const id = match[1];\n if (id) userIds.add(id);\n }\n for (const match of text.matchAll(CHANNEL_MENTION)) {\n const id = match[1];\n if (id) channelIds.add(id);\n }\n}\n\nfunction rewriteMentions(\n text: string,\n users: Map<string, ResolvedUser>,\n channels: Map<string, ResolvedChannel>,\n): string {\n return text\n .replace(USER_MENTION, (_match, id: string) => {\n const label = displayLabel(users.get(id));\n return label ? `@${label}` : `<@${id}>`;\n })\n .replace(CHANNEL_MENTION, (_match, id: string, fallbackName?: string) => {\n const name = channels.get(id)?.name ?? fallbackName;\n return name ? `#${name}` : `<#${id}>`;\n });\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\n\nconst FIVE_MINUTES_SECONDS = 60 * 5;\n\nexport function verifySlackSignature(args: {\n rawBody: string;\n signingSecret: string;\n signature: string | null;\n timestamp: string | null;\n nowSeconds?: number;\n}): boolean {\n if (!args.signature?.startsWith(\"v0=\") || !args.timestamp) return false;\n const ts = Number(args.timestamp);\n if (!Number.isFinite(ts)) return false;\n const now = args.nowSeconds ?? Math.floor(Date.now() / 1000);\n if (Math.abs(now - ts) > FIVE_MINUTES_SECONDS) return false;\n\n const base = `v0:${args.timestamp}:${args.rawBody}`;\n const expected = `v0=${createHmac(\"sha256\", args.signingSecret).update(base).digest(\"hex\")}`;\n const expectedBuf = Buffer.from(expected, \"utf8\");\n const actualBuf = Buffer.from(args.signature, \"utf8\");\n if (expectedBuf.length !== actualBuf.length) return false;\n return timingSafeEqual(expectedBuf, actualBuf);\n}\n","import { createHash } from \"node:crypto\";\nimport type { LocalToolHandler } from \"@render-harness/core\";\nimport { type ConnectorContribution, definePack, type PackContext } from \"@render-harness/registry\";\nimport pkg from \"../package.json\" with { type: \"json\" };\nimport { slackConversationId } from \"./convid.js\";\nimport { normalizeSlackEvent, type SlackNormalizeConfig } from \"./normalize.js\";\nimport { type SlackAccessMode, slackTools } from \"./tools.js\";\nimport { verifySlackSignature } from \"./verify.js\";\n\ninterface SlackConfig extends SlackNormalizeConfig {\n agent?: string;\n userId?: string;\n signingSecretEnv?: string;\n botTokenEnv?: string;\n accessMode?: SlackAccessMode;\n}\n\nconst DEFAULT_SIGNING_SECRET_ENV = \"SLACK_SIGNING_SECRET\";\nconst DEFAULT_BOT_TOKEN_ENV = \"SLACK_BOT_TOKEN\";\n\nconst pack = definePack({\n name: \"cap-slack\",\n version: pkg.version,\n envSchema: [\n {\n name: DEFAULT_SIGNING_SECRET_ENV,\n required: true,\n secret: true,\n description: \"Slack signing secret used to verify Events API requests.\",\n },\n {\n name: DEFAULT_BOT_TOKEN_ENV,\n required: true,\n secret: true,\n description: \"Slack bot token used for read tools and optional write tools.\",\n },\n ],\n localTools(ctx: PackContext): LocalToolHandler[] {\n const cfg = readConfig(ctx.config);\n const botToken = ctx.env(cfg.botTokenEnv);\n if (!botToken) return [];\n return slackTools({\n botToken,\n accessMode: cfg.accessMode,\n ...(cfg.allowedChannels ? { allowedChannels: cfg.allowedChannels } : {}),\n });\n },\n connectors(ctx: PackContext): ConnectorContribution[] {\n const cfg = readConfig(ctx.config);\n return [\n {\n key: \"slack\",\n webhook: async (req, webCtx) => {\n const rawBody = await req.text();\n const signingSecret = ctx.env(cfg.signingSecretEnv);\n if (!signingSecret) {\n return json({ error: \"missing_secret\", env: cfg.signingSecretEnv }, 500);\n }\n if (\n !verifySlackSignature({\n rawBody,\n signingSecret,\n signature: req.headers.get(\"x-slack-signature\"),\n timestamp: req.headers.get(\"x-slack-request-timestamp\"),\n })\n ) {\n return json({ error: \"invalid_signature\" }, 401);\n }\n\n const parsed = parseBody(rawBody);\n const normalized = normalizeSlackEvent(parsed, cfg);\n if (normalized.kind === \"challenge\") return json({ challenge: normalized.challenge });\n if (normalized.kind === \"noop\")\n return json({ ok: true, skipped: true, reason: normalized.reason });\n\n const agent = webCtx.resolveAgent(cfg.agent);\n const conversationId = slackConversationId({\n teamId: normalized.teamId,\n channel: normalized.channel,\n threadTs: normalized.threadTs,\n });\n const result = await webCtx.enqueueIntoConversation({\n conversationId,\n agentName: agent.name,\n agentVersion: agent.version,\n userId: cfg.userId ?? \"cap-slack\",\n runId: `slack-${hash(normalized.eventId)}`,\n title: `Slack ${normalized.channel}/${normalized.threadTs}`,\n initialContent: [{ type: \"text\", text: normalized.text }],\n metadata: {\n connector: \"cap-slack\",\n teamId: normalized.teamId,\n channel: normalized.channel,\n threadTs: normalized.threadTs,\n ts: normalized.ts,\n ...(normalized.userId ? { userId: normalized.userId } : {}),\n },\n });\n return json(result, result.status === \"enqueued\" ? 202 : 200);\n },\n },\n ];\n },\n});\n\nexport default pack;\n\ntype ResolvedSlackConfig = Required<\n Pick<SlackConfig, \"signingSecretEnv\" | \"botTokenEnv\" | \"accessMode\" | \"includeEdits\">\n> &\n Omit<SlackConfig, \"signingSecretEnv\" | \"botTokenEnv\" | \"accessMode\" | \"includeEdits\">;\n\nfunction readConfig(raw: Record<string, unknown>): ResolvedSlackConfig {\n const cfg: ResolvedSlackConfig = {\n signingSecretEnv: stringValue(raw.signingSecretEnv) ?? DEFAULT_SIGNING_SECRET_ENV,\n botTokenEnv: stringValue(raw.botTokenEnv) ?? DEFAULT_BOT_TOKEN_ENV,\n accessMode: raw.accessMode === \"read_write\" ? \"read_write\" : \"read\",\n includeEdits: raw.includeEdits === true,\n };\n const agent = stringValue(raw.agent);\n if (agent) cfg.agent = agent;\n const userId = stringValue(raw.userId);\n if (userId) cfg.userId = userId;\n const allowedChannels = stringArray(raw.allowedChannels);\n if (allowedChannels) cfg.allowedChannels = allowedChannels;\n return cfg;\n}\n\nfunction parseBody(rawBody: string): unknown {\n try {\n return JSON.parse(rawBody);\n } catch {\n return null;\n }\n}\n\nfunction hash(value: string): string {\n return createHash(\"sha256\").update(value).digest(\"hex\").slice(0, 32);\n}\n\nfunction json(body: unknown, status = 200): Response {\n return Response.json(body, { status });\n}\n\nfunction stringValue(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\nfunction stringArray(value: unknown): string[] | undefined {\n return Array.isArray(value)\n ? value.filter((item): item is string => typeof item === \"string\")\n : undefined;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../package.json","../src/convid.ts","../src/normalize.ts","../src/tools.ts","../src/verify.ts","../src/index.ts"],"names":["stringValue","createHash"],"mappings":"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","file":"index.js","sourcesContent":["{\n \"name\": \"@render-harness/cap-slack\",\n \"version\": \"0.5.0\",\n \"description\": \"Slack Events and Web API capability pack for the Render agent harness.\",\n \"type\": \"module\",\n \"license\": \"MIT\",\n \"main\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\"\n },\n \"./package.json\": \"./package.json\"\n },\n \"files\": [\n \"dist\"\n ],\n \"keywords\": [\n \"render-harness-cap\",\n \"render-harness\",\n \"slack\"\n ],\n \"renderHarness\": {\n \"gallery\": {\n \"label\": \"Slack\",\n \"envHint\": \"SLACK_BOT_TOKEN\"\n }\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"typecheck\": \"tsc --noEmit\",\n \"test\": \"vitest run --passWithNoTests\"\n },\n \"dependencies\": {\n \"@render-harness/registry\": \"workspace:*\",\n \"@slack/web-api\": \"^7.12.0\"\n },\n \"devDependencies\": {\n \"@render-harness/core\": \"workspace:*\",\n \"@types/node\": \"^25.6.2\",\n \"tsup\": \"^8.5.1\",\n \"typescript\": \"^6.0.3\",\n \"vitest\": \"^4.1.5\"\n },\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/render-lab/render-agent-harness.git\",\n \"directory\": \"packages/capabilities/cap-slack\"\n }\n}\n","import { createHash } from \"node:crypto\";\n\nexport function slackConversationId(args: {\n teamId: string;\n channel: string;\n threadTs: string;\n}): string {\n const digest = createHash(\"sha256\")\n .update(`${args.teamId}:${args.channel}:${args.threadTs}`)\n .digest(\"hex\")\n .slice(0, 24);\n return `slack-${digest}`;\n}\n","export type SlackNormalizedEvent =\n | { kind: \"challenge\"; challenge: string }\n | { kind: \"noop\"; reason: string }\n | {\n kind: \"message\";\n text: string;\n teamId: string;\n channel: string;\n ts: string;\n threadTs: string;\n eventId: string;\n userId?: string;\n };\n\nexport interface SlackNormalizeConfig {\n includeEdits?: boolean;\n allowedChannels?: string[];\n}\n\nexport function normalizeSlackEvent(\n body: unknown,\n cfg: SlackNormalizeConfig = {},\n): SlackNormalizedEvent {\n if (!body || typeof body !== \"object\") return { kind: \"noop\", reason: \"invalid_body\" };\n const payload = body as Record<string, unknown>;\n if (payload.type === \"url_verification\") {\n const challenge = stringValue(payload.challenge);\n return challenge\n ? { kind: \"challenge\", challenge }\n : { kind: \"noop\", reason: \"missing_challenge\" };\n }\n if (payload.type !== \"event_callback\") return { kind: \"noop\", reason: \"unsupported_type\" };\n\n const event = objectValue(payload.event);\n if (!event) return { kind: \"noop\", reason: \"missing_event\" };\n const eventType = stringValue(event.type);\n if (eventType !== \"app_mention\" && eventType !== \"message\") {\n return { kind: \"noop\", reason: \"unsupported_event\" };\n }\n if (event.bot_id || event.subtype === \"bot_message\")\n return { kind: \"noop\", reason: \"bot_message\" };\n if (event.subtype === \"message_changed\" && !cfg.includeEdits) {\n return { kind: \"noop\", reason: \"edit_ignored\" };\n }\n\n const channel = stringValue(event.channel);\n if (!channel) return { kind: \"noop\", reason: \"missing_channel\" };\n if (cfg.allowedChannels?.length && !cfg.allowedChannels.includes(channel)) {\n return { kind: \"noop\", reason: \"channel_not_allowed\" };\n }\n\n const teamId = stringValue(payload.team_id) ?? stringValue(event.team);\n const ts = stringValue(event.ts);\n const text = stringValue(event.text) ?? \"\";\n const eventId =\n stringValue(payload.event_id) ?? `${teamId ?? \"team\"}-${channel}-${ts ?? \"event\"}`;\n if (!teamId || !ts || text.trim().length === 0) return { kind: \"noop\", reason: \"missing_fields\" };\n const userId = stringValue(event.user);\n\n return {\n kind: \"message\",\n text,\n teamId,\n channel,\n ts,\n threadTs: stringValue(event.thread_ts) ?? ts,\n eventId,\n ...(userId ? { userId } : {}),\n };\n}\n\nfunction objectValue(value: unknown): Record<string, unknown> | null {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : null;\n}\n\nfunction stringValue(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n","import type { LocalToolHandler } from \"@render-harness/core\";\nimport { type RetryOptions, WebClient } from \"@slack/web-api\";\n\nexport type SlackAccessMode = \"read\" | \"read_write\";\n\n// @slack/web-api defaults to `tenRetriesInAboutThirtyMinutes` and no per-request\n// timeout, which means a single rate-limited or transient-failure response can\n// hang a tool call for up to 30 minutes with no surfaced progress. We replace\n// that with a bounded retry policy + 15s per-request timeout so the agent gets a\n// clear error within ~70s worst case instead of appearing stuck forever.\nconst SLACK_REQUEST_TIMEOUT_MS = 15_000;\nconst BOUNDED_RETRY_CONFIG: RetryOptions = {\n retries: 3,\n factor: 2,\n minTimeout: 500,\n maxTimeout: 3_000,\n};\n\ninterface ResolvedUser {\n id: string;\n name?: string;\n real_name?: string;\n display_name?: string;\n is_bot?: boolean;\n}\n\ninterface ResolvedChannel {\n id: string;\n name?: string;\n is_private?: boolean;\n is_archived?: boolean;\n}\n\ninterface SlackResolver {\n /** Look up a user by ID. Backed by `users.info`, cached for the agent process. */\n user: (id: string) => Promise<ResolvedUser>;\n /** Look up a channel by ID. Backed by `conversations.info`, cached for the agent process. */\n channel: (id: string) => Promise<ResolvedChannel>;\n /**\n * Resolve a channel input — either a raw Slack ID (`C…` / `G…` / `D…`),\n * a `#channel-name` string, or an `@user-handle` string (which opens or\n * reuses a DM channel). Returns the canonical channel ID for `chat.*` /\n * `conversations.*` calls. Lazy-fetches `conversations.list` and\n * `users.list` and caches by name/handle for subsequent lookups.\n */\n channelInput: (input: string) => Promise<string>;\n /**\n * Resolve a user input — either a raw `U…` ID or an `@handle` string\n * (with or without the leading `@`). Returns the user ID. Lazy-fetches\n * `users.list` and caches by handle.\n */\n userInput: (input: string) => Promise<string>;\n}\n\n// Slack channel/user/DM IDs are always `<prefix><A-Z0-9>+`; channel names\n// are restricted to lowercase letters / digits / `-` / `_`, so an input\n// that matches one of these patterns is unambiguously an ID. The {2,}\n// lower bound is intentionally loose so short test fixtures (`C123`) work\n// the same as real-world ids (`C0AQHA6M3PS`).\nconst CHANNEL_ID_PATTERN = /^[CGD][A-Z0-9]{2,}$/;\nconst USER_ID_PATTERN = /^U[A-Z0-9]{2,}$/;\n\nexport function slackTools(args: {\n botToken: string;\n accessMode: SlackAccessMode;\n allowedChannels?: string[];\n}): LocalToolHandler[] {\n const client = new WebClient(args.botToken, {\n timeout: SLACK_REQUEST_TIMEOUT_MS,\n retryConfig: BOUNDED_RETRY_CONFIG,\n });\n const resolver = createResolver(client);\n\n const tools: LocalToolHandler[] = [\n jsonTool(\n \"slack.get_thread\",\n \"Read a Slack thread's messages. `channel` accepts a Slack ID (C…/G…/D…), a `#channel-name`, or an `@user-handle` (opens a DM). Responses include resolved user display names and a rewritten text_resolved field.\",\n objectSchema({\n channel: { type: \"string\" },\n thread_ts: { type: \"string\" },\n }),\n async (input) => {\n const { channel, thread_ts } = input as { channel: string; thread_ts: string };\n const channelId = await resolver.channelInput(channel);\n assertAllowedChannel(channelId, args.allowedChannels);\n const resp = await client.conversations.replies({ channel: channelId, ts: thread_ts });\n return enrichConversationResponse(resp, channelId, resolver);\n },\n ),\n jsonTool(\n \"slack.get_channel_history\",\n \"Read recent Slack channel messages. `channel` accepts a Slack ID (C…/G…/D…), a `#channel-name`, or an `@user-handle` (opens a DM). Responses include resolved user display names and a rewritten text_resolved field.\",\n objectSchema({\n channel: { type: \"string\" },\n limit: { type: \"number\", optional: true },\n }),\n async (input) => {\n const { channel, limit } = input as { channel: string; limit?: number };\n const channelId = await resolver.channelInput(channel);\n assertAllowedChannel(channelId, args.allowedChannels);\n const resp = await client.conversations.history({\n channel: channelId,\n limit: limit ?? 20,\n });\n return enrichConversationResponse(resp, channelId, resolver);\n },\n ),\n jsonTool(\n \"slack.get_user_info\",\n \"Resolve a Slack user to display name, real name, and handle. Accepts a user ID (U0B4357MH7H), an `@handle`, or a bare handle.\",\n objectSchema({\n user: { type: \"string\" },\n }),\n async (input) => {\n const { user } = input as { user: string };\n const userId = await resolver.userInput(user);\n return resolver.user(userId);\n },\n ),\n jsonTool(\n \"slack.get_channel_info\",\n \"Resolve a Slack channel to name and metadata. Accepts a channel ID (C0AQHA6M3PS), a `#channel-name`, or an `@user-handle` (opens a DM).\",\n objectSchema({\n channel: { type: \"string\" },\n }),\n async (input) => {\n const { channel } = input as { channel: string };\n const channelId = await resolver.channelInput(channel);\n assertAllowedChannel(channelId, args.allowedChannels);\n return resolver.channel(channelId);\n },\n ),\n ];\n\n if (args.accessMode === \"read_write\") {\n tools.push(\n jsonTool(\n \"slack.send_message\",\n \"Send a Slack message, optionally as a thread reply. `channel` accepts a Slack ID (C…/G…/D…), a `#channel-name`, or an `@user-handle` (opens a DM).\",\n objectSchema({\n channel: { type: \"string\" },\n text: { type: \"string\" },\n thread_ts: { type: \"string\", optional: true },\n }),\n async (input) => {\n const { channel, text, thread_ts } = input as {\n channel: string;\n text: string;\n thread_ts?: string;\n };\n const channelId = await resolver.channelInput(channel);\n assertAllowedChannel(channelId, args.allowedChannels);\n return client.chat.postMessage({\n channel: channelId,\n text,\n ...(thread_ts ? { thread_ts } : {}),\n });\n },\n ),\n jsonTool(\n \"slack.add_reaction\",\n \"Add a reaction to a Slack message. `channel` accepts a Slack ID, a `#channel-name`, or an `@user-handle`.\",\n objectSchema({\n channel: { type: \"string\" },\n ts: { type: \"string\" },\n name: { type: \"string\" },\n }),\n async (input) => {\n const { channel, ts, name } = input as { channel: string; ts: string; name: string };\n const channelId = await resolver.channelInput(channel);\n assertAllowedChannel(channelId, args.allowedChannels);\n return client.reactions.add({ channel: channelId, timestamp: ts, name });\n },\n ),\n jsonTool(\n \"slack.update_message\",\n \"Update a Slack message. `channel` accepts a Slack ID, a `#channel-name`, or an `@user-handle`.\",\n objectSchema({\n channel: { type: \"string\" },\n ts: { type: \"string\" },\n text: { type: \"string\" },\n }),\n async (input) => {\n const { channel, ts, text } = input as { channel: string; ts: string; text: string };\n const channelId = await resolver.channelInput(channel);\n assertAllowedChannel(channelId, args.allowedChannels);\n return client.chat.update({ channel: channelId, ts, text });\n },\n ),\n );\n }\n\n return tools;\n}\n\nfunction jsonTool(\n name: string,\n description: string,\n inputSchema: Record<string, unknown>,\n call: (input: unknown) => Promise<unknown>,\n): LocalToolHandler {\n return {\n definition: { name, description, inputSchema, source: \"pack:cap-slack\" },\n handler: async ({ input }) => {\n try {\n return { content: JSON.stringify(await call(input), null, 2) };\n } catch (err) {\n return { content: err instanceof Error ? err.message : String(err), isError: true };\n }\n },\n };\n}\n\nfunction assertAllowedChannel(channel: string, allowedChannels: string[] | undefined): void {\n if (allowedChannels?.length && !allowedChannels.includes(channel)) {\n throw new Error(`Slack channel \"${channel}\" is not allowed by cap-slack config`);\n }\n}\n\nfunction objectSchema(properties: Record<string, unknown>) {\n return {\n type: \"object\",\n additionalProperties: false,\n properties,\n required: Object.entries(properties)\n .filter(([, value]) => !(value as { optional?: boolean }).optional)\n .map(([key]) => key),\n };\n}\n\nfunction createResolver(client: WebClient): SlackResolver {\n const users = new Map<string, ResolvedUser>();\n const channels = new Map<string, ResolvedChannel>();\n const pendingUsers = new Map<string, Promise<ResolvedUser>>();\n const pendingChannels = new Map<string, Promise<ResolvedChannel>>();\n\n // Name → ID indexes for reverse lookup. Populated lazily by\n // `ensureChannelsIndex` / `ensureUsersIndex` (which page through\n // `conversations.list` / `users.list` once and cache the full map for\n // the lifetime of this resolver — i.e. the agent process). DM channel\n // ids opened via `conversations.open` are written back into `dmByUserId`\n // so repeated `@handle` sends only hit the API once.\n let channelsIndex: Promise<Map<string, string>> | null = null;\n let usersIndex: Promise<Map<string, string>> | null = null;\n const dmByUserId = new Map<string, string>();\n\n const ensureChannelsIndex = (): Promise<Map<string, string>> => {\n if (channelsIndex) return channelsIndex;\n channelsIndex = (async () => {\n const byName = new Map<string, string>();\n let cursor: string | undefined;\n do {\n const resp = (await client.conversations.list({\n limit: 1000,\n types: \"public_channel,private_channel\",\n exclude_archived: true,\n ...(cursor ? { cursor } : {}),\n })) as unknown as {\n channels?: Array<{ id?: unknown; name?: unknown }>;\n response_metadata?: { next_cursor?: unknown };\n };\n for (const c of resp.channels ?? []) {\n if (typeof c.id === \"string\" && typeof c.name === \"string\") {\n byName.set(c.name.toLowerCase(), c.id);\n // Also warm the by-ID resolver cache so a later\n // `slack.get_channel_info` lookup is a hit.\n if (!channels.has(c.id)) channels.set(c.id, { id: c.id, name: c.name });\n }\n }\n cursor =\n typeof resp.response_metadata?.next_cursor === \"string\" &&\n resp.response_metadata.next_cursor.length > 0\n ? resp.response_metadata.next_cursor\n : undefined;\n } while (cursor);\n return byName;\n })().catch((err) => {\n // Reset on failure so a subsequent lookup retries (e.g. once\n // `channels:read` scope is added without the agent restarting).\n channelsIndex = null;\n throw err;\n });\n return channelsIndex;\n };\n\n const ensureUsersIndex = (): Promise<Map<string, string>> => {\n if (usersIndex) return usersIndex;\n usersIndex = (async () => {\n const byHandle = new Map<string, string>();\n let cursor: string | undefined;\n do {\n const resp = (await client.users.list({\n limit: 200,\n ...(cursor ? { cursor } : {}),\n })) as unknown as {\n members?: Array<{\n id?: unknown;\n name?: unknown;\n real_name?: unknown;\n profile?: { display_name?: unknown; display_name_normalized?: unknown };\n deleted?: unknown;\n }>;\n response_metadata?: { next_cursor?: unknown };\n };\n for (const u of resp.members ?? []) {\n if (typeof u.id !== \"string\" || u.deleted === true) continue;\n // Index every name variant the agent might use. Slack handles\n // are unique, but display names + real names can collide — last\n // writer wins, which is acceptable for an LLM hint.\n for (const candidate of [\n u.name,\n u.real_name,\n u.profile?.display_name,\n u.profile?.display_name_normalized,\n ]) {\n if (typeof candidate === \"string\" && candidate.length > 0) {\n byHandle.set(candidate.toLowerCase(), u.id);\n }\n }\n if (!users.has(u.id)) {\n users.set(u.id, buildResolvedUser(u.id, u as unknown as Record<string, unknown>));\n }\n }\n cursor =\n typeof resp.response_metadata?.next_cursor === \"string\" &&\n resp.response_metadata.next_cursor.length > 0\n ? resp.response_metadata.next_cursor\n : undefined;\n } while (cursor);\n return byHandle;\n })().catch((err) => {\n usersIndex = null;\n throw err;\n });\n return usersIndex;\n };\n\n const openDmFor = async (userId: string): Promise<string> => {\n const cached = dmByUserId.get(userId);\n if (cached) return cached;\n const resp = (await client.conversations.open({ users: userId })) as unknown as {\n channel?: { id?: unknown };\n };\n const channelId = resp.channel?.id;\n if (typeof channelId !== \"string\") {\n throw new Error(`conversations.open for ${userId} returned no channel id`);\n }\n dmByUserId.set(userId, channelId);\n return channelId;\n };\n\n const resolveUserInput = async (input: string): Promise<string> => {\n const trimmed = input.trim();\n if (trimmed.length === 0) throw new Error(\"Slack user input is empty\");\n // Accept Slack's `<@U123>` mention wrapping verbatim.\n const mention = /^<@(U[A-Z0-9]+)(?:\\|[^>]+)?>$/.exec(trimmed);\n if (mention?.[1]) return mention[1];\n if (USER_ID_PATTERN.test(trimmed)) return trimmed;\n const handle = trimmed.startsWith(\"@\") ? trimmed.slice(1) : trimmed;\n if (handle.length === 0) throw new Error(\"Slack user input has no handle after '@'\");\n const index = await ensureUsersIndex();\n const id = index.get(handle.toLowerCase());\n if (!id) {\n throw new Error(\n `Slack user \"${input}\" not found. Check the handle, or ensure the bot has 'users:read' scope.`,\n );\n }\n return id;\n };\n\n const resolveChannelInput = async (input: string): Promise<string> => {\n const trimmed = input.trim();\n if (trimmed.length === 0) throw new Error(\"Slack channel input is empty\");\n // Accept Slack's `<#C123|name>` mention wrapping verbatim.\n const mention = /^<#([CGD][A-Z0-9]+)(?:\\|[^>]+)?>$/.exec(trimmed);\n if (mention?.[1]) return mention[1];\n if (CHANNEL_ID_PATTERN.test(trimmed)) return trimmed;\n if (trimmed.startsWith(\"@\")) {\n const userId = await resolveUserInput(trimmed);\n return openDmFor(userId);\n }\n const name = trimmed.startsWith(\"#\") ? trimmed.slice(1) : trimmed;\n if (name.length === 0) throw new Error(\"Slack channel input has no name after '#'\");\n const index = await ensureChannelsIndex();\n const id = index.get(name.toLowerCase());\n if (!id) {\n throw new Error(\n `Slack channel \"${input}\" not found. Check the name, or ensure the bot has 'channels:read' / 'groups:read' scope and is a member of the channel.`,\n );\n }\n return id;\n };\n\n return {\n async user(id: string): Promise<ResolvedUser> {\n const cached = users.get(id);\n if (cached) return cached;\n const pending = pendingUsers.get(id);\n if (pending) return pending;\n const fetchOne = (async (): Promise<ResolvedUser> => {\n try {\n const resp = await client.users.info({ user: id });\n const raw = (resp as unknown as { user?: Record<string, unknown> }).user;\n const info = buildResolvedUser(id, raw);\n users.set(id, info);\n return info;\n } catch {\n const info: ResolvedUser = { id };\n users.set(id, info);\n return info;\n } finally {\n pendingUsers.delete(id);\n }\n })();\n pendingUsers.set(id, fetchOne);\n return fetchOne;\n },\n async channel(id: string): Promise<ResolvedChannel> {\n const cached = channels.get(id);\n if (cached) return cached;\n const pending = pendingChannels.get(id);\n if (pending) return pending;\n const fetchOne = (async (): Promise<ResolvedChannel> => {\n try {\n const resp = await client.conversations.info({ channel: id });\n const raw = (resp as unknown as { channel?: Record<string, unknown> }).channel;\n const info = buildResolvedChannel(id, raw);\n channels.set(id, info);\n return info;\n } catch {\n const info: ResolvedChannel = { id };\n channels.set(id, info);\n return info;\n } finally {\n pendingChannels.delete(id);\n }\n })();\n pendingChannels.set(id, fetchOne);\n return fetchOne;\n },\n channelInput: resolveChannelInput,\n userInput: resolveUserInput,\n };\n}\n\nfunction buildResolvedUser(id: string, raw: Record<string, unknown> | undefined): ResolvedUser {\n if (!raw) return { id };\n const profile = (raw.profile as Record<string, unknown> | undefined) ?? undefined;\n const info: ResolvedUser = { id };\n const name = typeof raw.name === \"string\" ? raw.name : undefined;\n if (name) info.name = name;\n const realName =\n typeof raw.real_name === \"string\"\n ? raw.real_name\n : typeof profile?.real_name === \"string\"\n ? (profile.real_name as string)\n : undefined;\n if (realName) info.real_name = realName;\n const displayName =\n typeof profile?.display_name === \"string\" && (profile.display_name as string).length > 0\n ? (profile.display_name as string)\n : undefined;\n if (displayName) info.display_name = displayName;\n if (raw.is_bot === true) info.is_bot = true;\n return info;\n}\n\nfunction buildResolvedChannel(\n id: string,\n raw: Record<string, unknown> | undefined,\n): ResolvedChannel {\n if (!raw) return { id };\n const info: ResolvedChannel = { id };\n if (typeof raw.name === \"string\") info.name = raw.name;\n if (raw.is_private === true) info.is_private = true;\n if (raw.is_archived === true) info.is_archived = true;\n return info;\n}\n\nfunction displayLabel(user: ResolvedUser | undefined): string | undefined {\n return user?.display_name ?? user?.real_name ?? user?.name;\n}\n\nconst USER_MENTION = /<@(U[A-Z0-9]+)(?:\\|[^>]+)?>/g;\nconst CHANNEL_MENTION = /<#(C[A-Z0-9]+)(?:\\|([^>]+))?>/g;\n\nasync function enrichConversationResponse(\n resp: unknown,\n channelId: string,\n resolver: SlackResolver,\n): Promise<unknown> {\n if (!resp || typeof resp !== \"object\") return resp;\n const body = resp as Record<string, unknown>;\n const rawMessages = Array.isArray(body.messages) ? (body.messages as unknown[]) : [];\n const userIds = new Set<string>();\n const channelIds = new Set<string>([channelId]);\n for (const msg of rawMessages) {\n if (!msg || typeof msg !== \"object\") continue;\n const m = msg as Record<string, unknown>;\n if (typeof m.user === \"string\") userIds.add(m.user);\n if (typeof m.text === \"string\") collectMentions(m.text, userIds, channelIds);\n }\n\n const [resolvedUserList, resolvedChannelList] = await Promise.all([\n Promise.all([...userIds].map(async (id) => [id, await resolver.user(id)] as const)),\n Promise.all([...channelIds].map(async (id) => [id, await resolver.channel(id)] as const)),\n ]);\n const usersById = new Map<string, ResolvedUser>(resolvedUserList);\n const channelsById = new Map<string, ResolvedChannel>(resolvedChannelList);\n\n const enrichedMessages = rawMessages.map((msg) => {\n if (!msg || typeof msg !== \"object\") return msg;\n const m = msg as Record<string, unknown>;\n const out: Record<string, unknown> = { ...m };\n if (typeof m.user === \"string\") {\n const label = displayLabel(usersById.get(m.user));\n if (label) out.user_display_name = label;\n }\n if (typeof m.text === \"string\") {\n out.text_resolved = rewriteMentions(m.text, usersById, channelsById);\n }\n return out;\n });\n\n const resolvedUsers: Record<string, ResolvedUser> = {};\n for (const [id, info] of usersById) resolvedUsers[id] = info;\n const channelInfo = channelsById.get(channelId);\n\n return {\n ...body,\n messages: enrichedMessages,\n resolved_users: resolvedUsers,\n ...(channelInfo ? { resolved_channel: channelInfo } : {}),\n };\n}\n\nfunction collectMentions(text: string, userIds: Set<string>, channelIds: Set<string>): void {\n for (const match of text.matchAll(USER_MENTION)) {\n const id = match[1];\n if (id) userIds.add(id);\n }\n for (const match of text.matchAll(CHANNEL_MENTION)) {\n const id = match[1];\n if (id) channelIds.add(id);\n }\n}\n\nfunction rewriteMentions(\n text: string,\n users: Map<string, ResolvedUser>,\n channels: Map<string, ResolvedChannel>,\n): string {\n return text\n .replace(USER_MENTION, (_match, id: string) => {\n const label = displayLabel(users.get(id));\n return label ? `@${label}` : `<@${id}>`;\n })\n .replace(CHANNEL_MENTION, (_match, id: string, fallbackName?: string) => {\n const name = channels.get(id)?.name ?? fallbackName;\n return name ? `#${name}` : `<#${id}>`;\n });\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\n\nconst FIVE_MINUTES_SECONDS = 60 * 5;\n\nexport function verifySlackSignature(args: {\n rawBody: string;\n signingSecret: string;\n signature: string | null;\n timestamp: string | null;\n nowSeconds?: number;\n}): boolean {\n if (!args.signature?.startsWith(\"v0=\") || !args.timestamp) return false;\n const ts = Number(args.timestamp);\n if (!Number.isFinite(ts)) return false;\n const now = args.nowSeconds ?? Math.floor(Date.now() / 1000);\n if (Math.abs(now - ts) > FIVE_MINUTES_SECONDS) return false;\n\n const base = `v0:${args.timestamp}:${args.rawBody}`;\n const expected = `v0=${createHmac(\"sha256\", args.signingSecret).update(base).digest(\"hex\")}`;\n const expectedBuf = Buffer.from(expected, \"utf8\");\n const actualBuf = Buffer.from(args.signature, \"utf8\");\n if (expectedBuf.length !== actualBuf.length) return false;\n return timingSafeEqual(expectedBuf, actualBuf);\n}\n","import { createHash } from \"node:crypto\";\nimport type { LocalToolHandler } from \"@render-harness/core\";\nimport { type ConnectorContribution, definePack, type PackContext } from \"@render-harness/registry\";\nimport pkg from \"../package.json\" with { type: \"json\" };\nimport { slackConversationId } from \"./convid.js\";\nimport { normalizeSlackEvent, type SlackNormalizeConfig } from \"./normalize.js\";\nimport { type SlackAccessMode, slackTools } from \"./tools.js\";\nimport { verifySlackSignature } from \"./verify.js\";\n\ninterface SlackConfig extends SlackNormalizeConfig {\n agent?: string;\n userId?: string;\n signingSecretEnv?: string;\n botTokenEnv?: string;\n accessMode?: SlackAccessMode;\n}\n\nconst DEFAULT_SIGNING_SECRET_ENV = \"SLACK_SIGNING_SECRET\";\nconst DEFAULT_BOT_TOKEN_ENV = \"SLACK_BOT_TOKEN\";\n\nconst pack = definePack({\n name: \"cap-slack\",\n version: pkg.version,\n envSchema: [\n {\n name: DEFAULT_SIGNING_SECRET_ENV,\n required: true,\n secret: true,\n description: \"Slack signing secret used to verify Events API requests.\",\n },\n {\n name: DEFAULT_BOT_TOKEN_ENV,\n required: true,\n secret: true,\n description: \"Slack bot token used for read tools and optional write tools.\",\n },\n ],\n localTools(ctx: PackContext): LocalToolHandler[] {\n const cfg = readConfig(ctx.config);\n const botToken = ctx.env(cfg.botTokenEnv);\n if (!botToken) return [];\n return slackTools({\n botToken,\n accessMode: cfg.accessMode,\n ...(cfg.allowedChannels ? { allowedChannels: cfg.allowedChannels } : {}),\n });\n },\n connectors(ctx: PackContext): ConnectorContribution[] {\n const cfg = readConfig(ctx.config);\n return [\n {\n key: \"slack\",\n webhook: async (req, webCtx) => {\n const rawBody = await req.text();\n const signingSecret = ctx.env(cfg.signingSecretEnv);\n if (!signingSecret) {\n return json({ error: \"missing_secret\", env: cfg.signingSecretEnv }, 500);\n }\n if (\n !verifySlackSignature({\n rawBody,\n signingSecret,\n signature: req.headers.get(\"x-slack-signature\"),\n timestamp: req.headers.get(\"x-slack-request-timestamp\"),\n })\n ) {\n return json({ error: \"invalid_signature\" }, 401);\n }\n\n const parsed = parseBody(rawBody);\n const normalized = normalizeSlackEvent(parsed, cfg);\n if (normalized.kind === \"challenge\") return json({ challenge: normalized.challenge });\n if (normalized.kind === \"noop\")\n return json({ ok: true, skipped: true, reason: normalized.reason });\n\n const agent = webCtx.resolveAgent(cfg.agent);\n const conversationId = slackConversationId({\n teamId: normalized.teamId,\n channel: normalized.channel,\n threadTs: normalized.threadTs,\n });\n const result = await webCtx.enqueueIntoConversation({\n conversationId,\n agentName: agent.name,\n agentVersion: agent.version,\n userId: cfg.userId ?? \"cap-slack\",\n runId: `slack-${hash(normalized.eventId)}`,\n title: `Slack ${normalized.channel}/${normalized.threadTs}`,\n initialContent: [{ type: \"text\", text: normalized.text }],\n metadata: {\n connector: \"cap-slack\",\n teamId: normalized.teamId,\n channel: normalized.channel,\n threadTs: normalized.threadTs,\n ts: normalized.ts,\n ...(normalized.userId ? { userId: normalized.userId } : {}),\n },\n });\n return json(result, result.status === \"enqueued\" ? 202 : 200);\n },\n },\n ];\n },\n});\n\nexport default pack;\n\ntype ResolvedSlackConfig = Required<\n Pick<SlackConfig, \"signingSecretEnv\" | \"botTokenEnv\" | \"accessMode\" | \"includeEdits\">\n> &\n Omit<SlackConfig, \"signingSecretEnv\" | \"botTokenEnv\" | \"accessMode\" | \"includeEdits\">;\n\nfunction readConfig(raw: Record<string, unknown>): ResolvedSlackConfig {\n const cfg: ResolvedSlackConfig = {\n signingSecretEnv: stringValue(raw.signingSecretEnv) ?? DEFAULT_SIGNING_SECRET_ENV,\n botTokenEnv: stringValue(raw.botTokenEnv) ?? DEFAULT_BOT_TOKEN_ENV,\n accessMode: raw.accessMode === \"read_write\" ? \"read_write\" : \"read\",\n includeEdits: raw.includeEdits === true,\n };\n const agent = stringValue(raw.agent);\n if (agent) cfg.agent = agent;\n const userId = stringValue(raw.userId);\n if (userId) cfg.userId = userId;\n const allowedChannels = stringArray(raw.allowedChannels);\n if (allowedChannels) cfg.allowedChannels = allowedChannels;\n return cfg;\n}\n\nfunction parseBody(rawBody: string): unknown {\n try {\n return JSON.parse(rawBody);\n } catch {\n return null;\n }\n}\n\nfunction hash(value: string): string {\n return createHash(\"sha256\").update(value).digest(\"hex\").slice(0, 32);\n}\n\nfunction json(body: unknown, status = 200): Response {\n return Response.json(body, { status });\n}\n\nfunction stringValue(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\nfunction stringArray(value: unknown): string[] | undefined {\n return Array.isArray(value)\n ? value.filter((item): item is string => typeof item === \"string\")\n : undefined;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@render-harness/cap-slack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Slack Events and Web API capability pack for the Render agent harness.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,14 +29,14 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@slack/web-api": "^7.12.0",
|
|
32
|
-
"@render-harness/registry": "0.
|
|
32
|
+
"@render-harness/registry": "0.5.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/node": "^25.6.2",
|
|
36
36
|
"tsup": "^8.5.1",
|
|
37
37
|
"typescript": "^6.0.3",
|
|
38
38
|
"vitest": "^4.1.5",
|
|
39
|
-
"@render-harness/core": "0.
|
|
39
|
+
"@render-harness/core": "0.5.0"
|
|
40
40
|
},
|
|
41
41
|
"publishConfig": {
|
|
42
42
|
"access": "public"
|