@opentag/slack 0.1.0 → 0.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Amplift
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -15,7 +15,7 @@ pnpm add @opentag/slack
15
15
  - `normalizeSlackAppMention`: converts a Slack app mention into an `OpenTagEvent`.
16
16
  - `slackThreadKey`: encodes team, channel, and thread timestamp for callback routing.
17
17
  - `parseSlackThreadKey`: decodes a Slack thread key for `chat.postMessage`.
18
- - `SlackChannelBinding`: channel-to-repository binding contract.
18
+ - `SlackChannelBinding`: Slack compatibility binding contract that maps into the generic channel binding layer.
19
19
 
20
20
  ## Example
21
21
 
@@ -34,6 +34,7 @@ const event = normalizeSlackAppMention({
34
34
  binding: {
35
35
  teamId: "T123",
36
36
  channelId: "C123",
37
+ repoProvider: "github",
37
38
  owner: "acme",
38
39
  repo: "demo"
39
40
  }
@@ -0,0 +1,7 @@
1
+ import type { SlackEventProcessorInput } from "./events.js";
2
+ export type SlackDispatcherEventConfig = {
3
+ dispatcherUrl: string;
4
+ dispatcherToken?: string;
5
+ };
6
+ export declare function createSlackDispatcherEventProcessorInput(config: SlackDispatcherEventConfig): SlackEventProcessorInput;
7
+ //# sourceMappingURL=dispatcher-events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatcher-events.d.ts","sourceRoot":"","sources":["../src/dispatcher-events.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAE5D,MAAM,MAAM,0BAA0B,GAAG;IACvC,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,wBAAgB,wCAAwC,CAAC,MAAM,EAAE,0BAA0B,GAAG,wBAAwB,CAsCrH"}
@@ -0,0 +1,70 @@
1
+ import { type OpenTagEvent } from "@opentag/core";
2
+ import { type SlackChannelBinding } from "./normalize.js";
3
+ export type SlackThreadActionInput = {
4
+ id: string;
5
+ rawText: string;
6
+ actor: {
7
+ provider: "slack";
8
+ providerUserId: string;
9
+ handle: string;
10
+ organizationId: string;
11
+ };
12
+ callback: {
13
+ provider: "slack";
14
+ uri: string;
15
+ threadKey: string;
16
+ };
17
+ metadata: Record<string, unknown>;
18
+ };
19
+ export type SlackEventEnvelope = {
20
+ token?: string;
21
+ type: "url_verification" | "event_callback";
22
+ challenge?: string;
23
+ team_id?: string;
24
+ api_app_id?: string;
25
+ event?: {
26
+ type: string;
27
+ user?: string;
28
+ text?: string;
29
+ ts?: string;
30
+ thread_ts?: string;
31
+ channel?: string;
32
+ subtype?: string;
33
+ bot_id?: string;
34
+ };
35
+ event_id?: string;
36
+ event_time?: number;
37
+ authorizations?: Array<{
38
+ user_id?: string;
39
+ }>;
40
+ };
41
+ export type SlackAppRuntimeConfig = {
42
+ agentId: string;
43
+ appId?: string;
44
+ callbackUri?: string;
45
+ };
46
+ export type SlackEventProcessorInput = {
47
+ resolveChannelBinding(input: {
48
+ teamId: string;
49
+ channelId: string;
50
+ }): Promise<SlackChannelBinding | null>;
51
+ createRun(event: OpenTagEvent): Promise<{
52
+ runId: string;
53
+ }>;
54
+ submitThreadAction?(action: SlackThreadActionInput): Promise<unknown>;
55
+ now(): string;
56
+ };
57
+ export type SlackEventProcessorStatus = 200 | 400;
58
+ export type SlackEventProcessorResult = {
59
+ kind: "json";
60
+ status: SlackEventProcessorStatus;
61
+ body: Record<string, unknown>;
62
+ } | {
63
+ kind: "text";
64
+ status: SlackEventProcessorStatus;
65
+ body: string;
66
+ };
67
+ export declare function createSlackEventProcessor(input: SlackEventProcessorInput): {
68
+ process(payload: SlackEventEnvelope, slackApp: SlackAppRuntimeConfig): Promise<SlackEventProcessorResult>;
69
+ };
70
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAC5E,OAAO,EAAwE,KAAK,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAEhI,MAAM,MAAM,sBAAsB,GAAG;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE;QACL,QAAQ,EAAE,OAAO,CAAC;QAClB,cAAc,EAAE,MAAM,CAAC;QACvB,MAAM,EAAE,MAAM,CAAC;QACf,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,QAAQ,EAAE;QACR,QAAQ,EAAE,OAAO,CAAC;QAClB,GAAG,EAAE,MAAM,CAAC;QACZ,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,kBAAkB,GAAG,gBAAgB,CAAC;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,KAAK,CAAC;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC9C,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,qBAAqB,CAAC,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAC;IACzG,SAAS,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3D,kBAAkB,CAAC,CAAC,MAAM,EAAE,sBAAsB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtE,GAAG,IAAI,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG,GAAG,GAAG,GAAG,CAAC;AAElD,MAAM,MAAM,yBAAyB,GACjC;IACE,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,yBAAyB,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B,GACD;IACE,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,yBAAyB,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAUN,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,wBAAwB;qBAE9C,kBAAkB,YAAY,qBAAqB,GAAG,OAAO,CAAC,yBAAyB,CAAC;EA0FlH"}
package/dist/index.d.ts CHANGED
@@ -1,2 +1,6 @@
1
+ export * from "./events.js";
2
+ export * from "./ingress.js";
1
3
  export * from "./normalize.js";
4
+ export * from "./render.js";
5
+ export * from "./socket-mode.js";
2
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC"}
package/dist/index.js CHANGED
@@ -1,10 +1,13 @@
1
+ // src/events.ts
2
+ import { parseThreadActionCommand } from "@opentag/core";
3
+
1
4
  // src/normalize.ts
2
5
  import { commandFromRawText } from "@opentag/core";
3
- function stripSlackAppMention(text, botUserId) {
6
+ function stripSlackAppMention(text2, botUserId) {
4
7
  const patterns = botUserId ? [new RegExp(`^<@${botUserId}>\\s*`, "i"), /^<@[^>]+>\s*/] : [/^<@[^>]+>\s*/];
5
8
  for (const pattern of patterns) {
6
- const stripped = text.replace(pattern, "").trim();
7
- if (stripped !== text.trim()) {
9
+ const stripped = text2.replace(pattern, "").trim();
10
+ if (stripped !== text2.trim()) {
8
11
  return stripped.length > 0 ? stripped : null;
9
12
  }
10
13
  }
@@ -49,11 +52,50 @@ function permissionsForIntent(intent) {
49
52
  }
50
53
  return permissions;
51
54
  }
55
+ function contextPointersForCommand(command) {
56
+ const context = [];
57
+ for (const reference of command.parsed?.references ?? []) {
58
+ if (reference.kind === "url") {
59
+ context.push({
60
+ kind: "url",
61
+ uri: reference.uri,
62
+ visibility: "organization",
63
+ title: reference.title ?? "Command URL reference"
64
+ });
65
+ continue;
66
+ }
67
+ if (reference.kind === "file" || reference.kind === "path") {
68
+ context.push({
69
+ kind: "file",
70
+ uri: reference.uri,
71
+ ...reference.line ? { line: reference.line } : {},
72
+ ...reference.startLine ? { startLine: reference.startLine } : {},
73
+ ...reference.endLine ? { endLine: reference.endLine } : {},
74
+ visibility: "organization",
75
+ title: referenceTitle(reference)
76
+ });
77
+ }
78
+ }
79
+ return context;
80
+ }
81
+ function referenceTitle(reference) {
82
+ return reference.title ?? "Command file reference";
83
+ }
84
+ function commandMetadata(command) {
85
+ if (!command.parsed) return {};
86
+ return {
87
+ commandParser: command.parsed.version,
88
+ commandDiagnostics: command.parsed.diagnostics,
89
+ ...command.parsed.approval ? { approval: command.parsed.approval } : {},
90
+ ...command.parsed.network ? { network: command.parsed.network } : {}
91
+ };
92
+ }
52
93
  function normalizeSlackAppMention(input) {
53
94
  const rawText = stripSlackAppMention(input.text, input.botUserId);
54
95
  if (!rawText) return null;
55
96
  const command = commandFromRawText(rawText);
56
97
  const replyThreadTs = input.threadTs ?? input.ts;
98
+ const agentId = input.agentId ?? "opentag";
57
99
  return {
58
100
  id: `evt_slack_app_mention_${input.eventId}`,
59
101
  source: "slack",
@@ -67,12 +109,14 @@ function normalizeSlackAppMention(input) {
67
109
  },
68
110
  target: {
69
111
  mention: input.botUserId ? `<@${input.botUserId}>` : "<@app>",
70
- agentId: "opentag"
112
+ agentId,
113
+ ...command.parsed?.executorHint ? { executorHint: command.parsed.executorHint } : {}
71
114
  },
72
115
  command,
73
116
  context: [
74
117
  {
75
- kind: "url",
118
+ provider: "slack",
119
+ kind: "message",
76
120
  uri: `slack://team/${input.teamId}/channel/${input.channelId}/message/${input.ts}`,
77
121
  visibility: "organization",
78
122
  title: "Slack message"
@@ -82,7 +126,8 @@ function normalizeSlackAppMention(input) {
82
126
  uri: input.text,
83
127
  visibility: "organization",
84
128
  title: "Slack message text"
85
- }
129
+ },
130
+ ...contextPointersForCommand(command)
86
131
  ],
87
132
  permissions: permissionsForIntent(command.intent),
88
133
  callback: {
@@ -98,16 +143,582 @@ function normalizeSlackAppMention(input) {
98
143
  teamId: input.teamId,
99
144
  channelId: input.channelId,
100
145
  messageTs: input.ts,
101
- repoProvider: "github",
146
+ ...input.appId ? { slackAppId: input.appId } : {},
147
+ ...input.botUserId ? { slackBotUserId: input.botUserId } : {},
148
+ ...commandMetadata(command),
149
+ repoProvider: input.binding.repoProvider ?? "github",
102
150
  owner: input.binding.owner,
103
151
  repo: input.binding.repo
104
152
  }
105
153
  };
106
154
  }
155
+
156
+ // src/events.ts
157
+ function json(body, status = 200) {
158
+ return { kind: "json", status, body };
159
+ }
160
+ function text(body, status = 200) {
161
+ return { kind: "text", status, body };
162
+ }
163
+ function createSlackEventProcessor(input) {
164
+ return {
165
+ async process(payload, slackApp) {
166
+ if (payload.type === "url_verification") {
167
+ return text(payload.challenge ?? "");
168
+ }
169
+ if (payload.type !== "event_callback" || !payload.event || !["app_mention", "message"].includes(payload.event.type)) {
170
+ return json({ ok: true });
171
+ }
172
+ if (payload.event.type === "message" && (payload.event.subtype || payload.event.bot_id)) {
173
+ return json({ ok: true });
174
+ }
175
+ if (!payload.team_id || !payload.event.channel || !payload.event.user || !payload.event.text || !payload.event.ts || !payload.event_id) {
176
+ return json({ error: "invalid_event_payload" }, 400);
177
+ }
178
+ const rawThreadActionText = payload.event.type === "app_mention" ? stripSlackAppMention(payload.event.text, payload.authorizations?.[0]?.user_id) : payload.event.text.trim();
179
+ if (payload.event.type === "message" && (!rawThreadActionText || !parseThreadActionCommand(rawThreadActionText))) {
180
+ return json({ ok: true });
181
+ }
182
+ const binding = await input.resolveChannelBinding({
183
+ teamId: payload.team_id,
184
+ channelId: payload.event.channel
185
+ });
186
+ if (!binding) {
187
+ return json({ ok: true, ignored: "unbound_channel" });
188
+ }
189
+ if (rawThreadActionText && parseThreadActionCommand(rawThreadActionText) && input.submitThreadAction) {
190
+ await input.submitThreadAction({
191
+ id: `approval_slack_${payload.event_id}`,
192
+ rawText: rawThreadActionText,
193
+ actor: {
194
+ provider: "slack",
195
+ providerUserId: payload.event.user,
196
+ handle: payload.event.user,
197
+ organizationId: payload.team_id
198
+ },
199
+ callback: {
200
+ provider: "slack",
201
+ uri: slackApp.callbackUri ?? "https://slack.com/api/chat.postMessage",
202
+ threadKey: encodeSlackThreadKey({
203
+ teamId: payload.team_id,
204
+ channelId: payload.event.channel,
205
+ threadTs: payload.event.thread_ts ?? payload.event.ts
206
+ })
207
+ },
208
+ metadata: {
209
+ teamId: payload.team_id,
210
+ channelId: payload.event.channel,
211
+ messageTs: payload.event.ts,
212
+ ...payload.api_app_id ? { slackAppId: payload.api_app_id } : {},
213
+ ...payload.authorizations?.[0]?.user_id ? { slackBotUserId: payload.authorizations[0].user_id } : {},
214
+ repoProvider: binding.repoProvider ?? "github",
215
+ owner: binding.owner,
216
+ repo: binding.repo
217
+ }
218
+ });
219
+ return json({ ok: true });
220
+ }
221
+ if (payload.event.type !== "app_mention") {
222
+ return json({ ok: true });
223
+ }
224
+ const event = normalizeSlackAppMention({
225
+ teamId: payload.team_id,
226
+ channelId: payload.event.channel,
227
+ userId: payload.event.user,
228
+ text: payload.event.text,
229
+ ts: payload.event.ts,
230
+ eventId: payload.event_id,
231
+ eventTime: payload.event_time ?? Math.floor(Date.parse(input.now()) / 1e3),
232
+ agentId: slackApp.agentId,
233
+ binding,
234
+ ...payload.api_app_id ? { appId: payload.api_app_id } : {},
235
+ ...payload.event.thread_ts ? { threadTs: payload.event.thread_ts } : {},
236
+ ...payload.authorizations?.[0]?.user_id ? { botUserId: payload.authorizations[0].user_id } : {},
237
+ ...slackApp.callbackUri ? { callbackUri: slackApp.callbackUri } : {}
238
+ });
239
+ if (!event) {
240
+ return json({ ok: true, ignored: "empty_command" });
241
+ }
242
+ await input.createRun(event);
243
+ return json({ ok: true });
244
+ }
245
+ };
246
+ }
247
+
248
+ // src/ingress.ts
249
+ import { createHmac, timingSafeEqual } from "crypto";
250
+ import { serve } from "@hono/node-server";
251
+ import { Hono } from "hono";
252
+
253
+ // src/dispatcher-events.ts
254
+ import { randomUUID } from "crypto";
255
+ import { createOpenTagClient } from "@opentag/client";
256
+ function createSlackDispatcherEventProcessorInput(config) {
257
+ const dispatcherClient = createOpenTagClient({
258
+ dispatcherUrl: config.dispatcherUrl,
259
+ ...config.dispatcherToken ? { pairingToken: config.dispatcherToken } : {}
260
+ });
261
+ return {
262
+ async resolveChannelBinding(input) {
263
+ try {
264
+ const { binding } = await dispatcherClient.getChannelBinding({
265
+ provider: "slack",
266
+ accountId: input.teamId,
267
+ conversationId: input.channelId
268
+ });
269
+ return {
270
+ teamId: binding.accountId,
271
+ channelId: binding.conversationId,
272
+ repoProvider: binding.repoProvider,
273
+ owner: binding.owner,
274
+ repo: binding.repo
275
+ };
276
+ } catch (error) {
277
+ if (error instanceof Error && error.message.includes("channel_binding_not_found")) {
278
+ return null;
279
+ }
280
+ throw error;
281
+ }
282
+ },
283
+ async createRun(event) {
284
+ const runId = `run_${randomUUID()}`;
285
+ const created = await dispatcherClient.createRun({ runId, event });
286
+ return created.outcome === "run_created" ? { runId: created.run.id } : { runId };
287
+ },
288
+ async submitThreadAction(action) {
289
+ await dispatcherClient.submitThreadAction(action);
290
+ },
291
+ now: () => (/* @__PURE__ */ new Date()).toISOString()
292
+ };
293
+ }
294
+
295
+ // src/ingress.ts
296
+ function computeSlackSignature(input) {
297
+ const base = `v0:${input.timestamp}:${input.rawBody}`;
298
+ const digest = createHmac("sha256", input.signingSecret).update(base).digest("hex");
299
+ return `v0=${digest}`;
300
+ }
301
+ function verifySlackSignature(input) {
302
+ const expected = computeSlackSignature(input);
303
+ const expectedBuffer = Buffer.from(expected);
304
+ const actualBuffer = Buffer.from(input.signature);
305
+ return expectedBuffer.length === actualBuffer.length && timingSafeEqual(expectedBuffer, actualBuffer);
306
+ }
307
+ function verifySlackTimestamp(input) {
308
+ const timestampSeconds = Number(input.timestamp);
309
+ if (!Number.isFinite(timestampSeconds)) return false;
310
+ const toleranceSeconds = input.toleranceSeconds ?? 300;
311
+ const ageSeconds = Math.abs(Math.floor(input.nowMs / 1e3) - timestampSeconds);
312
+ return ageSeconds <= toleranceSeconds;
313
+ }
314
+ function createSlackEventsApp(input) {
315
+ const app = new Hono();
316
+ const processor = createSlackEventProcessor(input);
317
+ function parseSlackPayload(rawBody) {
318
+ try {
319
+ return JSON.parse(rawBody);
320
+ } catch {
321
+ return null;
322
+ }
323
+ }
324
+ function resolveSlackApp(inputValue) {
325
+ const candidates = inputValue.apiAppId ? input.slackApps.filter((candidate) => !candidate.appId || candidate.appId === inputValue.apiAppId) : input.slackApps;
326
+ if (candidates.length === 0) {
327
+ return { error: "unknown_slack_app" };
328
+ }
329
+ const slackApp = candidates.find(
330
+ (candidate) => verifySlackSignature({
331
+ signingSecret: candidate.signingSecret,
332
+ timestamp: inputValue.timestamp,
333
+ rawBody: inputValue.rawBody,
334
+ signature: inputValue.signature
335
+ })
336
+ );
337
+ return slackApp ? { slackApp } : { error: "invalid_signature" };
338
+ }
339
+ app.post("/slack/events", async (c) => {
340
+ const timestamp = c.req.header("x-slack-request-timestamp");
341
+ const signature = c.req.header("x-slack-signature");
342
+ if (!timestamp || !signature) {
343
+ return c.json({ error: "missing_signature_headers" }, 401);
344
+ }
345
+ if (!verifySlackTimestamp({ timestamp, nowMs: input.clock?.() ?? Date.now() })) {
346
+ return c.json({ error: "stale_signature_timestamp" }, 401);
347
+ }
348
+ const rawBody = await c.req.text();
349
+ const payload = parseSlackPayload(rawBody);
350
+ if (!payload) {
351
+ return c.json({ error: "invalid_json" }, 400);
352
+ }
353
+ const resolvedSlackApp = resolveSlackApp({
354
+ rawBody,
355
+ signature,
356
+ timestamp,
357
+ ...payload.api_app_id ? { apiAppId: payload.api_app_id } : {}
358
+ });
359
+ if ("error" in resolvedSlackApp) {
360
+ return c.json({ error: resolvedSlackApp.error }, 401);
361
+ }
362
+ const result = await processor.process(payload, resolvedSlackApp.slackApp);
363
+ if (result.kind === "text") {
364
+ return c.text(result.body, result.status);
365
+ }
366
+ return c.json(result.body, result.status);
367
+ });
368
+ return app;
369
+ }
370
+ function startSlackIngress(config) {
371
+ const port = config.port ?? 3040;
372
+ const server = serve({
373
+ fetch: createSlackEventsApp({
374
+ slackApps: [
375
+ {
376
+ signingSecret: config.signingSecret,
377
+ agentId: config.agentId ?? "opentag",
378
+ ...config.appId ? { appId: config.appId } : {},
379
+ ...config.callbackUri ? { callbackUri: config.callbackUri } : {}
380
+ }
381
+ ],
382
+ ...createSlackDispatcherEventProcessorInput(config)
383
+ }).fetch,
384
+ port
385
+ });
386
+ return {
387
+ url: `http://localhost:${port}`,
388
+ server,
389
+ close() {
390
+ return new Promise((resolve, reject) => {
391
+ server.close((error) => {
392
+ if (error) {
393
+ reject(error);
394
+ return;
395
+ }
396
+ resolve();
397
+ });
398
+ });
399
+ }
400
+ };
401
+ }
402
+
403
+ // src/render.ts
404
+ import { suggestedActionCandidatesFromResult } from "@opentag/core";
405
+ function escapeSlackText(text2) {
406
+ return text2.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
407
+ }
408
+ function markdownToSlackMrkdwn(text2) {
409
+ const links = [];
410
+ const withoutLinks = text2.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, label, url) => {
411
+ const token = `\0SLACK_LINK_${links.length}\0`;
412
+ links.push(`<${url}|${escapeSlackText(label)}>`);
413
+ return token;
414
+ });
415
+ const converted = escapeSlackText(withoutLinks).replace(/\*\*(.+?)\*\*/g, "*$1*").replace(/__(.+?)__/g, "*$1*");
416
+ return links.reduce((output, link, index) => output.replace(`\0SLACK_LINK_${index}\0`, link), converted);
417
+ }
418
+ function renderSlackAcknowledgement(runId) {
419
+ return `I picked this up: \`${runId}\``;
420
+ }
421
+ function nextActionSummary(result) {
422
+ if (!result.nextAction) return void 0;
423
+ if (typeof result.nextAction === "string") return result.nextAction;
424
+ return result.nextAction.summary;
425
+ }
426
+ function stringParam(params, key) {
427
+ const value = params?.[key];
428
+ return typeof value === "string" && value.length > 0 ? value : void 0;
429
+ }
430
+ function stringArrayParam(params, key) {
431
+ const value = params?.[key];
432
+ if (!Array.isArray(value)) return [];
433
+ return value.filter((item) => typeof item === "string" && item.length > 0);
434
+ }
435
+ function renderVerificationParams(params) {
436
+ const value = params?.["verification"];
437
+ if (!Array.isArray(value)) return [];
438
+ return value.map((item) => {
439
+ if (!item || typeof item !== "object" || Array.isArray(item)) return void 0;
440
+ const command = item["command"];
441
+ const outcome = item["outcome"];
442
+ return typeof command === "string" && typeof outcome === "string" ? ` - \`${command}\`: ${outcome}` : void 0;
443
+ }).filter((line) => Boolean(line));
444
+ }
445
+ function renderSuggestedActionDetails(params, action) {
446
+ if (action !== "create_pull_request") return [];
447
+ const lines = [];
448
+ const title = stringParam(params, "title");
449
+ const head = stringParam(params, "head") ?? stringParam(params, "branch");
450
+ const base = stringParam(params, "base") ?? stringParam(params, "baseBranch");
451
+ const changedFiles = stringArrayParam(params, "changedFiles");
452
+ const risks = stringArrayParam(params, "risks");
453
+ const verification = renderVerificationParams(params);
454
+ if (title) lines.push(` Title: ${markdownToSlackMrkdwn(title)}`);
455
+ if (head || base) lines.push(` Branch: \`${head ?? "unknown"}\` -> \`${base ?? "main"}\``);
456
+ if (changedFiles.length > 0) lines.push(` Changed files: ${changedFiles.map((file) => `\`${file}\``).join(", ")}`);
457
+ if (risks.length > 0) {
458
+ lines.push(" Risks:");
459
+ for (const risk of risks) {
460
+ lines.push(` - ${markdownToSlackMrkdwn(risk)}`);
461
+ }
462
+ }
463
+ if (verification.length > 0) {
464
+ lines.push(" Verification:");
465
+ lines.push(...verification);
466
+ }
467
+ return lines;
468
+ }
469
+ function renderSuggestedActionsMarkdown(result) {
470
+ const candidates = suggestedActionCandidatesFromResult(result);
471
+ if (candidates.length === 0) return [];
472
+ const lines = ["*Suggested actions*"];
473
+ for (const candidate of candidates) {
474
+ lines.push(
475
+ "",
476
+ `${candidate.index}. *${markdownToSlackMrkdwn(candidate.intent.summary)}*`,
477
+ ` Intent: \`${candidate.intent.action}\` (\`${candidate.intent.domain}\`)`,
478
+ ` Proposal: \`${candidate.proposalId}\``,
479
+ ` Intent ID: \`${candidate.intent.intentId}\``
480
+ );
481
+ lines.push(...renderSuggestedActionDetails(candidate.intent.params, candidate.intent.action));
482
+ if (candidate.proposalPreconditions?.length) {
483
+ lines.push(" Preconditions:");
484
+ for (const precondition of candidate.proposalPreconditions) {
485
+ lines.push(` - ${markdownToSlackMrkdwn(precondition)}`);
486
+ }
487
+ }
488
+ }
489
+ lines.push(
490
+ "",
491
+ "Reply with:",
492
+ "- `approve 1` to record approval",
493
+ "- `apply 1` or `apply all` to apply supported actions",
494
+ "- `continue 1` to continue with a follow-up run",
495
+ "- `reject 1` to reject an action"
496
+ );
497
+ return lines;
498
+ }
499
+ function renderSlackFinalResult(result) {
500
+ const lines = [`Finished with *${result.conclusion}*.`, "", markdownToSlackMrkdwn(result.summary)];
501
+ if (result.verification?.length) {
502
+ lines.push("", "*Verification*");
503
+ for (const check of result.verification) {
504
+ lines.push(`- \`${check.command}\`: ${check.outcome}`);
505
+ }
506
+ }
507
+ const nextAction = nextActionSummary(result);
508
+ if (nextAction) {
509
+ lines.push("", `*Next action*: ${markdownToSlackMrkdwn(nextAction)}`);
510
+ }
511
+ const suggestedActions = renderSuggestedActionsMarkdown(result);
512
+ if (suggestedActions.length > 0) {
513
+ lines.push("", ...suggestedActions);
514
+ }
515
+ return lines.join("\n");
516
+ }
517
+ function createSlackFinalResultBlocks(result) {
518
+ const blocks = [
519
+ {
520
+ type: "section",
521
+ text: {
522
+ type: "mrkdwn",
523
+ text: `*Finished with ${result.conclusion}.*
524
+ ${markdownToSlackMrkdwn(result.summary)}`
525
+ }
526
+ }
527
+ ];
528
+ if (result.verification?.length) {
529
+ blocks.push({ type: "divider" });
530
+ blocks.push({
531
+ type: "section",
532
+ text: {
533
+ type: "mrkdwn",
534
+ text: markdownToSlackMrkdwn(["*Verification*", ...result.verification.map((check) => `- \`${check.command}\`: ${check.outcome}`)].join("\n"))
535
+ }
536
+ });
537
+ }
538
+ const nextAction = nextActionSummary(result);
539
+ if (nextAction) {
540
+ blocks.push({
541
+ type: "section",
542
+ text: {
543
+ type: "mrkdwn",
544
+ text: `*Next action*: ${markdownToSlackMrkdwn(nextAction)}`
545
+ }
546
+ });
547
+ }
548
+ const suggestedActions = renderSuggestedActionsMarkdown(result);
549
+ if (suggestedActions.length > 0) {
550
+ blocks.push({ type: "divider" });
551
+ blocks.push({
552
+ type: "section",
553
+ text: {
554
+ type: "mrkdwn",
555
+ text: suggestedActions.join("\n")
556
+ }
557
+ });
558
+ }
559
+ return blocks;
560
+ }
561
+ function createSlackPostMessagePayload(input) {
562
+ return {
563
+ channel: input.channelId,
564
+ text: markdownToSlackMrkdwn(input.text),
565
+ thread_ts: input.threadTs,
566
+ ...input.blocks?.length ? { blocks: input.blocks } : {}
567
+ };
568
+ }
569
+ function createSlackUpdateMessagePayload(input) {
570
+ return {
571
+ channel: input.channelId,
572
+ text: markdownToSlackMrkdwn(input.text),
573
+ ts: input.messageTs,
574
+ ...input.blocks?.length ? { blocks: input.blocks } : {}
575
+ };
576
+ }
577
+
578
+ // src/socket-mode.ts
579
+ import WebSocket from "ws";
580
+ var SLACK_CONNECTIONS_OPEN_URL = "https://slack.com/api/apps.connections.open";
581
+ var DEFAULT_RECONNECT_DELAY_MS = 1e3;
582
+ function rawDataToString(data) {
583
+ if (typeof data === "string") return data;
584
+ if (Buffer.isBuffer(data)) return data.toString("utf8");
585
+ if (Array.isArray(data)) return Buffer.concat(data).toString("utf8");
586
+ if (data instanceof ArrayBuffer) return Buffer.from(data).toString("utf8");
587
+ return Buffer.from(data).toString("utf8");
588
+ }
589
+ async function openSlackSocketUrl(input) {
590
+ const response = await input.fetchImpl(SLACK_CONNECTIONS_OPEN_URL, {
591
+ method: "POST",
592
+ headers: {
593
+ authorization: `Bearer ${input.appToken}`
594
+ }
595
+ });
596
+ const body = await response.json().catch(() => ({}));
597
+ if (!response.ok || !body.ok || !body.url) {
598
+ const reason = body.error ?? `http_${response.status}`;
599
+ throw new Error(`Slack Socket Mode connection failed: ${reason}`);
600
+ }
601
+ return body.url;
602
+ }
603
+ function parseSocketEnvelope(data) {
604
+ try {
605
+ return JSON.parse(rawDataToString(data));
606
+ } catch {
607
+ return null;
608
+ }
609
+ }
610
+ async function handleSocketMessage(input) {
611
+ const envelope = parseSocketEnvelope(input.data);
612
+ if (!envelope?.envelope_id) {
613
+ input.logError("[slack] ignored Socket Mode envelope without envelope_id");
614
+ return;
615
+ }
616
+ input.socket.send(JSON.stringify({ envelope_id: envelope.envelope_id }));
617
+ if (envelope.type !== "events_api" || !envelope.payload) {
618
+ return;
619
+ }
620
+ if (input.slackApp.appId && envelope.payload.api_app_id && envelope.payload.api_app_id !== input.slackApp.appId) {
621
+ return;
622
+ }
623
+ await input.processor.process(envelope.payload, input.slackApp);
624
+ }
625
+ function wait(ms) {
626
+ return new Promise((resolve) => setTimeout(resolve, ms));
627
+ }
628
+ function startSlackSocketModeApp(input, dependencies = {}) {
629
+ const fetchImpl = dependencies.fetchImpl ?? fetch;
630
+ const createWebSocket = dependencies.createWebSocket ?? ((url) => new WebSocket(url));
631
+ const reconnectDelayMs = dependencies.reconnectDelayMs ?? DEFAULT_RECONNECT_DELAY_MS;
632
+ const log = dependencies.log ?? ((message) => console.log(message));
633
+ const logError = dependencies.logError ?? ((message, error) => error ? console.error(message, error) : console.error(message));
634
+ const processor = createSlackEventProcessor(input);
635
+ let closed = false;
636
+ let activeSocket;
637
+ async function runOneConnection(socketUrl) {
638
+ await new Promise((resolve) => {
639
+ const socket = createWebSocket(socketUrl);
640
+ activeSocket = socket;
641
+ let settled = false;
642
+ const finish = () => {
643
+ if (settled) return;
644
+ settled = true;
645
+ if (activeSocket === socket) activeSocket = void 0;
646
+ resolve();
647
+ };
648
+ socket.once("open", () => {
649
+ log("[slack] Socket Mode connected");
650
+ });
651
+ socket.on("message", (data) => {
652
+ void handleSocketMessage({
653
+ data,
654
+ socket,
655
+ processor,
656
+ slackApp: input.slackApp,
657
+ logError
658
+ }).catch((error) => {
659
+ logError("[slack] failed to handle Socket Mode event:", error);
660
+ });
661
+ });
662
+ socket.once("close", finish);
663
+ socket.once("error", (error) => {
664
+ if (!closed) {
665
+ logError("[slack] Socket Mode connection error:", error);
666
+ }
667
+ socket.close();
668
+ finish();
669
+ });
670
+ });
671
+ }
672
+ const startPromise = (async () => {
673
+ while (!closed) {
674
+ const socketUrl = await openSlackSocketUrl({ appToken: input.appToken, fetchImpl });
675
+ await runOneConnection(socketUrl);
676
+ if (!closed) {
677
+ await wait(reconnectDelayMs);
678
+ }
679
+ }
680
+ })();
681
+ return {
682
+ startPromise,
683
+ async close() {
684
+ closed = true;
685
+ activeSocket?.close();
686
+ await startPromise.catch(() => void 0);
687
+ }
688
+ };
689
+ }
690
+ function startSlackSocketModeIngress(config, dependencies = {}) {
691
+ return startSlackSocketModeApp(
692
+ {
693
+ appToken: config.appToken,
694
+ slackApp: {
695
+ agentId: config.agentId ?? "opentag",
696
+ ...config.appId ? { appId: config.appId } : {},
697
+ ...config.callbackUri ? { callbackUri: config.callbackUri } : {}
698
+ },
699
+ ...createSlackDispatcherEventProcessorInput(config)
700
+ },
701
+ dependencies
702
+ );
703
+ }
107
704
  export {
705
+ computeSlackSignature,
706
+ createSlackEventProcessor,
707
+ createSlackEventsApp,
708
+ createSlackFinalResultBlocks,
709
+ createSlackPostMessagePayload,
710
+ createSlackUpdateMessagePayload,
108
711
  encodeSlackThreadKey,
712
+ markdownToSlackMrkdwn,
109
713
  normalizeSlackAppMention,
110
714
  parseSlackThreadKey,
111
- stripSlackAppMention
715
+ renderSlackAcknowledgement,
716
+ renderSlackFinalResult,
717
+ startSlackIngress,
718
+ startSlackSocketModeApp,
719
+ startSlackSocketModeIngress,
720
+ stripSlackAppMention,
721
+ verifySlackSignature,
722
+ verifySlackTimestamp
112
723
  };
113
724
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/normalize.ts"],"sourcesContent":["import { commandFromRawText, type OpenTagEvent, type PermissionGrant } from \"@opentag/core\";\n\nexport type SlackChannelBinding = {\n teamId: string;\n channelId: string;\n owner: string;\n repo: string;\n};\n\nexport type SlackAppMentionInput = {\n teamId: string;\n channelId: string;\n userId: string;\n text: string;\n ts: string;\n threadTs?: string;\n eventId: string;\n eventTime: number;\n botUserId?: string;\n callbackUri?: string;\n binding: SlackChannelBinding;\n};\n\nexport function stripSlackAppMention(text: string, botUserId?: string): string | null {\n const patterns = botUserId\n ? [new RegExp(`^<@${botUserId}>\\\\s*`, \"i\"), /^<@[^>]+>\\s*/]\n : [/^<@[^>]+>\\s*/];\n\n for (const pattern of patterns) {\n const stripped = text.replace(pattern, \"\").trim();\n if (stripped !== text.trim()) {\n return stripped.length > 0 ? stripped : null;\n }\n }\n\n return null;\n}\n\nexport function encodeSlackThreadKey(input: { teamId: string; channelId: string; threadTs: string }): string {\n return `${input.teamId}|${input.channelId}|${input.threadTs}`;\n}\n\nexport function parseSlackThreadKey(threadKey: string): { teamId: string; channelId: string; threadTs: string } {\n const [teamId, channelId, threadTs] = threadKey.split(\"|\");\n if (!teamId || !channelId || !threadTs) {\n throw new Error(`Invalid Slack thread key: ${threadKey}`);\n }\n return { teamId, channelId, threadTs };\n}\n\nfunction permissionsForIntent(intent: ReturnType<typeof commandFromRawText>[\"intent\"]): PermissionGrant[] {\n const permissions: PermissionGrant[] = [\n {\n scope: \"chat:postMessage\",\n reason: \"reply in the originating Slack thread\"\n },\n {\n scope: \"runner:local\",\n reason: \"execute the run on a paired local daemon\"\n }\n ];\n\n if (intent === \"fix\" || intent === \"run\") {\n permissions.push(\n {\n scope: \"repo:read\",\n reason: \"inspect the repository in the paired local checkout\"\n },\n {\n scope: \"repo:write\",\n reason: \"commit code changes on an isolated run branch\"\n },\n {\n scope: \"pr:create\",\n reason: \"open a pull request for completed code changes\"\n }\n );\n }\n\n return permissions;\n}\n\nexport function normalizeSlackAppMention(input: SlackAppMentionInput): OpenTagEvent | null {\n const rawText = stripSlackAppMention(input.text, input.botUserId);\n if (!rawText) return null;\n\n const command = commandFromRawText(rawText);\n const replyThreadTs = input.threadTs ?? input.ts;\n\n return {\n id: `evt_slack_app_mention_${input.eventId}`,\n source: \"slack\",\n sourceEventId: input.eventId,\n receivedAt: new Date(input.eventTime * 1000).toISOString(),\n actor: {\n provider: \"slack\",\n providerUserId: input.userId,\n handle: input.userId,\n organizationId: input.teamId\n },\n target: {\n mention: input.botUserId ? `<@${input.botUserId}>` : \"<@app>\",\n agentId: \"opentag\"\n },\n command,\n context: [\n {\n kind: \"url\",\n uri: `slack://team/${input.teamId}/channel/${input.channelId}/message/${input.ts}`,\n visibility: \"organization\",\n title: \"Slack message\"\n },\n {\n kind: \"text\",\n uri: input.text,\n visibility: \"organization\",\n title: \"Slack message text\"\n }\n ],\n permissions: permissionsForIntent(command.intent),\n callback: {\n provider: \"slack\",\n uri: input.callbackUri ?? \"https://slack.com/api/chat.postMessage\",\n threadKey: encodeSlackThreadKey({\n teamId: input.teamId,\n channelId: input.channelId,\n threadTs: replyThreadTs\n })\n },\n metadata: {\n teamId: input.teamId,\n channelId: input.channelId,\n messageTs: input.ts,\n repoProvider: \"github\",\n owner: input.binding.owner,\n repo: input.binding.repo\n }\n };\n}\n"],"mappings":";AAAA,SAAS,0BAAmE;AAuBrE,SAAS,qBAAqB,MAAc,WAAmC;AACpF,QAAM,WAAW,YACb,CAAC,IAAI,OAAO,MAAM,SAAS,SAAS,GAAG,GAAG,cAAc,IACxD,CAAC,cAAc;AAEnB,aAAW,WAAW,UAAU;AAC9B,UAAM,WAAW,KAAK,QAAQ,SAAS,EAAE,EAAE,KAAK;AAChD,QAAI,aAAa,KAAK,KAAK,GAAG;AAC5B,aAAO,SAAS,SAAS,IAAI,WAAW;AAAA,IAC1C;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,qBAAqB,OAAwE;AAC3G,SAAO,GAAG,MAAM,MAAM,IAAI,MAAM,SAAS,IAAI,MAAM,QAAQ;AAC7D;AAEO,SAAS,oBAAoB,WAA4E;AAC9G,QAAM,CAAC,QAAQ,WAAW,QAAQ,IAAI,UAAU,MAAM,GAAG;AACzD,MAAI,CAAC,UAAU,CAAC,aAAa,CAAC,UAAU;AACtC,UAAM,IAAI,MAAM,6BAA6B,SAAS,EAAE;AAAA,EAC1D;AACA,SAAO,EAAE,QAAQ,WAAW,SAAS;AACvC;AAEA,SAAS,qBAAqB,QAA4E;AACxG,QAAM,cAAiC;AAAA,IACrC;AAAA,MACE,OAAO;AAAA,MACP,QAAQ;AAAA,IACV;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,MAAI,WAAW,SAAS,WAAW,OAAO;AACxC,gBAAY;AAAA,MACV;AAAA,QACE,OAAO;AAAA,QACP,QAAQ;AAAA,MACV;AAAA,MACA;AAAA,QACE,OAAO;AAAA,QACP,QAAQ;AAAA,MACV;AAAA,MACA;AAAA,QACE,OAAO;AAAA,QACP,QAAQ;AAAA,MACV;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,yBAAyB,OAAkD;AACzF,QAAM,UAAU,qBAAqB,MAAM,MAAM,MAAM,SAAS;AAChE,MAAI,CAAC,QAAS,QAAO;AAErB,QAAM,UAAU,mBAAmB,OAAO;AAC1C,QAAM,gBAAgB,MAAM,YAAY,MAAM;AAE9C,SAAO;AAAA,IACL,IAAI,yBAAyB,MAAM,OAAO;AAAA,IAC1C,QAAQ;AAAA,IACR,eAAe,MAAM;AAAA,IACrB,YAAY,IAAI,KAAK,MAAM,YAAY,GAAI,EAAE,YAAY;AAAA,IACzD,OAAO;AAAA,MACL,UAAU;AAAA,MACV,gBAAgB,MAAM;AAAA,MACtB,QAAQ,MAAM;AAAA,MACd,gBAAgB,MAAM;AAAA,IACxB;AAAA,IACA,QAAQ;AAAA,MACN,SAAS,MAAM,YAAY,KAAK,MAAM,SAAS,MAAM;AAAA,MACrD,SAAS;AAAA,IACX;AAAA,IACA;AAAA,IACA,SAAS;AAAA,MACP;AAAA,QACE,MAAM;AAAA,QACN,KAAK,gBAAgB,MAAM,MAAM,YAAY,MAAM,SAAS,YAAY,MAAM,EAAE;AAAA,QAChF,YAAY;AAAA,QACZ,OAAO;AAAA,MACT;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,KAAK,MAAM;AAAA,QACX,YAAY;AAAA,QACZ,OAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,aAAa,qBAAqB,QAAQ,MAAM;AAAA,IAChD,UAAU;AAAA,MACR,UAAU;AAAA,MACV,KAAK,MAAM,eAAe;AAAA,MAC1B,WAAW,qBAAqB;AAAA,QAC9B,QAAQ,MAAM;AAAA,QACd,WAAW,MAAM;AAAA,QACjB,UAAU;AAAA,MACZ,CAAC;AAAA,IACH;AAAA,IACA,UAAU;AAAA,MACR,QAAQ,MAAM;AAAA,MACd,WAAW,MAAM;AAAA,MACjB,WAAW,MAAM;AAAA,MACjB,cAAc;AAAA,MACd,OAAO,MAAM,QAAQ;AAAA,MACrB,MAAM,MAAM,QAAQ;AAAA,IACtB;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/events.ts","../src/normalize.ts","../src/ingress.ts","../src/dispatcher-events.ts","../src/render.ts","../src/socket-mode.ts"],"sourcesContent":["import { parseThreadActionCommand, type OpenTagEvent } from \"@opentag/core\";\nimport { encodeSlackThreadKey, normalizeSlackAppMention, stripSlackAppMention, type SlackChannelBinding } from \"./normalize.js\";\n\nexport type SlackThreadActionInput = {\n id: string;\n rawText: string;\n actor: {\n provider: \"slack\";\n providerUserId: string;\n handle: string;\n organizationId: string;\n };\n callback: {\n provider: \"slack\";\n uri: string;\n threadKey: string;\n };\n metadata: Record<string, unknown>;\n};\n\nexport type SlackEventEnvelope = {\n token?: string;\n type: \"url_verification\" | \"event_callback\";\n challenge?: string;\n team_id?: string;\n api_app_id?: string;\n event?: {\n type: string;\n user?: string;\n text?: string;\n ts?: string;\n thread_ts?: string;\n channel?: string;\n subtype?: string;\n bot_id?: string;\n };\n event_id?: string;\n event_time?: number;\n authorizations?: Array<{ user_id?: string }>;\n};\n\nexport type SlackAppRuntimeConfig = {\n agentId: string;\n appId?: string;\n callbackUri?: string;\n};\n\nexport type SlackEventProcessorInput = {\n resolveChannelBinding(input: { teamId: string; channelId: string }): Promise<SlackChannelBinding | null>;\n createRun(event: OpenTagEvent): Promise<{ runId: string }>;\n submitThreadAction?(action: SlackThreadActionInput): Promise<unknown>;\n now(): string;\n};\n\nexport type SlackEventProcessorStatus = 200 | 400;\n\nexport type SlackEventProcessorResult =\n | {\n kind: \"json\";\n status: SlackEventProcessorStatus;\n body: Record<string, unknown>;\n }\n | {\n kind: \"text\";\n status: SlackEventProcessorStatus;\n body: string;\n };\n\nfunction json(body: Record<string, unknown>, status: SlackEventProcessorStatus = 200): SlackEventProcessorResult {\n return { kind: \"json\", status, body };\n}\n\nfunction text(body: string, status: SlackEventProcessorStatus = 200): SlackEventProcessorResult {\n return { kind: \"text\", status, body };\n}\n\nexport function createSlackEventProcessor(input: SlackEventProcessorInput) {\n return {\n async process(payload: SlackEventEnvelope, slackApp: SlackAppRuntimeConfig): Promise<SlackEventProcessorResult> {\n if (payload.type === \"url_verification\") {\n return text(payload.challenge ?? \"\");\n }\n if (payload.type !== \"event_callback\" || !payload.event || ![\"app_mention\", \"message\"].includes(payload.event.type)) {\n return json({ ok: true });\n }\n if (payload.event.type === \"message\" && (payload.event.subtype || payload.event.bot_id)) {\n return json({ ok: true });\n }\n if (!payload.team_id || !payload.event.channel || !payload.event.user || !payload.event.text || !payload.event.ts || !payload.event_id) {\n return json({ error: \"invalid_event_payload\" }, 400);\n }\n\n const rawThreadActionText =\n payload.event.type === \"app_mention\"\n ? stripSlackAppMention(payload.event.text, payload.authorizations?.[0]?.user_id)\n : payload.event.text.trim();\n if (payload.event.type === \"message\" && (!rawThreadActionText || !parseThreadActionCommand(rawThreadActionText))) {\n return json({ ok: true });\n }\n\n const binding = await input.resolveChannelBinding({\n teamId: payload.team_id,\n channelId: payload.event.channel\n });\n if (!binding) {\n return json({ ok: true, ignored: \"unbound_channel\" });\n }\n\n if (rawThreadActionText && parseThreadActionCommand(rawThreadActionText) && input.submitThreadAction) {\n await input.submitThreadAction({\n id: `approval_slack_${payload.event_id}`,\n rawText: rawThreadActionText,\n actor: {\n provider: \"slack\",\n providerUserId: payload.event.user,\n handle: payload.event.user,\n organizationId: payload.team_id\n },\n callback: {\n provider: \"slack\",\n uri: slackApp.callbackUri ?? \"https://slack.com/api/chat.postMessage\",\n threadKey: encodeSlackThreadKey({\n teamId: payload.team_id,\n channelId: payload.event.channel,\n threadTs: payload.event.thread_ts ?? payload.event.ts\n })\n },\n metadata: {\n teamId: payload.team_id,\n channelId: payload.event.channel,\n messageTs: payload.event.ts,\n ...(payload.api_app_id ? { slackAppId: payload.api_app_id } : {}),\n ...(payload.authorizations?.[0]?.user_id ? { slackBotUserId: payload.authorizations[0].user_id } : {}),\n repoProvider: binding.repoProvider ?? \"github\",\n owner: binding.owner,\n repo: binding.repo\n }\n });\n return json({ ok: true });\n }\n\n if (payload.event.type !== \"app_mention\") {\n return json({ ok: true });\n }\n\n const event = normalizeSlackAppMention({\n teamId: payload.team_id,\n channelId: payload.event.channel,\n userId: payload.event.user,\n text: payload.event.text,\n ts: payload.event.ts,\n eventId: payload.event_id,\n eventTime: payload.event_time ?? Math.floor(Date.parse(input.now()) / 1000),\n agentId: slackApp.agentId,\n binding,\n ...(payload.api_app_id ? { appId: payload.api_app_id } : {}),\n ...(payload.event.thread_ts ? { threadTs: payload.event.thread_ts } : {}),\n ...(payload.authorizations?.[0]?.user_id ? { botUserId: payload.authorizations[0].user_id } : {}),\n ...(slackApp.callbackUri ? { callbackUri: slackApp.callbackUri } : {})\n });\n if (!event) {\n return json({ ok: true, ignored: \"empty_command\" });\n }\n\n await input.createRun(event);\n return json({ ok: true });\n }\n };\n}\n","import { commandFromRawText, type ContextPointer, type OpenTagCommand, type OpenTagEvent, type PermissionGrant } from \"@opentag/core\";\n\nexport type SlackChannelBinding = {\n teamId: string;\n channelId: string;\n repoProvider?: string;\n owner: string;\n repo: string;\n};\n\nexport type SlackAppMentionInput = {\n teamId: string;\n channelId: string;\n userId: string;\n text: string;\n ts: string;\n threadTs?: string;\n eventId: string;\n eventTime: number;\n appId?: string;\n agentId?: string;\n botUserId?: string;\n callbackUri?: string;\n binding: SlackChannelBinding;\n};\n\nexport function stripSlackAppMention(text: string, botUserId?: string): string | null {\n const patterns = botUserId\n ? [new RegExp(`^<@${botUserId}>\\\\s*`, \"i\"), /^<@[^>]+>\\s*/]\n : [/^<@[^>]+>\\s*/];\n\n for (const pattern of patterns) {\n const stripped = text.replace(pattern, \"\").trim();\n if (stripped !== text.trim()) {\n return stripped.length > 0 ? stripped : null;\n }\n }\n\n return null;\n}\n\nexport function encodeSlackThreadKey(input: { teamId: string; channelId: string; threadTs: string }): string {\n return `${input.teamId}|${input.channelId}|${input.threadTs}`;\n}\n\nexport function parseSlackThreadKey(threadKey: string): { teamId: string; channelId: string; threadTs: string } {\n const [teamId, channelId, threadTs] = threadKey.split(\"|\");\n if (!teamId || !channelId || !threadTs) {\n throw new Error(`Invalid Slack thread key: ${threadKey}`);\n }\n return { teamId, channelId, threadTs };\n}\n\nfunction permissionsForIntent(intent: OpenTagCommand[\"intent\"]): PermissionGrant[] {\n const permissions: PermissionGrant[] = [\n {\n scope: \"chat:postMessage\",\n reason: \"reply in the originating Slack thread\"\n },\n {\n scope: \"runner:local\",\n reason: \"execute the run on a paired local daemon\"\n }\n ];\n\n if (intent === \"fix\" || intent === \"run\") {\n permissions.push(\n {\n scope: \"repo:read\",\n reason: \"inspect the repository in the paired local checkout\"\n },\n {\n scope: \"repo:write\",\n reason: \"commit code changes on an isolated run branch\"\n },\n {\n scope: \"pr:create\",\n reason: \"open a pull request for completed code changes\"\n }\n );\n }\n\n return permissions;\n}\n\nfunction contextPointersForCommand(command: OpenTagCommand): ContextPointer[] {\n const context: ContextPointer[] = [];\n\n for (const reference of command.parsed?.references ?? []) {\n if (reference.kind === \"url\") {\n context.push({\n kind: \"url\",\n uri: reference.uri,\n visibility: \"organization\",\n title: reference.title ?? \"Command URL reference\"\n });\n continue;\n }\n\n if (reference.kind === \"file\" || reference.kind === \"path\") {\n context.push({\n kind: \"file\",\n uri: reference.uri,\n ...(reference.line ? { line: reference.line } : {}),\n ...(reference.startLine ? { startLine: reference.startLine } : {}),\n ...(reference.endLine ? { endLine: reference.endLine } : {}),\n visibility: \"organization\",\n title: referenceTitle(reference)\n });\n }\n }\n\n return context;\n}\n\nfunction referenceTitle(reference: NonNullable<OpenTagCommand[\"parsed\"]>[\"references\"][number]): string {\n return reference.title ?? \"Command file reference\";\n}\n\nfunction commandMetadata(command: OpenTagCommand): Record<string, unknown> {\n if (!command.parsed) return {};\n return {\n commandParser: command.parsed.version,\n commandDiagnostics: command.parsed.diagnostics,\n ...(command.parsed.approval ? { approval: command.parsed.approval } : {}),\n ...(command.parsed.network ? { network: command.parsed.network } : {})\n };\n}\n\nexport function normalizeSlackAppMention(input: SlackAppMentionInput): OpenTagEvent | null {\n const rawText = stripSlackAppMention(input.text, input.botUserId);\n if (!rawText) return null;\n\n const command = commandFromRawText(rawText);\n const replyThreadTs = input.threadTs ?? input.ts;\n const agentId = input.agentId ?? \"opentag\";\n\n return {\n id: `evt_slack_app_mention_${input.eventId}`,\n source: \"slack\",\n sourceEventId: input.eventId,\n receivedAt: new Date(input.eventTime * 1000).toISOString(),\n actor: {\n provider: \"slack\",\n providerUserId: input.userId,\n handle: input.userId,\n organizationId: input.teamId\n },\n target: {\n mention: input.botUserId ? `<@${input.botUserId}>` : \"<@app>\",\n agentId,\n ...(command.parsed?.executorHint ? { executorHint: command.parsed.executorHint } : {})\n },\n command,\n context: [\n {\n provider: \"slack\",\n kind: \"message\",\n uri: `slack://team/${input.teamId}/channel/${input.channelId}/message/${input.ts}`,\n visibility: \"organization\",\n title: \"Slack message\"\n },\n {\n kind: \"text\",\n uri: input.text,\n visibility: \"organization\",\n title: \"Slack message text\"\n },\n ...contextPointersForCommand(command)\n ],\n permissions: permissionsForIntent(command.intent),\n callback: {\n provider: \"slack\",\n uri: input.callbackUri ?? \"https://slack.com/api/chat.postMessage\",\n threadKey: encodeSlackThreadKey({\n teamId: input.teamId,\n channelId: input.channelId,\n threadTs: replyThreadTs\n })\n },\n metadata: {\n teamId: input.teamId,\n channelId: input.channelId,\n messageTs: input.ts,\n ...(input.appId ? { slackAppId: input.appId } : {}),\n ...(input.botUserId ? { slackBotUserId: input.botUserId } : {}),\n ...commandMetadata(command),\n repoProvider: input.binding.repoProvider ?? \"github\",\n owner: input.binding.owner,\n repo: input.binding.repo\n }\n };\n}\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\nimport { serve } from \"@hono/node-server\";\nimport { Hono } from \"hono\";\nimport { createSlackDispatcherEventProcessorInput } from \"./dispatcher-events.js\";\nimport { createSlackEventProcessor, type SlackAppRuntimeConfig, type SlackEventEnvelope, type SlackEventProcessorInput } from \"./events.js\";\n\nexport type SlackEventsAppInput = {\n slackApps: Array<\n SlackAppRuntimeConfig & {\n signingSecret: string;\n }\n >;\n clock?: () => number;\n} & SlackEventProcessorInput;\n\nexport type SlackEventsApiIngressConfig = {\n signingSecret: string;\n dispatcherUrl: string;\n dispatcherToken?: string;\n port?: number;\n agentId?: string;\n appId?: string;\n callbackUri?: string;\n};\n\nexport type SlackIngressConfig = SlackEventsApiIngressConfig;\n\nexport type SlackIngressHandle = {\n url: string;\n server: ReturnType<typeof serve>;\n close(): Promise<void>;\n};\n\nexport function computeSlackSignature(input: {\n signingSecret: string;\n timestamp: string;\n rawBody: string;\n}): string {\n const base = `v0:${input.timestamp}:${input.rawBody}`;\n const digest = createHmac(\"sha256\", input.signingSecret).update(base).digest(\"hex\");\n return `v0=${digest}`;\n}\n\nexport function verifySlackSignature(input: {\n signingSecret: string;\n timestamp: string;\n rawBody: string;\n signature: string;\n}): boolean {\n const expected = computeSlackSignature(input);\n const expectedBuffer = Buffer.from(expected);\n const actualBuffer = Buffer.from(input.signature);\n return expectedBuffer.length === actualBuffer.length && timingSafeEqual(expectedBuffer, actualBuffer);\n}\n\nexport function verifySlackTimestamp(input: { timestamp: string; nowMs: number; toleranceSeconds?: number }): boolean {\n const timestampSeconds = Number(input.timestamp);\n if (!Number.isFinite(timestampSeconds)) return false;\n const toleranceSeconds = input.toleranceSeconds ?? 300;\n const ageSeconds = Math.abs(Math.floor(input.nowMs / 1000) - timestampSeconds);\n return ageSeconds <= toleranceSeconds;\n}\n\nexport function createSlackEventsApp(input: SlackEventsAppInput) {\n const app = new Hono();\n const processor = createSlackEventProcessor(input);\n\n function parseSlackPayload(rawBody: string): SlackEventEnvelope | null {\n try {\n return JSON.parse(rawBody) as SlackEventEnvelope;\n } catch {\n return null;\n }\n }\n\n function resolveSlackApp(inputValue: {\n apiAppId?: string;\n rawBody: string;\n signature: string;\n timestamp: string;\n }) {\n const candidates = inputValue.apiAppId\n ? input.slackApps.filter((candidate) => !candidate.appId || candidate.appId === inputValue.apiAppId)\n : input.slackApps;\n if (candidates.length === 0) {\n return { error: \"unknown_slack_app\" as const };\n }\n const slackApp = candidates.find((candidate) =>\n verifySlackSignature({\n signingSecret: candidate.signingSecret,\n timestamp: inputValue.timestamp,\n rawBody: inputValue.rawBody,\n signature: inputValue.signature\n })\n );\n return slackApp ? { slackApp } : { error: \"invalid_signature\" as const };\n }\n\n app.post(\"/slack/events\", async (c) => {\n const timestamp = c.req.header(\"x-slack-request-timestamp\");\n const signature = c.req.header(\"x-slack-signature\");\n if (!timestamp || !signature) {\n return c.json({ error: \"missing_signature_headers\" }, 401);\n }\n if (!verifySlackTimestamp({ timestamp, nowMs: input.clock?.() ?? Date.now() })) {\n return c.json({ error: \"stale_signature_timestamp\" }, 401);\n }\n const rawBody = await c.req.text();\n const payload = parseSlackPayload(rawBody);\n if (!payload) {\n return c.json({ error: \"invalid_json\" }, 400);\n }\n const resolvedSlackApp = resolveSlackApp({\n rawBody,\n signature,\n timestamp,\n ...(payload.api_app_id ? { apiAppId: payload.api_app_id } : {})\n });\n if (\"error\" in resolvedSlackApp) {\n return c.json({ error: resolvedSlackApp.error }, 401);\n }\n const result = await processor.process(payload, resolvedSlackApp.slackApp);\n if (result.kind === \"text\") {\n return c.text(result.body, result.status);\n }\n return c.json(result.body, result.status);\n });\n\n return app;\n}\n\nexport function startSlackIngress(config: SlackEventsApiIngressConfig): SlackIngressHandle {\n const port = config.port ?? 3040;\n const server = serve({\n fetch: createSlackEventsApp({\n slackApps: [\n {\n signingSecret: config.signingSecret,\n agentId: config.agentId ?? \"opentag\",\n ...(config.appId ? { appId: config.appId } : {}),\n ...(config.callbackUri ? { callbackUri: config.callbackUri } : {})\n }\n ],\n ...createSlackDispatcherEventProcessorInput(config)\n }).fetch,\n port\n });\n\n return {\n url: `http://localhost:${port}`,\n server,\n close() {\n return new Promise((resolve, reject) => {\n server.close((error?: Error) => {\n if (error) {\n reject(error);\n return;\n }\n resolve();\n });\n });\n }\n };\n}\n","import { randomUUID } from \"node:crypto\";\nimport { createOpenTagClient } from \"@opentag/client\";\nimport type { SlackEventProcessorInput } from \"./events.js\";\n\nexport type SlackDispatcherEventConfig = {\n dispatcherUrl: string;\n dispatcherToken?: string;\n};\n\nexport function createSlackDispatcherEventProcessorInput(config: SlackDispatcherEventConfig): SlackEventProcessorInput {\n const dispatcherClient = createOpenTagClient({\n dispatcherUrl: config.dispatcherUrl,\n ...(config.dispatcherToken ? { pairingToken: config.dispatcherToken } : {})\n });\n\n return {\n async resolveChannelBinding(input) {\n try {\n const { binding } = await dispatcherClient.getChannelBinding({\n provider: \"slack\",\n accountId: input.teamId,\n conversationId: input.channelId\n });\n return {\n teamId: binding.accountId,\n channelId: binding.conversationId,\n repoProvider: binding.repoProvider,\n owner: binding.owner,\n repo: binding.repo\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"channel_binding_not_found\")) {\n return null;\n }\n throw error;\n }\n },\n async createRun(event) {\n const runId = `run_${randomUUID()}`;\n const created = await dispatcherClient.createRun({ runId, event });\n return created.outcome === \"run_created\" ? { runId: created.run.id } : { runId };\n },\n async submitThreadAction(action) {\n await dispatcherClient.submitThreadAction(action);\n },\n now: () => new Date().toISOString()\n };\n}\n","import { suggestedActionCandidatesFromResult, type OpenTagRunResult } from \"@opentag/core\";\n\nexport type SlackTextBlock = {\n type: \"section\";\n text: {\n type: \"mrkdwn\";\n text: string;\n };\n};\n\nexport type SlackDividerBlock = {\n type: \"divider\";\n};\n\nexport type SlackBlock = SlackTextBlock | SlackDividerBlock;\n\nexport type SlackMessagePayload = {\n channel: string;\n text: string;\n thread_ts?: string;\n ts?: string;\n blocks?: SlackBlock[];\n};\n\nfunction escapeSlackText(text: string): string {\n return text.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\");\n}\n\nexport function markdownToSlackMrkdwn(text: string): string {\n const links: string[] = [];\n const withoutLinks = text.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, (_match, label: string, url: string) => {\n const token = `\\u0000SLACK_LINK_${links.length}\\u0000`;\n links.push(`<${url}|${escapeSlackText(label)}>`);\n return token;\n });\n const converted = escapeSlackText(withoutLinks)\n .replace(/\\*\\*(.+?)\\*\\*/g, \"*$1*\")\n .replace(/__(.+?)__/g, \"*$1*\");\n return links.reduce((output, link, index) => output.replace(`\\u0000SLACK_LINK_${index}\\u0000`, link), converted);\n}\n\nexport function renderSlackAcknowledgement(runId: string): string {\n return `I picked this up: \\`${runId}\\``;\n}\n\nfunction nextActionSummary(result: OpenTagRunResult): string | undefined {\n if (!result.nextAction) return undefined;\n if (typeof result.nextAction === \"string\") return result.nextAction;\n return result.nextAction.summary;\n}\n\nfunction stringParam(params: Record<string, unknown> | undefined, key: string): string | undefined {\n const value = params?.[key];\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\nfunction stringArrayParam(params: Record<string, unknown> | undefined, key: string): string[] {\n const value = params?.[key];\n if (!Array.isArray(value)) return [];\n return value.filter((item): item is string => typeof item === \"string\" && item.length > 0);\n}\n\nfunction renderVerificationParams(params: Record<string, unknown> | undefined): string[] {\n const value = params?.[\"verification\"];\n if (!Array.isArray(value)) return [];\n return value\n .map((item) => {\n if (!item || typeof item !== \"object\" || Array.isArray(item)) return undefined;\n const command = (item as Record<string, unknown>)[\"command\"];\n const outcome = (item as Record<string, unknown>)[\"outcome\"];\n return typeof command === \"string\" && typeof outcome === \"string\" ? ` - \\`${command}\\`: ${outcome}` : undefined;\n })\n .filter((line): line is string => Boolean(line));\n}\n\nfunction renderSuggestedActionDetails(params: Record<string, unknown> | undefined, action: string): string[] {\n if (action !== \"create_pull_request\") return [];\n const lines: string[] = [];\n const title = stringParam(params, \"title\");\n const head = stringParam(params, \"head\") ?? stringParam(params, \"branch\");\n const base = stringParam(params, \"base\") ?? stringParam(params, \"baseBranch\");\n const changedFiles = stringArrayParam(params, \"changedFiles\");\n const risks = stringArrayParam(params, \"risks\");\n const verification = renderVerificationParams(params);\n if (title) lines.push(` Title: ${markdownToSlackMrkdwn(title)}`);\n if (head || base) lines.push(` Branch: \\`${head ?? \"unknown\"}\\` -> \\`${base ?? \"main\"}\\``);\n if (changedFiles.length > 0) lines.push(` Changed files: ${changedFiles.map((file) => `\\`${file}\\``).join(\", \")}`);\n if (risks.length > 0) {\n lines.push(\" Risks:\");\n for (const risk of risks) {\n lines.push(` - ${markdownToSlackMrkdwn(risk)}`);\n }\n }\n if (verification.length > 0) {\n lines.push(\" Verification:\");\n lines.push(...verification);\n }\n return lines;\n}\n\nfunction renderSuggestedActionsMarkdown(result: OpenTagRunResult): string[] {\n const candidates = suggestedActionCandidatesFromResult(result);\n if (candidates.length === 0) return [];\n\n const lines = [\"*Suggested actions*\"];\n for (const candidate of candidates) {\n lines.push(\n \"\",\n `${candidate.index}. *${markdownToSlackMrkdwn(candidate.intent.summary)}*`,\n ` Intent: \\`${candidate.intent.action}\\` (\\`${candidate.intent.domain}\\`)`,\n ` Proposal: \\`${candidate.proposalId}\\``,\n ` Intent ID: \\`${candidate.intent.intentId}\\``\n );\n lines.push(...renderSuggestedActionDetails(candidate.intent.params, candidate.intent.action));\n if (candidate.proposalPreconditions?.length) {\n lines.push(\" Preconditions:\");\n for (const precondition of candidate.proposalPreconditions) {\n lines.push(` - ${markdownToSlackMrkdwn(precondition)}`);\n }\n }\n }\n\n lines.push(\n \"\",\n \"Reply with:\",\n \"- `approve 1` to record approval\",\n \"- `apply 1` or `apply all` to apply supported actions\",\n \"- `continue 1` to continue with a follow-up run\",\n \"- `reject 1` to reject an action\"\n );\n return lines;\n}\n\nexport function renderSlackFinalResult(result: OpenTagRunResult): string {\n const lines = [`Finished with *${result.conclusion}*.`, \"\", markdownToSlackMrkdwn(result.summary)];\n\n if (result.verification?.length) {\n lines.push(\"\", \"*Verification*\");\n for (const check of result.verification) {\n lines.push(`- \\`${check.command}\\`: ${check.outcome}`);\n }\n }\n\n const nextAction = nextActionSummary(result);\n if (nextAction) {\n lines.push(\"\", `*Next action*: ${markdownToSlackMrkdwn(nextAction)}`);\n }\n\n const suggestedActions = renderSuggestedActionsMarkdown(result);\n if (suggestedActions.length > 0) {\n lines.push(\"\", ...suggestedActions);\n }\n\n return lines.join(\"\\n\");\n}\n\nexport function createSlackFinalResultBlocks(result: OpenTagRunResult): SlackBlock[] {\n const blocks: SlackBlock[] = [\n {\n type: \"section\",\n text: {\n type: \"mrkdwn\",\n text: `*Finished with ${result.conclusion}.*\\n${markdownToSlackMrkdwn(result.summary)}`\n }\n }\n ];\n\n if (result.verification?.length) {\n blocks.push({ type: \"divider\" });\n blocks.push({\n type: \"section\",\n text: {\n type: \"mrkdwn\",\n text: markdownToSlackMrkdwn([\"*Verification*\", ...result.verification.map((check) => `- \\`${check.command}\\`: ${check.outcome}`)].join(\"\\n\"))\n }\n });\n }\n\n const nextAction = nextActionSummary(result);\n if (nextAction) {\n blocks.push({\n type: \"section\",\n text: {\n type: \"mrkdwn\",\n text: `*Next action*: ${markdownToSlackMrkdwn(nextAction)}`\n }\n });\n }\n\n const suggestedActions = renderSuggestedActionsMarkdown(result);\n if (suggestedActions.length > 0) {\n blocks.push({ type: \"divider\" });\n blocks.push({\n type: \"section\",\n text: {\n type: \"mrkdwn\",\n text: suggestedActions.join(\"\\n\")\n }\n });\n }\n\n return blocks;\n}\n\nexport function createSlackPostMessagePayload(input: { channelId: string; text: string; threadTs: string; blocks?: SlackBlock[] }): SlackMessagePayload {\n return {\n channel: input.channelId,\n text: markdownToSlackMrkdwn(input.text),\n thread_ts: input.threadTs,\n ...(input.blocks?.length ? { blocks: input.blocks } : {})\n };\n}\n\nexport function createSlackUpdateMessagePayload(input: { channelId: string; text: string; messageTs: string; blocks?: SlackBlock[] }): SlackMessagePayload {\n return {\n channel: input.channelId,\n text: markdownToSlackMrkdwn(input.text),\n ts: input.messageTs,\n ...(input.blocks?.length ? { blocks: input.blocks } : {})\n };\n}\n","import WebSocket, { type RawData } from \"ws\";\nimport { createSlackDispatcherEventProcessorInput, type SlackDispatcherEventConfig } from \"./dispatcher-events.js\";\nimport { createSlackEventProcessor, type SlackAppRuntimeConfig, type SlackEventEnvelope, type SlackEventProcessorInput } from \"./events.js\";\n\nconst SLACK_CONNECTIONS_OPEN_URL = \"https://slack.com/api/apps.connections.open\";\nconst DEFAULT_RECONNECT_DELAY_MS = 1_000;\n\nexport type SlackSocketModeEnvelope = {\n type?: string;\n envelope_id?: string;\n payload?: SlackEventEnvelope;\n accepts_response_payload?: boolean;\n};\n\nexport type SlackSocketModeAppInput = {\n appToken: string;\n slackApp: SlackAppRuntimeConfig;\n} & SlackEventProcessorInput;\n\nexport type SlackSocketModeIngressConfig = SlackDispatcherEventConfig & {\n appToken: string;\n agentId?: string;\n appId?: string;\n callbackUri?: string;\n};\n\nexport type SlackSocketModeIngressHandle = {\n startPromise: Promise<void>;\n close(): Promise<void>;\n};\n\nexport type SlackSocketModeDependencies = {\n fetchImpl?: typeof fetch;\n createWebSocket?(url: string): WebSocket;\n reconnectDelayMs?: number;\n log?(message: string): void;\n logError?(message: string, error?: unknown): void;\n};\n\ntype SlackConnectionsOpenResponse = {\n ok?: boolean;\n url?: string;\n error?: string;\n needed?: string;\n provided?: string;\n};\n\nfunction rawDataToString(data: RawData): string {\n if (typeof data === \"string\") return data;\n if (Buffer.isBuffer(data)) return data.toString(\"utf8\");\n if (Array.isArray(data)) return Buffer.concat(data).toString(\"utf8\");\n if (data instanceof ArrayBuffer) return Buffer.from(data).toString(\"utf8\");\n return Buffer.from(data).toString(\"utf8\");\n}\n\nasync function openSlackSocketUrl(input: { appToken: string; fetchImpl: typeof fetch }): Promise<string> {\n const response = await input.fetchImpl(SLACK_CONNECTIONS_OPEN_URL, {\n method: \"POST\",\n headers: {\n authorization: `Bearer ${input.appToken}`\n }\n });\n const body = (await response.json().catch(() => ({}))) as SlackConnectionsOpenResponse;\n if (!response.ok || !body.ok || !body.url) {\n const reason = body.error ?? `http_${response.status}`;\n throw new Error(`Slack Socket Mode connection failed: ${reason}`);\n }\n return body.url;\n}\n\nfunction parseSocketEnvelope(data: RawData): SlackSocketModeEnvelope | null {\n try {\n return JSON.parse(rawDataToString(data)) as SlackSocketModeEnvelope;\n } catch {\n return null;\n }\n}\n\nasync function handleSocketMessage(input: {\n data: RawData;\n socket: WebSocket;\n processor: ReturnType<typeof createSlackEventProcessor>;\n slackApp: SlackAppRuntimeConfig;\n logError(message: string, error?: unknown): void;\n}): Promise<void> {\n const envelope = parseSocketEnvelope(input.data);\n if (!envelope?.envelope_id) {\n input.logError(\"[slack] ignored Socket Mode envelope without envelope_id\");\n return;\n }\n\n input.socket.send(JSON.stringify({ envelope_id: envelope.envelope_id }));\n\n if (envelope.type !== \"events_api\" || !envelope.payload) {\n return;\n }\n if (input.slackApp.appId && envelope.payload.api_app_id && envelope.payload.api_app_id !== input.slackApp.appId) {\n return;\n }\n\n await input.processor.process(envelope.payload, input.slackApp);\n}\n\nfunction wait(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport function startSlackSocketModeApp(\n input: SlackSocketModeAppInput,\n dependencies: SlackSocketModeDependencies = {}\n): SlackSocketModeIngressHandle {\n const fetchImpl = dependencies.fetchImpl ?? fetch;\n const createWebSocket = dependencies.createWebSocket ?? ((url: string) => new WebSocket(url));\n const reconnectDelayMs = dependencies.reconnectDelayMs ?? DEFAULT_RECONNECT_DELAY_MS;\n const log = dependencies.log ?? ((message: string) => console.log(message));\n const logError = dependencies.logError ?? ((message: string, error?: unknown) => (error ? console.error(message, error) : console.error(message)));\n const processor = createSlackEventProcessor(input);\n let closed = false;\n let activeSocket: WebSocket | undefined;\n\n async function runOneConnection(socketUrl: string): Promise<void> {\n await new Promise<void>((resolve) => {\n const socket = createWebSocket(socketUrl);\n activeSocket = socket;\n let settled = false;\n const finish = () => {\n if (settled) return;\n settled = true;\n if (activeSocket === socket) activeSocket = undefined;\n resolve();\n };\n\n socket.once(\"open\", () => {\n log(\"[slack] Socket Mode connected\");\n });\n socket.on(\"message\", (data) => {\n void handleSocketMessage({\n data,\n socket,\n processor,\n slackApp: input.slackApp,\n logError\n }).catch((error: unknown) => {\n logError(\"[slack] failed to handle Socket Mode event:\", error);\n });\n });\n socket.once(\"close\", finish);\n socket.once(\"error\", (error) => {\n if (!closed) {\n logError(\"[slack] Socket Mode connection error:\", error);\n }\n socket.close();\n finish();\n });\n });\n }\n\n const startPromise = (async () => {\n while (!closed) {\n const socketUrl = await openSlackSocketUrl({ appToken: input.appToken, fetchImpl });\n await runOneConnection(socketUrl);\n if (!closed) {\n await wait(reconnectDelayMs);\n }\n }\n })();\n\n return {\n startPromise,\n async close() {\n closed = true;\n activeSocket?.close();\n await startPromise.catch(() => undefined);\n }\n };\n}\n\nexport function startSlackSocketModeIngress(\n config: SlackSocketModeIngressConfig,\n dependencies: SlackSocketModeDependencies = {}\n): SlackSocketModeIngressHandle {\n return startSlackSocketModeApp(\n {\n appToken: config.appToken,\n slackApp: {\n agentId: config.agentId ?? \"opentag\",\n ...(config.appId ? { appId: config.appId } : {}),\n ...(config.callbackUri ? { callbackUri: config.callbackUri } : {})\n },\n ...createSlackDispatcherEventProcessorInput(config)\n },\n dependencies\n );\n}\n"],"mappings":"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","names":["text","text"]}
@@ -0,0 +1,43 @@
1
+ import { serve } from "@hono/node-server";
2
+ import { Hono } from "hono";
3
+ import { type SlackAppRuntimeConfig, type SlackEventProcessorInput } from "./events.js";
4
+ export type SlackEventsAppInput = {
5
+ slackApps: Array<SlackAppRuntimeConfig & {
6
+ signingSecret: string;
7
+ }>;
8
+ clock?: () => number;
9
+ } & SlackEventProcessorInput;
10
+ export type SlackEventsApiIngressConfig = {
11
+ signingSecret: string;
12
+ dispatcherUrl: string;
13
+ dispatcherToken?: string;
14
+ port?: number;
15
+ agentId?: string;
16
+ appId?: string;
17
+ callbackUri?: string;
18
+ };
19
+ export type SlackIngressConfig = SlackEventsApiIngressConfig;
20
+ export type SlackIngressHandle = {
21
+ url: string;
22
+ server: ReturnType<typeof serve>;
23
+ close(): Promise<void>;
24
+ };
25
+ export declare function computeSlackSignature(input: {
26
+ signingSecret: string;
27
+ timestamp: string;
28
+ rawBody: string;
29
+ }): string;
30
+ export declare function verifySlackSignature(input: {
31
+ signingSecret: string;
32
+ timestamp: string;
33
+ rawBody: string;
34
+ signature: string;
35
+ }): boolean;
36
+ export declare function verifySlackTimestamp(input: {
37
+ timestamp: string;
38
+ nowMs: number;
39
+ toleranceSeconds?: number;
40
+ }): boolean;
41
+ export declare function createSlackEventsApp(input: SlackEventsAppInput): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
42
+ export declare function startSlackIngress(config: SlackEventsApiIngressConfig): SlackIngressHandle;
43
+ //# sourceMappingURL=ingress.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingress.d.ts","sourceRoot":"","sources":["../src/ingress.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAA6B,KAAK,qBAAqB,EAA2B,KAAK,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAE5I,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,EAAE,KAAK,CACd,qBAAqB,GAAG;QACtB,aAAa,EAAE,MAAM,CAAC;KACvB,CACF,CAAC;IACF,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;CACtB,GAAG,wBAAwB,CAAC;AAE7B,MAAM,MAAM,2BAA2B,GAAG;IACxC,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,2BAA2B,CAAC;AAE7D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;IACjC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAC3C,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG,MAAM,CAIT;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAC1C,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAKV;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAMpH;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,mBAAmB,8EAkE9D;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,2BAA2B,GAAG,kBAAkB,CAgCzF"}
@@ -2,6 +2,7 @@ import { type OpenTagEvent } from "@opentag/core";
2
2
  export type SlackChannelBinding = {
3
3
  teamId: string;
4
4
  channelId: string;
5
+ repoProvider?: string;
5
6
  owner: string;
6
7
  repo: string;
7
8
  };
@@ -14,6 +15,8 @@ export type SlackAppMentionInput = {
14
15
  threadTs?: string;
15
16
  eventId: string;
16
17
  eventTime: number;
18
+ appId?: string;
19
+ agentId?: string;
17
20
  botUserId?: string;
18
21
  callbackUri?: string;
19
22
  binding: SlackChannelBinding;
@@ -1 +1 @@
1
- {"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../src/normalize.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,YAAY,EAAwB,MAAM,eAAe,CAAC;AAE5F,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,mBAAmB,CAAC;CAC9B,CAAC;AAEF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAapF;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAE3G;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAM9G;AAkCD,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,oBAAoB,GAAG,YAAY,GAAG,IAAI,CAwDzF"}
1
+ {"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../src/normalize.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgE,KAAK,YAAY,EAAwB,MAAM,eAAe,CAAC;AAEtI,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,mBAAmB,CAAC;CAC9B,CAAC;AAEF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAapF;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAE3G;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAM9G;AA8ED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,oBAAoB,GAAG,YAAY,GAAG,IAAI,CA+DzF"}
@@ -0,0 +1,36 @@
1
+ import { type OpenTagRunResult } from "@opentag/core";
2
+ export type SlackTextBlock = {
3
+ type: "section";
4
+ text: {
5
+ type: "mrkdwn";
6
+ text: string;
7
+ };
8
+ };
9
+ export type SlackDividerBlock = {
10
+ type: "divider";
11
+ };
12
+ export type SlackBlock = SlackTextBlock | SlackDividerBlock;
13
+ export type SlackMessagePayload = {
14
+ channel: string;
15
+ text: string;
16
+ thread_ts?: string;
17
+ ts?: string;
18
+ blocks?: SlackBlock[];
19
+ };
20
+ export declare function markdownToSlackMrkdwn(text: string): string;
21
+ export declare function renderSlackAcknowledgement(runId: string): string;
22
+ export declare function renderSlackFinalResult(result: OpenTagRunResult): string;
23
+ export declare function createSlackFinalResultBlocks(result: OpenTagRunResult): SlackBlock[];
24
+ export declare function createSlackPostMessagePayload(input: {
25
+ channelId: string;
26
+ text: string;
27
+ threadTs: string;
28
+ blocks?: SlackBlock[];
29
+ }): SlackMessagePayload;
30
+ export declare function createSlackUpdateMessagePayload(input: {
31
+ channelId: string;
32
+ text: string;
33
+ messageTs: string;
34
+ blocks?: SlackBlock[];
35
+ }): SlackMessagePayload;
36
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuC,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAE3F,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE;QACJ,IAAI,EAAE,QAAQ,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,SAAS,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,cAAc,GAAG,iBAAiB,CAAC;AAE5D,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,UAAU,EAAE,CAAC;CACvB,CAAC;AAMF,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAW1D;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhE;AA0FD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAqBvE;AAED,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,gBAAgB,GAAG,UAAU,EAAE,CA8CnF;AAED,wBAAgB,6BAA6B,CAAC,KAAK,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,UAAU,EAAE,CAAA;CAAE,GAAG,mBAAmB,CAOtJ;AAED,wBAAgB,+BAA+B,CAAC,KAAK,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,UAAU,EAAE,CAAA;CAAE,GAAG,mBAAmB,CAOzJ"}
@@ -0,0 +1,33 @@
1
+ import WebSocket from "ws";
2
+ import { type SlackDispatcherEventConfig } from "./dispatcher-events.js";
3
+ import { type SlackAppRuntimeConfig, type SlackEventEnvelope, type SlackEventProcessorInput } from "./events.js";
4
+ export type SlackSocketModeEnvelope = {
5
+ type?: string;
6
+ envelope_id?: string;
7
+ payload?: SlackEventEnvelope;
8
+ accepts_response_payload?: boolean;
9
+ };
10
+ export type SlackSocketModeAppInput = {
11
+ appToken: string;
12
+ slackApp: SlackAppRuntimeConfig;
13
+ } & SlackEventProcessorInput;
14
+ export type SlackSocketModeIngressConfig = SlackDispatcherEventConfig & {
15
+ appToken: string;
16
+ agentId?: string;
17
+ appId?: string;
18
+ callbackUri?: string;
19
+ };
20
+ export type SlackSocketModeIngressHandle = {
21
+ startPromise: Promise<void>;
22
+ close(): Promise<void>;
23
+ };
24
+ export type SlackSocketModeDependencies = {
25
+ fetchImpl?: typeof fetch;
26
+ createWebSocket?(url: string): WebSocket;
27
+ reconnectDelayMs?: number;
28
+ log?(message: string): void;
29
+ logError?(message: string, error?: unknown): void;
30
+ };
31
+ export declare function startSlackSocketModeApp(input: SlackSocketModeAppInput, dependencies?: SlackSocketModeDependencies): SlackSocketModeIngressHandle;
32
+ export declare function startSlackSocketModeIngress(config: SlackSocketModeIngressConfig, dependencies?: SlackSocketModeDependencies): SlackSocketModeIngressHandle;
33
+ //# sourceMappingURL=socket-mode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"socket-mode.d.ts","sourceRoot":"","sources":["../src/socket-mode.ts"],"names":[],"mappings":"AAAA,OAAO,SAA2B,MAAM,IAAI,CAAC;AAC7C,OAAO,EAA4C,KAAK,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AACnH,OAAO,EAA6B,KAAK,qBAAqB,EAAE,KAAK,kBAAkB,EAAE,KAAK,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAK5I,MAAM,MAAM,uBAAuB,GAAG;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,qBAAqB,CAAC;CACjC,GAAG,wBAAwB,CAAC;AAE7B,MAAM,MAAM,4BAA4B,GAAG,0BAA0B,GAAG;IACtE,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG;IACzC,YAAY,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACxC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,eAAe,CAAC,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CACnD,CAAC;AAsEF,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,uBAAuB,EAC9B,YAAY,GAAE,2BAAgC,GAC7C,4BAA4B,CAiE9B;AAED,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,4BAA4B,EACpC,YAAY,GAAE,2BAAgC,GAC7C,4BAA4B,CAa9B"}
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "@opentag/slack",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Slack app mention normalization and callback helpers for OpenTag.",
5
5
  "type": "module",
6
+ "engines": {
7
+ "node": ">=20"
8
+ },
6
9
  "main": "./dist/index.js",
7
10
  "types": "./dist/index.d.ts",
8
11
  "exports": {
@@ -26,11 +29,16 @@
26
29
  "agents",
27
30
  "webhooks"
28
31
  ],
29
- "license": "Apache-2.0",
32
+ "license": "MIT",
30
33
  "dependencies": {
31
- "@opentag/core": "0.1.0"
34
+ "@hono/node-server": "^1.13.7",
35
+ "hono": "^4.6.15",
36
+ "ws": "^8.21.0",
37
+ "@opentag/client": "0.2.0",
38
+ "@opentag/core": "0.2.0"
32
39
  },
33
40
  "devDependencies": {
41
+ "@types/ws": "^8.18.1",
34
42
  "tsup": "^8.5.1",
35
43
  "typescript": "^5.9.3"
36
44
  },