@render-harness/cap-slack 0.2.6 → 0.4.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 +12 -2
- package/dist/index.js +171 -5
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -46,8 +46,18 @@ Each Slack thread maps to one harness conversation, so follow-up messages in the
|
|
|
46
46
|
|
|
47
47
|
Read tools are available when `SLACK_BOT_TOKEN` is set:
|
|
48
48
|
|
|
49
|
-
- `slack.get_thread`
|
|
50
|
-
- `slack.get_channel_history`
|
|
49
|
+
- `slack.get_thread` — read a Slack thread's messages.
|
|
50
|
+
- `slack.get_channel_history` — read recent Slack channel messages.
|
|
51
|
+
- `slack.get_user_info` — resolve a Slack user ID (for example `U0B4357MH7H`) to a display name, real name, and handle.
|
|
52
|
+
- `slack.get_channel_info` — resolve a Slack channel ID (for example `C0AQHA6M3PS`) to a channel name and metadata.
|
|
53
|
+
|
|
54
|
+
`slack.get_thread` and `slack.get_channel_history` also auto-enrich their responses so agents don't have to render raw IDs:
|
|
55
|
+
|
|
56
|
+
- Each message gains a `user_display_name` field with the best available label (`display_name` → `real_name` → `name`).
|
|
57
|
+
- Each message gains a `text_resolved` field where `<@U…>` mentions become `@display_name` and `<#C…|name>` mentions become `#name`.
|
|
58
|
+
- The response gains a `resolved_users` map keyed by user ID and a `resolved_channel` object describing the requested channel.
|
|
59
|
+
|
|
60
|
+
User and channel lookups are cached for the lifetime of the agent process to keep enrichment cheap across turns.
|
|
51
61
|
|
|
52
62
|
Set `accessMode: read_write` to enable write tools:
|
|
53
63
|
|
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.4.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}`;
|
|
@@ -62,10 +62,11 @@ function stringValue(value) {
|
|
|
62
62
|
}
|
|
63
63
|
function slackTools(args) {
|
|
64
64
|
const client = new WebClient(args.botToken);
|
|
65
|
+
const resolver = createResolver(client);
|
|
65
66
|
const tools = [
|
|
66
67
|
jsonTool(
|
|
67
68
|
"slack.get_thread",
|
|
68
|
-
"Read a Slack thread's messages.",
|
|
69
|
+
"Read a Slack thread's messages. Responses include resolved user display names and a rewritten text_resolved field.",
|
|
69
70
|
objectSchema({
|
|
70
71
|
channel: { type: "string" },
|
|
71
72
|
thread_ts: { type: "string" }
|
|
@@ -73,12 +74,13 @@ function slackTools(args) {
|
|
|
73
74
|
async (input) => {
|
|
74
75
|
const { channel, thread_ts } = input;
|
|
75
76
|
assertAllowedChannel(channel, args.allowedChannels);
|
|
76
|
-
|
|
77
|
+
const resp = await client.conversations.replies({ channel, ts: thread_ts });
|
|
78
|
+
return enrichConversationResponse(resp, channel, resolver);
|
|
77
79
|
}
|
|
78
80
|
),
|
|
79
81
|
jsonTool(
|
|
80
82
|
"slack.get_channel_history",
|
|
81
|
-
"Read recent Slack channel messages.",
|
|
83
|
+
"Read recent Slack channel messages. Responses include resolved user display names and a rewritten text_resolved field.",
|
|
82
84
|
objectSchema({
|
|
83
85
|
channel: { type: "string" },
|
|
84
86
|
limit: { type: "number", optional: true }
|
|
@@ -86,7 +88,31 @@ function slackTools(args) {
|
|
|
86
88
|
async (input) => {
|
|
87
89
|
const { channel, limit } = input;
|
|
88
90
|
assertAllowedChannel(channel, args.allowedChannels);
|
|
89
|
-
|
|
91
|
+
const resp = await client.conversations.history({ channel, limit: limit ?? 20 });
|
|
92
|
+
return enrichConversationResponse(resp, channel, resolver);
|
|
93
|
+
}
|
|
94
|
+
),
|
|
95
|
+
jsonTool(
|
|
96
|
+
"slack.get_user_info",
|
|
97
|
+
"Resolve a Slack user ID (e.g. U0B4357MH7H) to a display name, real name, and handle.",
|
|
98
|
+
objectSchema({
|
|
99
|
+
user: { type: "string" }
|
|
100
|
+
}),
|
|
101
|
+
async (input) => {
|
|
102
|
+
const { user } = input;
|
|
103
|
+
return resolver.user(user);
|
|
104
|
+
}
|
|
105
|
+
),
|
|
106
|
+
jsonTool(
|
|
107
|
+
"slack.get_channel_info",
|
|
108
|
+
"Resolve a Slack channel ID (e.g. C0AQHA6M3PS) to a channel name and metadata.",
|
|
109
|
+
objectSchema({
|
|
110
|
+
channel: { type: "string" }
|
|
111
|
+
}),
|
|
112
|
+
async (input) => {
|
|
113
|
+
const { channel } = input;
|
|
114
|
+
assertAllowedChannel(channel, args.allowedChannels);
|
|
115
|
+
return resolver.channel(channel);
|
|
90
116
|
}
|
|
91
117
|
)
|
|
92
118
|
];
|
|
@@ -167,6 +193,146 @@ function objectSchema(properties) {
|
|
|
167
193
|
required: Object.entries(properties).filter(([, value]) => !value.optional).map(([key]) => key)
|
|
168
194
|
};
|
|
169
195
|
}
|
|
196
|
+
function createResolver(client) {
|
|
197
|
+
const users = /* @__PURE__ */ new Map();
|
|
198
|
+
const channels = /* @__PURE__ */ new Map();
|
|
199
|
+
const pendingUsers = /* @__PURE__ */ new Map();
|
|
200
|
+
const pendingChannels = /* @__PURE__ */ new Map();
|
|
201
|
+
return {
|
|
202
|
+
async user(id) {
|
|
203
|
+
const cached = users.get(id);
|
|
204
|
+
if (cached) return cached;
|
|
205
|
+
const pending = pendingUsers.get(id);
|
|
206
|
+
if (pending) return pending;
|
|
207
|
+
const fetchOne = (async () => {
|
|
208
|
+
try {
|
|
209
|
+
const resp = await client.users.info({ user: id });
|
|
210
|
+
const raw = resp.user;
|
|
211
|
+
const info = buildResolvedUser(id, raw);
|
|
212
|
+
users.set(id, info);
|
|
213
|
+
return info;
|
|
214
|
+
} catch {
|
|
215
|
+
const info = { id };
|
|
216
|
+
users.set(id, info);
|
|
217
|
+
return info;
|
|
218
|
+
} finally {
|
|
219
|
+
pendingUsers.delete(id);
|
|
220
|
+
}
|
|
221
|
+
})();
|
|
222
|
+
pendingUsers.set(id, fetchOne);
|
|
223
|
+
return fetchOne;
|
|
224
|
+
},
|
|
225
|
+
async channel(id) {
|
|
226
|
+
const cached = channels.get(id);
|
|
227
|
+
if (cached) return cached;
|
|
228
|
+
const pending = pendingChannels.get(id);
|
|
229
|
+
if (pending) return pending;
|
|
230
|
+
const fetchOne = (async () => {
|
|
231
|
+
try {
|
|
232
|
+
const resp = await client.conversations.info({ channel: id });
|
|
233
|
+
const raw = resp.channel;
|
|
234
|
+
const info = buildResolvedChannel(id, raw);
|
|
235
|
+
channels.set(id, info);
|
|
236
|
+
return info;
|
|
237
|
+
} catch {
|
|
238
|
+
const info = { id };
|
|
239
|
+
channels.set(id, info);
|
|
240
|
+
return info;
|
|
241
|
+
} finally {
|
|
242
|
+
pendingChannels.delete(id);
|
|
243
|
+
}
|
|
244
|
+
})();
|
|
245
|
+
pendingChannels.set(id, fetchOne);
|
|
246
|
+
return fetchOne;
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
function buildResolvedUser(id, raw) {
|
|
251
|
+
if (!raw) return { id };
|
|
252
|
+
const profile = raw.profile ?? void 0;
|
|
253
|
+
const info = { id };
|
|
254
|
+
const name = typeof raw.name === "string" ? raw.name : void 0;
|
|
255
|
+
if (name) info.name = name;
|
|
256
|
+
const realName = typeof raw.real_name === "string" ? raw.real_name : typeof profile?.real_name === "string" ? profile.real_name : void 0;
|
|
257
|
+
if (realName) info.real_name = realName;
|
|
258
|
+
const displayName = typeof profile?.display_name === "string" && profile.display_name.length > 0 ? profile.display_name : void 0;
|
|
259
|
+
if (displayName) info.display_name = displayName;
|
|
260
|
+
if (raw.is_bot === true) info.is_bot = true;
|
|
261
|
+
return info;
|
|
262
|
+
}
|
|
263
|
+
function buildResolvedChannel(id, raw) {
|
|
264
|
+
if (!raw) return { id };
|
|
265
|
+
const info = { id };
|
|
266
|
+
if (typeof raw.name === "string") info.name = raw.name;
|
|
267
|
+
if (raw.is_private === true) info.is_private = true;
|
|
268
|
+
if (raw.is_archived === true) info.is_archived = true;
|
|
269
|
+
return info;
|
|
270
|
+
}
|
|
271
|
+
function displayLabel(user) {
|
|
272
|
+
return user?.display_name ?? user?.real_name ?? user?.name;
|
|
273
|
+
}
|
|
274
|
+
var USER_MENTION = /<@(U[A-Z0-9]+)(?:\|[^>]+)?>/g;
|
|
275
|
+
var CHANNEL_MENTION = /<#(C[A-Z0-9]+)(?:\|([^>]+))?>/g;
|
|
276
|
+
async function enrichConversationResponse(resp, channelId, resolver) {
|
|
277
|
+
if (!resp || typeof resp !== "object") return resp;
|
|
278
|
+
const body = resp;
|
|
279
|
+
const rawMessages = Array.isArray(body.messages) ? body.messages : [];
|
|
280
|
+
const userIds = /* @__PURE__ */ new Set();
|
|
281
|
+
const channelIds = /* @__PURE__ */ new Set([channelId]);
|
|
282
|
+
for (const msg of rawMessages) {
|
|
283
|
+
if (!msg || typeof msg !== "object") continue;
|
|
284
|
+
const m = msg;
|
|
285
|
+
if (typeof m.user === "string") userIds.add(m.user);
|
|
286
|
+
if (typeof m.text === "string") collectMentions(m.text, userIds, channelIds);
|
|
287
|
+
}
|
|
288
|
+
const [resolvedUserList, resolvedChannelList] = await Promise.all([
|
|
289
|
+
Promise.all([...userIds].map(async (id) => [id, await resolver.user(id)])),
|
|
290
|
+
Promise.all([...channelIds].map(async (id) => [id, await resolver.channel(id)]))
|
|
291
|
+
]);
|
|
292
|
+
const usersById = new Map(resolvedUserList);
|
|
293
|
+
const channelsById = new Map(resolvedChannelList);
|
|
294
|
+
const enrichedMessages = rawMessages.map((msg) => {
|
|
295
|
+
if (!msg || typeof msg !== "object") return msg;
|
|
296
|
+
const m = msg;
|
|
297
|
+
const out = { ...m };
|
|
298
|
+
if (typeof m.user === "string") {
|
|
299
|
+
const label = displayLabel(usersById.get(m.user));
|
|
300
|
+
if (label) out.user_display_name = label;
|
|
301
|
+
}
|
|
302
|
+
if (typeof m.text === "string") {
|
|
303
|
+
out.text_resolved = rewriteMentions(m.text, usersById, channelsById);
|
|
304
|
+
}
|
|
305
|
+
return out;
|
|
306
|
+
});
|
|
307
|
+
const resolvedUsers = {};
|
|
308
|
+
for (const [id, info] of usersById) resolvedUsers[id] = info;
|
|
309
|
+
const channelInfo = channelsById.get(channelId);
|
|
310
|
+
return {
|
|
311
|
+
...body,
|
|
312
|
+
messages: enrichedMessages,
|
|
313
|
+
resolved_users: resolvedUsers,
|
|
314
|
+
...channelInfo ? { resolved_channel: channelInfo } : {}
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
function collectMentions(text, userIds, channelIds) {
|
|
318
|
+
for (const match of text.matchAll(USER_MENTION)) {
|
|
319
|
+
const id = match[1];
|
|
320
|
+
if (id) userIds.add(id);
|
|
321
|
+
}
|
|
322
|
+
for (const match of text.matchAll(CHANNEL_MENTION)) {
|
|
323
|
+
const id = match[1];
|
|
324
|
+
if (id) channelIds.add(id);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
function rewriteMentions(text, users, channels) {
|
|
328
|
+
return text.replace(USER_MENTION, (_match, id) => {
|
|
329
|
+
const label = displayLabel(users.get(id));
|
|
330
|
+
return label ? `@${label}` : `<@${id}>`;
|
|
331
|
+
}).replace(CHANNEL_MENTION, (_match, id, fallbackName) => {
|
|
332
|
+
const name = channels.get(id)?.name ?? fallbackName;
|
|
333
|
+
return name ? `#${name}` : `<#${id}>`;
|
|
334
|
+
});
|
|
335
|
+
}
|
|
170
336
|
var FIVE_MINUTES_SECONDS = 60 * 5;
|
|
171
337
|
function verifySlackSignature(args) {
|
|
172
338
|
if (!args.signature?.startsWith("v0=") || !args.timestamp) return false;
|
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":";;;;;;;AAAA,IAAA,eAAA,GAAA;AAAA,EAEE,OAAA,EAAW,OAmDb,CAAA;ACnDO,SAAS,oBAAoB,IAAA,EAIzB;AACT,EAAA,MAAM,MAAA,GAAS,WAAW,QAAQ,CAAA,CAC/B,OAAO,CAAA,EAAG,IAAA,CAAK,MAAM,CAAA,CAAA,EAAI,IAAA,CAAK,OAAO,CAAA,CAAA,EAAI,IAAA,CAAK,QAAQ,CAAA,CAAE,CAAA,CACxD,OAAO,KAAK,CAAA,CACZ,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA;AACd,EAAA,OAAO,SAAS,MAAM,CAAA,CAAA;AACxB;;;ACOO,SAAS,mBAAA,CACd,IAAA,EACA,GAAA,GAA4B,EAAC,EACP;AACtB,EAAA,IAAI,CAAC,IAAA,IAAQ,OAAO,IAAA,KAAS,QAAA,SAAiB,EAAE,IAAA,EAAM,MAAA,EAAQ,MAAA,EAAQ,cAAA,EAAe;AACrF,EAAA,MAAM,OAAA,GAAU,IAAA;AAChB,EAAA,IAAI,OAAA,CAAQ,SAAS,kBAAA,EAAoB;AACvC,IAAA,MAAM,SAAA,GAAY,WAAA,CAAY,OAAA,CAAQ,SAAS,CAAA;AAC/C,IAAA,OAAO,SAAA,GACH,EAAE,IAAA,EAAM,WAAA,EAAa,SAAA,KACrB,EAAE,IAAA,EAAM,MAAA,EAAQ,MAAA,EAAQ,mBAAA,EAAoB;AAAA,EAClD;AACA,EAAA,IAAI,OAAA,CAAQ,SAAS,gBAAA,EAAkB,OAAO,EAAE,IAAA,EAAM,MAAA,EAAQ,QAAQ,kBAAA,EAAmB;AAEzF,EAAA,MAAM,KAAA,GAAQ,WAAA,CAAY,OAAA,CAAQ,KAAK,CAAA;AACvC,EAAA,IAAI,CAAC,KAAA,EAAO,OAAO,EAAE,IAAA,EAAM,MAAA,EAAQ,QAAQ,eAAA,EAAgB;AAC3D,EAAA,MAAM,SAAA,GAAY,WAAA,CAAY,KAAA,CAAM,IAAI,CAAA;AACxC,EAAA,IAAI,SAAA,KAAc,aAAA,IAAiB,SAAA,KAAc,SAAA,EAAW;AAC1D,IAAA,OAAO,EAAE,IAAA,EAAM,MAAA,EAAQ,MAAA,EAAQ,mBAAA,EAAoB;AAAA,EACrD;AACA,EAAA,IAAI,KAAA,CAAM,MAAA,IAAU,KAAA,CAAM,OAAA,KAAY,aAAA;AACpC,IAAA,OAAO,EAAE,IAAA,EAAM,MAAA,EAAQ,MAAA,EAAQ,aAAA,EAAc;AAC/C,EAAA,IAAI,KAAA,CAAM,OAAA,KAAY,iBAAA,IAAqB,CAAC,IAAI,YAAA,EAAc;AAC5D,IAAA,OAAO,EAAE,IAAA,EAAM,MAAA,EAAQ,MAAA,EAAQ,cAAA,EAAe;AAAA,EAChD;AAEA,EAAA,MAAM,OAAA,GAAU,WAAA,CAAY,KAAA,CAAM,OAAO,CAAA;AACzC,EAAA,IAAI,CAAC,OAAA,EAAS,OAAO,EAAE,IAAA,EAAM,MAAA,EAAQ,QAAQ,iBAAA,EAAkB;AAC/D,EAAA,IAAI,GAAA,CAAI,iBAAiB,MAAA,IAAU,CAAC,IAAI,eAAA,CAAgB,QAAA,CAAS,OAAO,CAAA,EAAG;AACzE,IAAA,OAAO,EAAE,IAAA,EAAM,MAAA,EAAQ,MAAA,EAAQ,qBAAA,EAAsB;AAAA,EACvD;AAEA,EAAA,MAAM,SAAS,WAAA,CAAY,OAAA,CAAQ,OAAO,CAAA,IAAK,WAAA,CAAY,MAAM,IAAI,CAAA;AACrE,EAAA,MAAM,EAAA,GAAK,WAAA,CAAY,KAAA,CAAM,EAAE,CAAA;AAC/B,EAAA,MAAM,IAAA,GAAO,WAAA,CAAY,KAAA,CAAM,IAAI,CAAA,IAAK,EAAA;AACxC,EAAA,MAAM,OAAA,GACJ,WAAA,CAAY,OAAA,CAAQ,QAAQ,CAAA,IAAK,CAAA,EAAG,MAAA,IAAU,MAAM,CAAA,CAAA,EAAI,OAAO,CAAA,CAAA,EAAI,EAAA,IAAM,OAAO,CAAA,CAAA;AAClF,EAAA,IAAI,CAAC,MAAA,IAAU,CAAC,EAAA,IAAM,KAAK,IAAA,EAAK,CAAE,MAAA,KAAW,CAAA,EAAG,OAAO,EAAE,IAAA,EAAM,MAAA,EAAQ,QAAQ,gBAAA,EAAiB;AAChG,EAAA,MAAM,MAAA,GAAS,WAAA,CAAY,KAAA,CAAM,IAAI,CAAA;AAErC,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,SAAA;AAAA,IACN,IAAA;AAAA,IACA,MAAA;AAAA,IACA,OAAA;AAAA,IACA,EAAA;AAAA,IACA,QAAA,EAAU,WAAA,CAAY,KAAA,CAAM,SAAS,CAAA,IAAK,EAAA;AAAA,IAC1C,OAAA;AAAA,IACA,GAAI,MAAA,GAAS,EAAE,MAAA,KAAW;AAAC,GAC7B;AACF;AAEA,SAAS,YAAY,KAAA,EAAgD;AACnE,EAAA,OAAO,KAAA,IAAS,OAAO,KAAA,KAAU,QAAA,IAAY,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,GAC5D,KAAA,GACD,IAAA;AACN;AAEA,SAAS,YAAY,KAAA,EAAoC;AACvD,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,CAAM,MAAA,GAAS,IAAI,KAAA,GAAQ,MAAA;AACjE;AC1EO,SAAS,WAAW,IAAA,EAIJ;AACrB,EAAA,MAAM,MAAA,GAAS,IAAI,SAAA,CAAU,IAAA,CAAK,QAAQ,CAAA;AAC1C,EAAA,MAAM,KAAA,GAA4B;AAAA,IAChC,QAAA;AAAA,MACE,kBAAA;AAAA,MACA,iCAAA;AAAA,MACA,YAAA,CAAa;AAAA,QACX,OAAA,EAAS,EAAE,IAAA,EAAM,QAAA,EAAS;AAAA,QAC1B,SAAA,EAAW,EAAE,IAAA,EAAM,QAAA;AAAS,OAC7B,CAAA;AAAA,MACD,OAAO,KAAA,KAAU;AACf,QAAA,MAAM,EAAE,OAAA,EAAS,SAAA,EAAU,GAAI,KAAA;AAC/B,QAAA,oBAAA,CAAqB,OAAA,EAAS,KAAK,eAAe,CAAA;AAClD,QAAA,OAAO,OAAO,aAAA,CAAc,OAAA,CAAQ,EAAE,OAAA,EAAS,EAAA,EAAI,WAAW,CAAA;AAAA,MAChE;AAAA,KACF;AAAA,IACA,QAAA;AAAA,MACE,2BAAA;AAAA,MACA,qCAAA;AAAA,MACA,YAAA,CAAa;AAAA,QACX,OAAA,EAAS,EAAE,IAAA,EAAM,QAAA,EAAS;AAAA,QAC1B,KAAA,EAAO,EAAE,IAAA,EAAM,QAAA,EAAU,UAAU,IAAA;AAAK,OACzC,CAAA;AAAA,MACD,OAAO,KAAA,KAAU;AACf,QAAA,MAAM,EAAE,OAAA,EAAS,KAAA,EAAM,GAAI,KAAA;AAC3B,QAAA,oBAAA,CAAqB,OAAA,EAAS,KAAK,eAAe,CAAA;AAClD,QAAA,OAAO,MAAA,CAAO,cAAc,OAAA,CAAQ,EAAE,SAAS,KAAA,EAAO,KAAA,IAAS,IAAI,CAAA;AAAA,MACrE;AAAA;AACF,GACF;AAEA,EAAA,IAAI,IAAA,CAAK,eAAe,YAAA,EAAc;AACpC,IAAA,KAAA,CAAM,IAAA;AAAA,MACJ,QAAA;AAAA,QACE,oBAAA;AAAA,QACA,qDAAA;AAAA,QACA,YAAA,CAAa;AAAA,UACX,OAAA,EAAS,EAAE,IAAA,EAAM,QAAA,EAAS;AAAA,UAC1B,IAAA,EAAM,EAAE,IAAA,EAAM,QAAA,EAAS;AAAA,UACvB,SAAA,EAAW,EAAE,IAAA,EAAM,QAAA,EAAU,UAAU,IAAA;AAAK,SAC7C,CAAA;AAAA,QACD,OAAO,KAAA,KAAU;AACf,UAAA,MAAM,EAAE,OAAA,EAAS,IAAA,EAAM,SAAA,EAAU,GAAI,KAAA;AAKrC,UAAA,oBAAA,CAAqB,OAAA,EAAS,KAAK,eAAe,CAAA;AAClD,UAAA,OAAO,MAAA,CAAO,KAAK,WAAA,CAAY;AAAA,YAC7B,OAAA;AAAA,YACA,IAAA;AAAA,YACA,GAAI,SAAA,GAAY,EAAE,SAAA,KAAc;AAAC,WAClC,CAAA;AAAA,QACH;AAAA,OACF;AAAA,MACA,QAAA;AAAA,QACE,oBAAA;AAAA,QACA,oCAAA;AAAA,QACA,YAAA,CAAa;AAAA,UACX,OAAA,EAAS,EAAE,IAAA,EAAM,QAAA,EAAS;AAAA,UAC1B,EAAA,EAAI,EAAE,IAAA,EAAM,QAAA,EAAS;AAAA,UACrB,IAAA,EAAM,EAAE,IAAA,EAAM,QAAA;AAAS,SACxB,CAAA;AAAA,QACD,OAAO,KAAA,KAAU;AACf,UAAA,MAAM,EAAE,OAAA,EAAS,EAAA,EAAI,IAAA,EAAK,GAAI,KAAA;AAC9B,UAAA,oBAAA,CAAqB,OAAA,EAAS,KAAK,eAAe,CAAA;AAClD,UAAA,OAAO,MAAA,CAAO,UAAU,GAAA,CAAI,EAAE,SAAS,SAAA,EAAW,EAAA,EAAI,MAAM,CAAA;AAAA,QAC9D;AAAA,OACF;AAAA,MACA,QAAA;AAAA,QACE,sBAAA;AAAA,QACA,yBAAA;AAAA,QACA,YAAA,CAAa;AAAA,UACX,OAAA,EAAS,EAAE,IAAA,EAAM,QAAA,EAAS;AAAA,UAC1B,EAAA,EAAI,EAAE,IAAA,EAAM,QAAA,EAAS;AAAA,UACrB,IAAA,EAAM,EAAE,IAAA,EAAM,QAAA;AAAS,SACxB,CAAA;AAAA,QACD,OAAO,KAAA,KAAU;AACf,UAAA,MAAM,EAAE,OAAA,EAAS,EAAA,EAAI,IAAA,EAAK,GAAI,KAAA;AAC9B,UAAA,oBAAA,CAAqB,OAAA,EAAS,KAAK,eAAe,CAAA;AAClD,UAAA,OAAO,OAAO,IAAA,CAAK,MAAA,CAAO,EAAE,OAAA,EAAS,EAAA,EAAI,MAAM,CAAA;AAAA,QACjD;AAAA;AACF,KACF;AAAA,EACF;AAEA,EAAA,OAAO,KAAA;AACT;AAEA,SAAS,QAAA,CACP,IAAA,EACA,WAAA,EACA,WAAA,EACA,IAAA,EACkB;AAClB,EAAA,OAAO;AAAA,IACL,YAAY,EAAE,IAAA,EAAM,WAAA,EAAa,WAAA,EAAa,QAAQ,gBAAA,EAAiB;AAAA,IACvE,OAAA,EAAS,OAAO,EAAE,KAAA,EAAM,KAAM;AAC5B,MAAA,IAAI;AACF,QAAA,OAAO,EAAE,OAAA,EAAS,IAAA,CAAK,SAAA,CAAU,MAAM,KAAK,KAAK,CAAA,EAAG,IAAA,EAAM,CAAC,CAAA,EAAE;AAAA,MAC/D,SAAS,GAAA,EAAK;AACZ,QAAA,OAAO,EAAE,OAAA,EAAS,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,UAAU,MAAA,CAAO,GAAG,CAAA,EAAG,OAAA,EAAS,IAAA,EAAK;AAAA,MACpF;AAAA,IACF;AAAA,GACF;AACF;AAEA,SAAS,oBAAA,CAAqB,SAAiB,eAAA,EAA6C;AAC1F,EAAA,IAAI,iBAAiB,MAAA,IAAU,CAAC,eAAA,CAAgB,QAAA,CAAS,OAAO,CAAA,EAAG;AACjE,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,eAAA,EAAkB,OAAO,CAAA,oCAAA,CAAsC,CAAA;AAAA,EACjF;AACF;AAEA,SAAS,aAAa,UAAA,EAAqC;AACzD,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,QAAA;AAAA,IACN,oBAAA,EAAsB,KAAA;AAAA,IACtB,UAAA;AAAA,IACA,QAAA,EAAU,OAAO,OAAA,CAAQ,UAAU,EAChC,MAAA,CAAO,CAAC,GAAG,KAAK,MAAM,CAAE,KAAA,CAAiC,QAAQ,CAAA,CACjE,GAAA,CAAI,CAAC,CAAC,GAAG,MAAM,GAAG;AAAA,GACvB;AACF;ACjIA,IAAM,uBAAuB,EAAA,GAAK,CAAA;AAE3B,SAAS,qBAAqB,IAAA,EAMzB;AACV,EAAA,IAAI,CAAC,KAAK,SAAA,EAAW,UAAA,CAAW,KAAK,CAAA,IAAK,CAAC,IAAA,CAAK,SAAA,EAAW,OAAO,KAAA;AAClE,EAAA,MAAM,EAAA,GAAK,MAAA,CAAO,IAAA,CAAK,SAAS,CAAA;AAChC,EAAA,IAAI,CAAC,MAAA,CAAO,QAAA,CAAS,EAAE,GAAG,OAAO,KAAA;AACjC,EAAA,MAAM,GAAA,GAAM,KAAK,UAAA,IAAc,IAAA,CAAK,MAAM,IAAA,CAAK,GAAA,KAAQ,GAAI,CAAA;AAC3D,EAAA,IAAI,KAAK,GAAA,CAAI,GAAA,GAAM,EAAE,CAAA,GAAI,sBAAsB,OAAO,KAAA;AAEtD,EAAA,MAAM,OAAO,CAAA,GAAA,EAAM,IAAA,CAAK,SAAS,CAAA,CAAA,EAAI,KAAK,OAAO,CAAA,CAAA;AACjD,EAAA,MAAM,QAAA,GAAW,CAAA,GAAA,EAAM,UAAA,CAAW,QAAA,EAAU,IAAA,CAAK,aAAa,CAAA,CAAE,MAAA,CAAO,IAAI,CAAA,CAAE,MAAA,CAAO,KAAK,CAAC,CAAA,CAAA;AAC1F,EAAA,MAAM,WAAA,GAAc,MAAA,CAAO,IAAA,CAAK,QAAA,EAAU,MAAM,CAAA;AAChD,EAAA,MAAM,SAAA,GAAY,MAAA,CAAO,IAAA,CAAK,IAAA,CAAK,WAAW,MAAM,CAAA;AACpD,EAAA,IAAI,WAAA,CAAY,MAAA,KAAW,SAAA,CAAU,MAAA,EAAQ,OAAO,KAAA;AACpD,EAAA,OAAO,eAAA,CAAgB,aAAa,SAAS,CAAA;AAC/C;;;ACNA,IAAM,0BAAA,GAA6B,sBAAA;AACnC,IAAM,qBAAA,GAAwB,iBAAA;AAE9B,IAAM,OAAO,UAAA,CAAW;AAAA,EACtB,IAAA,EAAM,WAAA;AAAA,EACN,SAAS,eAAA,CAAI,OAAA;AAAA,EACb,SAAA,EAAW;AAAA,IACT;AAAA,MACE,IAAA,EAAM,0BAAA;AAAA,MACN,QAAA,EAAU,IAAA;AAAA,MACV,MAAA,EAAQ,IAAA;AAAA,MACR,WAAA,EAAa;AAAA,KACf;AAAA,IACA;AAAA,MACE,IAAA,EAAM,qBAAA;AAAA,MACN,QAAA,EAAU,IAAA;AAAA,MACV,MAAA,EAAQ,IAAA;AAAA,MACR,WAAA,EAAa;AAAA;AACf,GACF;AAAA,EACA,WAAW,GAAA,EAAsC;AAC/C,IAAA,MAAM,GAAA,GAAM,UAAA,CAAW,GAAA,CAAI,MAAM,CAAA;AACjC,IAAA,MAAM,QAAA,GAAW,GAAA,CAAI,GAAA,CAAI,GAAA,CAAI,WAAW,CAAA;AACxC,IAAA,IAAI,CAAC,QAAA,EAAU,OAAO,EAAC;AACvB,IAAA,OAAO,UAAA,CAAW;AAAA,MAChB,QAAA;AAAA,MACA,YAAY,GAAA,CAAI,UAAA;AAAA,MAChB,GAAI,IAAI,eAAA,GAAkB,EAAE,iBAAiB,GAAA,CAAI,eAAA,KAAoB;AAAC,KACvE,CAAA;AAAA,EACH,CAAA;AAAA,EACA,WAAW,GAAA,EAA2C;AACpD,IAAA,MAAM,GAAA,GAAM,UAAA,CAAW,GAAA,CAAI,MAAM,CAAA;AACjC,IAAA,OAAO;AAAA,MACL;AAAA,QACE,GAAA,EAAK,OAAA;AAAA,QACL,OAAA,EAAS,OAAO,GAAA,EAAK,MAAA,KAAW;AAC9B,UAAA,MAAM,OAAA,GAAU,MAAM,GAAA,CAAI,IAAA,EAAK;AAC/B,UAAA,MAAM,aAAA,GAAgB,GAAA,CAAI,GAAA,CAAI,GAAA,CAAI,gBAAgB,CAAA;AAClD,UAAA,IAAI,CAAC,aAAA,EAAe;AAClB,YAAA,OAAO,IAAA,CAAK,EAAE,KAAA,EAAO,gBAAA,EAAkB,KAAK,GAAA,CAAI,gBAAA,IAAoB,GAAG,CAAA;AAAA,UACzE;AACA,UAAA,IACE,CAAC,oBAAA,CAAqB;AAAA,YACpB,OAAA;AAAA,YACA,aAAA;AAAA,YACA,SAAA,EAAW,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,mBAAmB,CAAA;AAAA,YAC9C,SAAA,EAAW,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,2BAA2B;AAAA,WACvD,CAAA,EACD;AACA,YAAA,OAAO,IAAA,CAAK,EAAE,KAAA,EAAO,mBAAA,IAAuB,GAAG,CAAA;AAAA,UACjD;AAEA,UAAA,MAAM,MAAA,GAAS,UAAU,OAAO,CAAA;AAChC,UAAA,MAAM,UAAA,GAAa,mBAAA,CAAoB,MAAA,EAAQ,GAAG,CAAA;AAClD,UAAA,IAAI,UAAA,CAAW,SAAS,WAAA,EAAa,OAAO,KAAK,EAAE,SAAA,EAAW,UAAA,CAAW,SAAA,EAAW,CAAA;AACpF,UAAA,IAAI,WAAW,IAAA,KAAS,MAAA;AACtB,YAAA,OAAO,IAAA,CAAK,EAAE,EAAA,EAAI,IAAA,EAAM,SAAS,IAAA,EAAM,MAAA,EAAQ,UAAA,CAAW,MAAA,EAAQ,CAAA;AAEpE,UAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,YAAA,CAAa,GAAA,CAAI,KAAK,CAAA;AAC3C,UAAA,MAAM,iBAAiB,mBAAA,CAAoB;AAAA,YACzC,QAAQ,UAAA,CAAW,MAAA;AAAA,YACnB,SAAS,UAAA,CAAW,OAAA;AAAA,YACpB,UAAU,UAAA,CAAW;AAAA,WACtB,CAAA;AACD,UAAA,MAAM,MAAA,GAAS,MAAM,MAAA,CAAO,uBAAA,CAAwB;AAAA,YAClD,cAAA;AAAA,YACA,WAAW,KAAA,CAAM,IAAA;AAAA,YACjB,cAAc,KAAA,CAAM,OAAA;AAAA,YACpB,MAAA,EAAQ,IAAI,MAAA,IAAU,WAAA;AAAA,YACtB,KAAA,EAAO,CAAA,MAAA,EAAS,IAAA,CAAK,UAAA,CAAW,OAAO,CAAC,CAAA,CAAA;AAAA,YACxC,OAAO,CAAA,MAAA,EAAS,UAAA,CAAW,OAAO,CAAA,CAAA,EAAI,WAAW,QAAQ,CAAA,CAAA;AAAA,YACzD,cAAA,EAAgB,CAAC,EAAE,IAAA,EAAM,QAAQ,IAAA,EAAM,UAAA,CAAW,MAAM,CAAA;AAAA,YACxD,QAAA,EAAU;AAAA,cACR,SAAA,EAAW,WAAA;AAAA,cACX,QAAQ,UAAA,CAAW,MAAA;AAAA,cACnB,SAAS,UAAA,CAAW,OAAA;AAAA,cACpB,UAAU,UAAA,CAAW,QAAA;AAAA,cACrB,IAAI,UAAA,CAAW,EAAA;AAAA,cACf,GAAI,WAAW,MAAA,GAAS,EAAE,QAAQ,UAAA,CAAW,MAAA,KAAW;AAAC;AAC3D,WACD,CAAA;AACD,UAAA,OAAO,KAAK,MAAA,EAAQ,MAAA,CAAO,MAAA,KAAW,UAAA,GAAa,MAAM,GAAG,CAAA;AAAA,QAC9D;AAAA;AACF,KACF;AAAA,EACF;AACF,CAAC,CAAA;AAED,IAAO,WAAA,GAAQ;AAOf,SAAS,WAAW,GAAA,EAAmD;AACrE,EAAA,MAAM,GAAA,GAA2B;AAAA,IAC/B,gBAAA,EAAkBA,YAAAA,CAAY,GAAA,CAAI,gBAAgB,CAAA,IAAK,0BAAA;AAAA,IACvD,WAAA,EAAaA,YAAAA,CAAY,GAAA,CAAI,WAAW,CAAA,IAAK,qBAAA;AAAA,IAC7C,UAAA,EAAY,GAAA,CAAI,UAAA,KAAe,YAAA,GAAe,YAAA,GAAe,MAAA;AAAA,IAC7D,YAAA,EAAc,IAAI,YAAA,KAAiB;AAAA,GACrC;AACA,EAAA,MAAM,KAAA,GAAQA,YAAAA,CAAY,GAAA,CAAI,KAAK,CAAA;AACnC,EAAA,IAAI,KAAA,MAAW,KAAA,GAAQ,KAAA;AACvB,EAAA,MAAM,MAAA,GAASA,YAAAA,CAAY,GAAA,CAAI,MAAM,CAAA;AACrC,EAAA,IAAI,MAAA,MAAY,MAAA,GAAS,MAAA;AACzB,EAAA,MAAM,eAAA,GAAkB,WAAA,CAAY,GAAA,CAAI,eAAe,CAAA;AACvD,EAAA,IAAI,eAAA,MAAqB,eAAA,GAAkB,eAAA;AAC3C,EAAA,OAAO,GAAA;AACT;AAEA,SAAS,UAAU,OAAA,EAA0B;AAC3C,EAAA,IAAI;AACF,IAAA,OAAO,IAAA,CAAK,MAAM,OAAO,CAAA;AAAA,EAC3B,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAEA,SAAS,KAAK,KAAA,EAAuB;AACnC,EAAA,OAAOC,UAAAA,CAAW,QAAQ,CAAA,CAAE,MAAA,CAAO,KAAK,CAAA,CAAE,MAAA,CAAO,KAAK,CAAA,CAAE,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA;AACrE;AAEA,SAAS,IAAA,CAAK,IAAA,EAAe,MAAA,GAAS,GAAA,EAAe;AACnD,EAAA,OAAO,QAAA,CAAS,IAAA,CAAK,IAAA,EAAM,EAAE,QAAQ,CAAA;AACvC;AAEA,SAASD,aAAY,KAAA,EAAoC;AACvD,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,CAAM,MAAA,GAAS,IAAI,KAAA,GAAQ,MAAA;AACjE;AAEA,SAAS,YAAY,KAAA,EAAsC;AACzD,EAAA,OAAO,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,GACtB,KAAA,CAAM,MAAA,CAAO,CAAC,IAAA,KAAyB,OAAO,IAAA,KAAS,QAAQ,CAAA,GAC/D,MAAA;AACN","file":"index.js","sourcesContent":["{\n \"name\": \"@render-harness/cap-slack\",\n \"version\": \"0.2.6\",\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 { WebClient } from \"@slack/web-api\";\n\nexport type SlackAccessMode = \"read\" | \"read_write\";\n\nexport function slackTools(args: {\n botToken: string;\n accessMode: SlackAccessMode;\n allowedChannels?: string[];\n}): LocalToolHandler[] {\n const client = new WebClient(args.botToken);\n const tools: LocalToolHandler[] = [\n jsonTool(\n \"slack.get_thread\",\n \"Read a Slack thread's messages.\",\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 return client.conversations.replies({ channel, ts: thread_ts });\n },\n ),\n jsonTool(\n \"slack.get_channel_history\",\n \"Read recent Slack channel messages.\",\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 return client.conversations.history({ channel, limit: limit ?? 20 });\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","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.4.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 { WebClient } from \"@slack/web-api\";\n\nexport type SlackAccessMode = \"read\" | \"read_write\";\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 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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@render-harness/cap-slack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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.4.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.4.0"
|
|
40
40
|
},
|
|
41
41
|
"publishConfig": {
|
|
42
42
|
"access": "public"
|