@inkeep/agents-work-apps 0.0.0-dev-20260212214459 → 0.0.0-dev-20260213074206

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.
@@ -4,10 +4,10 @@ import "./routes/setup.js";
4
4
  import "./routes/tokenExchange.js";
5
5
  import { WebhookVerificationResult, verifyWebhookSignature } from "./routes/webhooks.js";
6
6
  import { Hono } from "hono";
7
- import * as hono_types0 from "hono/types";
7
+ import * as hono_types3 from "hono/types";
8
8
 
9
9
  //#region src/github/index.d.ts
10
- declare function createGithubRoutes(): Hono<hono_types0.BlankEnv, hono_types0.BlankSchema, "/">;
11
- declare const githubRoutes: Hono<hono_types0.BlankEnv, hono_types0.BlankSchema, "/">;
10
+ declare function createGithubRoutes(): Hono<hono_types3.BlankEnv, hono_types3.BlankSchema, "/">;
11
+ declare const githubRoutes: Hono<hono_types3.BlankEnv, hono_types3.BlankSchema, "/">;
12
12
  //#endregion
13
13
  export { GenerateInstallationAccessTokenResult, GenerateTokenError, GenerateTokenResult, GitHubAppConfig, InstallationAccessToken, InstallationInfo, LookupInstallationError, LookupInstallationForRepoResult, LookupInstallationResult, WebhookVerificationResult, clearConfigCache, createAppJwt, createGithubRoutes, determineStatus, fetchInstallationDetails, fetchInstallationRepositories, generateInstallationAccessToken, getGitHubAppConfig, getGitHubAppName, getStateSigningSecret, getWebhookSecret, githubRoutes, isGitHubAppConfigured, isGitHubAppNameConfigured, isStateSigningConfigured, isWebhookConfigured, lookupInstallationForRepo, validateGitHubAppConfigOnStartup, validateGitHubInstallFlowConfigOnStartup, validateGitHubWebhookConfigOnStartup, verifyWebhookSignature };
@@ -1,7 +1,7 @@
1
- import * as hono0 from "hono";
1
+ import * as hono1 from "hono";
2
2
 
3
3
  //#region src/github/mcp/auth.d.ts
4
- declare const githubMcpAuth: () => hono0.MiddlewareHandler<{
4
+ declare const githubMcpAuth: () => hono1.MiddlewareHandler<{
5
5
  Variables: {
6
6
  toolId: string;
7
7
  };
@@ -1,11 +1,11 @@
1
1
  import { Hono } from "hono";
2
- import * as hono_types3 from "hono/types";
2
+ import * as hono_types7 from "hono/types";
3
3
 
4
4
  //#region src/github/mcp/index.d.ts
5
5
  declare const app: Hono<{
6
6
  Variables: {
7
7
  toolId: string;
8
8
  };
9
- }, hono_types3.BlankSchema, "/">;
9
+ }, hono_types7.BlankSchema, "/">;
10
10
  //#endregion
11
11
  export { app as default };
@@ -1,7 +1,7 @@
1
1
  import { Hono } from "hono";
2
- import * as hono_types4 from "hono/types";
2
+ import * as hono_types8 from "hono/types";
3
3
 
4
4
  //#region src/github/routes/setup.d.ts
5
- declare const app: Hono<hono_types4.BlankEnv, hono_types4.BlankSchema, "/">;
5
+ declare const app: Hono<hono_types8.BlankEnv, hono_types8.BlankSchema, "/">;
6
6
  //#endregion
7
7
  export { app as default };
@@ -1,7 +1,7 @@
1
1
  import { Hono } from "hono";
2
- import * as hono_types6 from "hono/types";
2
+ import * as hono_types0 from "hono/types";
3
3
 
4
4
  //#region src/github/routes/tokenExchange.d.ts
5
- declare const app: Hono<hono_types6.BlankEnv, hono_types6.BlankSchema, "/">;
5
+ declare const app: Hono<hono_types0.BlankEnv, hono_types0.BlankSchema, "/">;
6
6
  //#endregion
7
7
  export { app as default };
@@ -1,5 +1,5 @@
1
1
  import { Hono } from "hono";
2
- import * as hono_types8 from "hono/types";
2
+ import * as hono_types1 from "hono/types";
3
3
 
4
4
  //#region src/github/routes/webhooks.d.ts
5
5
  interface WebhookVerificationResult {
@@ -7,6 +7,6 @@ interface WebhookVerificationResult {
7
7
  error?: string;
8
8
  }
9
9
  declare function verifyWebhookSignature(payload: string, signature: string | undefined, secret: string): WebhookVerificationResult;
10
- declare const app: Hono<hono_types8.BlankEnv, hono_types8.BlankSchema, "/">;
10
+ declare const app: Hono<hono_types1.BlankEnv, hono_types1.BlankSchema, "/">;
11
11
  //#endregion
12
12
  export { WebhookVerificationResult, app as default, verifyWebhookSignature };
@@ -5,6 +5,7 @@ import { findWorkspaceConnectionByTeamId, getSlackIntegrationId, getSlackNango,
5
5
  import { getSlackClient, getSlackUserInfo } from "../services/client.js";
6
6
  import { sendResponseUrlMessage } from "../services/events/utils.js";
7
7
  import { handleCommand } from "../services/commands/index.js";
8
+ import { SLACK_SPAN_KEYS, SLACK_SPAN_NAMES, tracer } from "../tracer.js";
8
9
  import { handleAppMention } from "../services/events/app-mention.js";
9
10
  import { handleMessageShortcut, handleOpenAgentSelectorModal, handleOpenFollowUpModal } from "../services/events/block-actions.js";
10
11
  import { handleFollowUpSubmission, handleModalSubmission } from "../services/events/modal-submission.js";
@@ -13,6 +14,7 @@ import { parseSlackCommandBody, parseSlackEventBody, verifySlackRequest } from "
13
14
  import "../services/index.js";
14
15
  import { OpenAPIHono } from "@hono/zod-openapi";
15
16
  import { deleteAllWorkAppSlackChannelAgentConfigsByTeam, deleteAllWorkAppSlackUserMappingsByTeam, deleteWorkAppSlackWorkspaceByNangoConnectionId } from "@inkeep/agents-core";
17
+ import { SpanStatusCode } from "@opentelemetry/api";
16
18
 
17
19
  //#region src/slack/routes/events.ts
18
20
  /**
@@ -61,217 +63,364 @@ app.post("/commands", async (c) => {
61
63
  return c.json(response);
62
64
  });
63
65
  app.post("/events", async (c) => {
64
- const contentType = c.req.header("content-type") || "";
65
- const body = await c.req.text();
66
- const timestamp = c.req.header("x-slack-request-timestamp") || "";
67
- const signature = c.req.header("x-slack-signature") || "";
68
- let eventBody;
69
- try {
70
- eventBody = parseSlackEventBody(body, contentType);
71
- } catch (error) {
72
- logger.error({
73
- error,
74
- contentType,
75
- bodyPreview: body.slice(0, 200)
76
- }, "Failed to parse Slack event body");
77
- return c.json({ error: "Invalid payload" }, 400);
78
- }
79
- logger.debug({ eventType: eventBody.type }, "Slack event received");
80
- const eventType = eventBody.type;
81
- if (!env.SLACK_SIGNING_SECRET) {
82
- logger.error({}, "SLACK_SIGNING_SECRET not configured - rejecting request");
83
- return c.json({ error: "Server configuration error" }, 500);
84
- }
85
- if (!verifySlackRequest(env.SLACK_SIGNING_SECRET, body, timestamp, signature)) {
86
- logger.error({}, "Invalid Slack request signature");
87
- return c.json({ error: "Invalid request signature" }, 401);
88
- }
89
- if (eventType === "url_verification") {
90
- logger.info({}, "Responding to Slack URL verification challenge");
91
- return c.text(String(eventBody.challenge));
92
- }
93
- if (eventType === "event_callback") {
94
- const teamId = eventBody.team_id;
95
- const event = eventBody.event;
96
- if (event?.bot_id || event?.subtype === "bot_message") {
97
- logger.debug({ botId: event.bot_id }, "Ignoring bot message");
98
- return c.json({ ok: true });
99
- }
100
- logger.debug({
101
- eventType: event?.type,
102
- teamId
103
- }, "Slack event callback");
104
- if (event?.type === "app_mention" && event.channel && event.user && teamId) {
105
- const question = (event.text || "").replace(/<@[A-Z0-9]+>/g, "").trim();
106
- logger.info({
107
- userId: event.user,
108
- channel: event.channel,
109
- teamId
110
- }, "Bot was mentioned");
111
- handleAppMention({
112
- slackUserId: event.user,
113
- channel: event.channel,
114
- text: question,
115
- threadTs: event.thread_ts || event.ts || "",
116
- messageTs: event.ts || "",
117
- teamId
118
- }).catch((err) => {
119
- const errorMessage = err instanceof Error ? err.message : String(err);
120
- const errorStack = err instanceof Error ? err.stack : void 0;
121
- logger.error({
122
- errorMessage,
123
- errorStack
124
- }, "Failed to handle app mention (outer catch)");
125
- });
126
- }
127
- }
128
- if (eventType === "block_actions" || eventType === "interactive_message") {
129
- logger.debug({ eventType }, "Slack interactive event received");
130
- const actions = eventBody.actions;
131
- const teamId = eventBody.team?.id;
132
- const responseUrl = eventBody.response_url;
133
- const triggerId = eventBody.trigger_id;
134
- if (actions && teamId) for (const action of actions) {
135
- if (action.action_id === "open_agent_selector_modal" && action.value && triggerId) handleOpenAgentSelectorModal({
136
- triggerId,
137
- actionValue: action.value,
138
- teamId,
139
- responseUrl: responseUrl || ""
140
- }).catch(async (err) => {
141
- const errorMessage = err instanceof Error ? err.message : String(err);
66
+ return tracer.startActiveSpan(SLACK_SPAN_NAMES.WEBHOOK, async (span) => {
67
+ let outcome = "ignored_unknown_event";
68
+ try {
69
+ const contentType = c.req.header("content-type") || "";
70
+ const body = await c.req.text();
71
+ const timestamp = c.req.header("x-slack-request-timestamp") || "";
72
+ const signature = c.req.header("x-slack-signature") || "";
73
+ let eventBody;
74
+ try {
75
+ eventBody = parseSlackEventBody(body, contentType);
76
+ } catch (error) {
77
+ outcome = "validation_error";
78
+ span.setAttribute(SLACK_SPAN_KEYS.OUTCOME, outcome);
142
79
  logger.error({
143
- errorMessage,
144
- actionId: action.action_id
145
- }, "Failed to open agent selector modal");
146
- if (responseUrl) await sendResponseUrlMessage(responseUrl, {
147
- text: "Sorry, something went wrong while opening the agent selector. Please try again.",
148
- response_type: "ephemeral"
149
- }).catch((e) => logger.warn({ error: e }, "Failed to send error notification via response URL"));
150
- });
151
- if (action.action_id === "modal_project_select") {
152
- const selectedProjectId = action.selected_option?.value;
153
- const view = eventBody.view;
154
- if (selectedProjectId && view?.id) (async () => {
155
- try {
156
- const metadata = JSON.parse(view.private_metadata || "{}");
157
- const tenantId = metadata.tenantId;
158
- if (!tenantId) {
159
- logger.warn({ teamId }, "No tenantId in modal metadata — skipping project update");
160
- return;
80
+ error,
81
+ contentType,
82
+ bodyPreview: body.slice(0, 200)
83
+ }, "Failed to parse Slack event body");
84
+ span.end();
85
+ return c.json({ error: "Invalid payload" }, 400);
86
+ }
87
+ const eventType = eventBody.type;
88
+ span.setAttribute(SLACK_SPAN_KEYS.EVENT_TYPE, eventType || "unknown");
89
+ span.updateName(`${SLACK_SPAN_NAMES.WEBHOOK} ${eventType || "unknown"}`);
90
+ if (!env.SLACK_SIGNING_SECRET) {
91
+ outcome = "error";
92
+ span.setAttribute(SLACK_SPAN_KEYS.OUTCOME, outcome);
93
+ logger.error({}, "SLACK_SIGNING_SECRET not configured - rejecting request");
94
+ span.end();
95
+ return c.json({ error: "Server configuration error" }, 500);
96
+ }
97
+ if (!verifySlackRequest(env.SLACK_SIGNING_SECRET, body, timestamp, signature)) {
98
+ outcome = "signature_invalid";
99
+ span.setAttribute(SLACK_SPAN_KEYS.OUTCOME, outcome);
100
+ logger.error({ eventType }, "Invalid Slack request signature");
101
+ span.end();
102
+ return c.json({ error: "Invalid request signature" }, 401);
103
+ }
104
+ if (eventType === "url_verification") {
105
+ outcome = "url_verification";
106
+ span.setAttribute(SLACK_SPAN_KEYS.OUTCOME, outcome);
107
+ logger.info({}, "Responding to Slack URL verification challenge");
108
+ span.end();
109
+ return c.text(String(eventBody.challenge));
110
+ }
111
+ if (eventType === "event_callback") {
112
+ const teamId = eventBody.team_id;
113
+ const event = eventBody.event;
114
+ const innerEventType = event?.type || "unknown";
115
+ span.setAttribute(SLACK_SPAN_KEYS.INNER_EVENT_TYPE, innerEventType);
116
+ if (teamId) span.setAttribute(SLACK_SPAN_KEYS.TEAM_ID, teamId);
117
+ if (event?.channel) span.setAttribute(SLACK_SPAN_KEYS.CHANNEL_ID, event.channel);
118
+ if (event?.user) span.setAttribute(SLACK_SPAN_KEYS.USER_ID, event.user);
119
+ span.updateName(`${SLACK_SPAN_NAMES.WEBHOOK} event_callback.${innerEventType}`);
120
+ if (event?.bot_id || event?.subtype === "bot_message") {
121
+ outcome = "ignored_bot_message";
122
+ span.setAttribute(SLACK_SPAN_KEYS.IS_BOT_MESSAGE, true);
123
+ span.setAttribute(SLACK_SPAN_KEYS.OUTCOME, outcome);
124
+ logger.info({
125
+ botId: event.bot_id,
126
+ subtype: event?.subtype,
127
+ teamId,
128
+ innerEventType
129
+ }, "Ignoring bot message");
130
+ span.end();
131
+ return c.json({ ok: true });
132
+ }
133
+ if (event?.type === "app_mention" && event.channel && event.user && teamId) {
134
+ outcome = "handled";
135
+ span.setAttribute(SLACK_SPAN_KEYS.OUTCOME, outcome);
136
+ const question = (event.text || "").replace(/<@[A-Z0-9]+>/g, "").trim();
137
+ span.setAttribute(SLACK_SPAN_KEYS.HAS_QUERY, question.length > 0);
138
+ if (event.thread_ts) span.setAttribute(SLACK_SPAN_KEYS.THREAD_TS, event.thread_ts);
139
+ if (event.ts) span.setAttribute(SLACK_SPAN_KEYS.MESSAGE_TS, event.ts);
140
+ logger.info({
141
+ userId: event.user,
142
+ channel: event.channel,
143
+ teamId,
144
+ hasQuery: question.length > 0
145
+ }, "Handling event: app_mention");
146
+ handleAppMention({
147
+ slackUserId: event.user,
148
+ channel: event.channel,
149
+ text: question,
150
+ threadTs: event.thread_ts || event.ts || "",
151
+ messageTs: event.ts || "",
152
+ teamId
153
+ }).catch((err) => {
154
+ const errorMessage = err instanceof Error ? err.message : String(err);
155
+ const errorStack = err instanceof Error ? err.stack : void 0;
156
+ logger.error({
157
+ errorMessage,
158
+ errorStack
159
+ }, "Failed to handle app mention (outer catch)");
160
+ });
161
+ } else {
162
+ outcome = "ignored_unknown_event";
163
+ span.setAttribute(SLACK_SPAN_KEYS.OUTCOME, outcome);
164
+ logger.info({
165
+ innerEventType,
166
+ teamId
167
+ }, `Ignoring unhandled event_callback: ${innerEventType}`);
168
+ }
169
+ }
170
+ if (eventType === "block_actions" || eventType === "interactive_message") {
171
+ const actions = eventBody.actions;
172
+ const teamId = eventBody.team?.id;
173
+ const responseUrl = eventBody.response_url;
174
+ const triggerId = eventBody.trigger_id;
175
+ const actionIds = actions?.map((a) => a.action_id) || [];
176
+ if (teamId) span.setAttribute(SLACK_SPAN_KEYS.TEAM_ID, teamId);
177
+ span.setAttribute(SLACK_SPAN_KEYS.ACTION_IDS, actionIds.join(","));
178
+ span.updateName(`${SLACK_SPAN_NAMES.WEBHOOK} ${eventType} [${actionIds.join(",")}]`);
179
+ if (actions && teamId) {
180
+ let anyHandled = false;
181
+ for (const action of actions) {
182
+ if (action.action_id === "open_agent_selector_modal" && action.value && triggerId) {
183
+ anyHandled = true;
184
+ logger.info({
185
+ teamId,
186
+ actionId: action.action_id
187
+ }, "Handling block_action: open_agent_selector_modal");
188
+ handleOpenAgentSelectorModal({
189
+ triggerId,
190
+ actionValue: action.value,
191
+ teamId,
192
+ responseUrl: responseUrl || ""
193
+ }).catch(async (err) => {
194
+ const errorMessage = err instanceof Error ? err.message : String(err);
195
+ logger.error({
196
+ errorMessage,
197
+ actionId: action.action_id
198
+ }, "Failed to open agent selector modal");
199
+ if (responseUrl) await sendResponseUrlMessage(responseUrl, {
200
+ text: "Sorry, something went wrong while opening the agent selector. Please try again.",
201
+ response_type: "ephemeral"
202
+ }).catch((e) => logger.warn({ error: e }, "Failed to send error notification via response URL"));
203
+ });
161
204
  }
162
- const workspace = await findWorkspaceConnectionByTeamId(teamId);
163
- if (!workspace?.botToken) return;
164
- const slackClient = getSlackClient(workspace.botToken);
165
- const { fetchProjectsForTenant, fetchAgentsForProject } = await import("../services/events/utils.js");
166
- const { buildAgentSelectorModal, buildMessageShortcutModal } = await import("../services/modals.js");
167
- const projectList = await fetchProjectsForTenant(tenantId);
168
- const agentList = await fetchAgentsForProject(tenantId, selectedProjectId);
169
- const agentOptions = agentList.map((a) => ({
170
- id: a.id,
171
- name: a.name,
172
- projectId: a.projectId,
173
- projectName: a.projectName || a.projectId
174
- }));
175
- const modal = metadata.messageContext ? buildMessageShortcutModal({
176
- projects: projectList,
177
- agents: agentOptions,
178
- metadata,
179
- selectedProjectId,
180
- messageContext: metadata.messageContext
181
- }) : buildAgentSelectorModal({
182
- projects: projectList,
183
- agents: agentOptions,
184
- metadata,
185
- selectedProjectId
205
+ if (action.action_id === "modal_project_select") {
206
+ anyHandled = true;
207
+ const selectedProjectId = action.selected_option?.value;
208
+ const view = eventBody.view;
209
+ logger.info({
210
+ teamId,
211
+ actionId: action.action_id,
212
+ selectedProjectId
213
+ }, "Handling block_action: modal_project_select");
214
+ if (selectedProjectId && view?.id) (async () => {
215
+ try {
216
+ const metadata = JSON.parse(view.private_metadata || "{}");
217
+ const tenantId = metadata.tenantId;
218
+ if (!tenantId) {
219
+ logger.warn({ teamId }, "No tenantId in modal metadata — skipping project update");
220
+ return;
221
+ }
222
+ const workspace = await findWorkspaceConnectionByTeamId(teamId);
223
+ if (!workspace?.botToken) return;
224
+ const slackClient = getSlackClient(workspace.botToken);
225
+ const { fetchProjectsForTenant, fetchAgentsForProject } = await import("../services/events/utils.js");
226
+ const { buildAgentSelectorModal, buildMessageShortcutModal } = await import("../services/modals.js");
227
+ const projectList = await fetchProjectsForTenant(tenantId);
228
+ const agentList = await fetchAgentsForProject(tenantId, selectedProjectId);
229
+ const agentOptions = agentList.map((a) => ({
230
+ id: a.id,
231
+ name: a.name,
232
+ projectId: a.projectId,
233
+ projectName: a.projectName || a.projectId
234
+ }));
235
+ const modal = metadata.messageContext ? buildMessageShortcutModal({
236
+ projects: projectList,
237
+ agents: agentOptions,
238
+ metadata,
239
+ selectedProjectId,
240
+ messageContext: metadata.messageContext
241
+ }) : buildAgentSelectorModal({
242
+ projects: projectList,
243
+ agents: agentOptions,
244
+ metadata,
245
+ selectedProjectId
246
+ });
247
+ await slackClient.views.update({
248
+ view_id: view.id,
249
+ view: modal
250
+ });
251
+ logger.debug({
252
+ selectedProjectId,
253
+ agentCount: agentList.length
254
+ }, "Updated modal with agents for selected project");
255
+ } catch (err) {
256
+ logger.error({
257
+ err,
258
+ selectedProjectId
259
+ }, "Failed to update modal on project change");
260
+ }
261
+ })();
262
+ }
263
+ if (action.action_id === "open_follow_up_modal" && action.value && triggerId) {
264
+ anyHandled = true;
265
+ logger.info({
266
+ teamId,
267
+ actionId: action.action_id
268
+ }, "Handling block_action: open_follow_up_modal");
269
+ handleOpenFollowUpModal({
270
+ triggerId,
271
+ actionValue: action.value,
272
+ teamId,
273
+ responseUrl: responseUrl || void 0
274
+ }).catch((err) => {
275
+ const errorMessage = err instanceof Error ? err.message : String(err);
276
+ logger.error({
277
+ errorMessage,
278
+ actionId: action.action_id
279
+ }, "Failed to open follow-up modal");
280
+ });
281
+ }
282
+ }
283
+ outcome = anyHandled ? "handled" : "ignored_no_action_match";
284
+ span.setAttribute(SLACK_SPAN_KEYS.OUTCOME, outcome);
285
+ if (!anyHandled) logger.info({
286
+ teamId,
287
+ actionIds,
288
+ eventType
289
+ }, "Ignoring block_actions: no matching action handlers");
290
+ } else {
291
+ outcome = "ignored_no_action_match";
292
+ span.setAttribute(SLACK_SPAN_KEYS.OUTCOME, outcome);
293
+ logger.info({
294
+ teamId,
295
+ eventType,
296
+ hasActions: Boolean(actions)
297
+ }, "Ignoring block_actions: missing actions or teamId");
298
+ }
299
+ }
300
+ if (eventType === "message_action") {
301
+ const callbackId = eventBody.callback_id;
302
+ span.setAttribute(SLACK_SPAN_KEYS.CALLBACK_ID, callbackId || "unknown");
303
+ span.updateName(`${SLACK_SPAN_NAMES.WEBHOOK} message_action.${callbackId || "unknown"}`);
304
+ if (callbackId === "ask_agent_shortcut") {
305
+ const triggerId = eventBody.trigger_id;
306
+ const teamId = eventBody.team?.id;
307
+ const channelId = eventBody.channel?.id;
308
+ const userId = eventBody.user?.id;
309
+ const message = eventBody.message;
310
+ const responseUrl = eventBody.response_url;
311
+ if (teamId) span.setAttribute(SLACK_SPAN_KEYS.TEAM_ID, teamId);
312
+ if (channelId) span.setAttribute(SLACK_SPAN_KEYS.CHANNEL_ID, channelId);
313
+ if (userId) span.setAttribute(SLACK_SPAN_KEYS.USER_ID, userId);
314
+ if (triggerId && teamId && channelId && userId && message?.ts) {
315
+ outcome = "handled";
316
+ span.setAttribute(SLACK_SPAN_KEYS.OUTCOME, outcome);
317
+ logger.info({
318
+ teamId,
319
+ channelId,
320
+ userId,
321
+ callbackId
322
+ }, "Handling message_action: ask_agent_shortcut");
323
+ handleMessageShortcut({
324
+ triggerId,
325
+ teamId,
326
+ channelId,
327
+ userId,
328
+ messageTs: message.ts,
329
+ messageText: message.text || "",
330
+ threadTs: message.thread_ts,
331
+ responseUrl
332
+ }).catch((err) => {
333
+ const errorMessage = err instanceof Error ? err.message : String(err);
334
+ logger.error({
335
+ errorMessage,
336
+ callbackId
337
+ }, "Failed to handle message shortcut");
186
338
  });
187
- await slackClient.views.update({
188
- view_id: view.id,
189
- view: modal
339
+ } else {
340
+ outcome = "ignored_unknown_event";
341
+ span.setAttribute(SLACK_SPAN_KEYS.OUTCOME, outcome);
342
+ logger.info({
343
+ teamId,
344
+ channelId,
345
+ userId,
346
+ callbackId,
347
+ hasTriggerId: Boolean(triggerId)
348
+ }, "Ignoring message_action: missing required fields");
349
+ }
350
+ } else {
351
+ outcome = "ignored_unknown_event";
352
+ span.setAttribute(SLACK_SPAN_KEYS.OUTCOME, outcome);
353
+ logger.info({ callbackId }, `Ignoring unhandled message_action: ${callbackId}`);
354
+ }
355
+ }
356
+ if (eventType === "view_submission") {
357
+ const callbackId = eventBody.view?.callback_id;
358
+ span.setAttribute(SLACK_SPAN_KEYS.CALLBACK_ID, callbackId || "unknown");
359
+ span.updateName(`${SLACK_SPAN_NAMES.WEBHOOK} view_submission.${callbackId || "unknown"}`);
360
+ if (callbackId === "agent_selector_modal") {
361
+ const view = eventBody.view;
362
+ const agentSelect = view.state?.values?.agent_select_block?.agent_select;
363
+ if (!agentSelect?.selected_option?.value || agentSelect.selected_option.value === "none") {
364
+ outcome = "validation_error";
365
+ span.setAttribute(SLACK_SPAN_KEYS.OUTCOME, outcome);
366
+ logger.info({ callbackId }, "Rejecting view_submission: no agent selected");
367
+ span.end();
368
+ return c.json({
369
+ response_action: "errors",
370
+ errors: { agent_select_block: "Please select an agent. If none are available, add agents to this project in the dashboard." }
190
371
  });
191
- logger.debug({
192
- selectedProjectId,
193
- agentCount: agentList.length
194
- }, "Updated modal with agents for selected project");
195
- } catch (err) {
196
- logger.error({
197
- err,
198
- selectedProjectId
199
- }, "Failed to update modal on project change");
200
372
  }
201
- })();
373
+ outcome = "handled";
374
+ span.setAttribute(SLACK_SPAN_KEYS.OUTCOME, outcome);
375
+ logger.info({ callbackId }, "Handling view_submission: agent_selector_modal");
376
+ handleModalSubmission(view).catch((err) => {
377
+ const errorMessage = err instanceof Error ? err.message : String(err);
378
+ logger.error({
379
+ errorMessage,
380
+ callbackId
381
+ }, "Failed to handle modal submission");
382
+ });
383
+ span.end();
384
+ return new Response(null, { status: 200 });
385
+ }
386
+ if (callbackId === "follow_up_modal") {
387
+ const view = eventBody.view;
388
+ outcome = "handled";
389
+ span.setAttribute(SLACK_SPAN_KEYS.OUTCOME, outcome);
390
+ logger.info({ callbackId }, "Handling view_submission: follow_up_modal");
391
+ handleFollowUpSubmission(view).catch((err) => {
392
+ const errorMessage = err instanceof Error ? err.message : String(err);
393
+ logger.error({
394
+ errorMessage,
395
+ callbackId
396
+ }, "Failed to handle follow-up submission");
397
+ });
398
+ span.end();
399
+ return new Response(null, { status: 200 });
400
+ }
401
+ outcome = "ignored_unknown_event";
402
+ span.setAttribute(SLACK_SPAN_KEYS.OUTCOME, outcome);
403
+ logger.info({ callbackId }, `Ignoring unhandled view_submission: ${callbackId}`);
202
404
  }
203
- if (action.action_id === "open_follow_up_modal" && action.value && triggerId) handleOpenFollowUpModal({
204
- triggerId,
205
- actionValue: action.value,
206
- teamId,
207
- responseUrl: responseUrl || void 0
208
- }).catch((err) => {
209
- const errorMessage = err instanceof Error ? err.message : String(err);
210
- logger.error({
211
- errorMessage,
212
- actionId: action.action_id
213
- }, "Failed to open follow-up modal");
214
- });
215
- }
216
- }
217
- if (eventType === "message_action") {
218
- const callbackId = eventBody.callback_id;
219
- if (callbackId === "ask_agent_shortcut") {
220
- const triggerId = eventBody.trigger_id;
221
- const teamId = eventBody.team?.id;
222
- const channelId = eventBody.channel?.id;
223
- const userId = eventBody.user?.id;
224
- const message = eventBody.message;
225
- const responseUrl = eventBody.response_url;
226
- if (triggerId && teamId && channelId && userId && message?.ts) handleMessageShortcut({
227
- triggerId,
228
- teamId,
229
- channelId,
230
- userId,
231
- messageTs: message.ts,
232
- messageText: message.text || "",
233
- threadTs: message.thread_ts,
234
- responseUrl
235
- }).catch((err) => {
236
- const errorMessage = err instanceof Error ? err.message : String(err);
237
- logger.error({
238
- errorMessage,
239
- callbackId
240
- }, "Failed to handle message shortcut");
241
- });
242
- }
243
- }
244
- if (eventType === "view_submission") {
245
- const callbackId = eventBody.view?.callback_id;
246
- if (callbackId === "agent_selector_modal") {
247
- const view = eventBody.view;
248
- const agentSelect = view.state?.values?.agent_select_block?.agent_select;
249
- if (!agentSelect?.selected_option?.value || agentSelect.selected_option.value === "none") return c.json({
250
- response_action: "errors",
251
- errors: { agent_select_block: "Please select an agent. If none are available, add agents to this project in the dashboard." }
252
- });
253
- handleModalSubmission(view).catch((err) => {
254
- const errorMessage = err instanceof Error ? err.message : String(err);
255
- logger.error({
256
- errorMessage,
257
- callbackId
258
- }, "Failed to handle modal submission");
259
- });
260
- return new Response(null, { status: 200 });
261
- }
262
- if (callbackId === "follow_up_modal") {
263
- const view = eventBody.view;
264
- handleFollowUpSubmission(view).catch((err) => {
265
- const errorMessage = err instanceof Error ? err.message : String(err);
266
- logger.error({
267
- errorMessage,
268
- callbackId
269
- }, "Failed to handle follow-up submission");
405
+ if (eventType !== "event_callback" && eventType !== "block_actions" && eventType !== "interactive_message" && eventType !== "message_action" && eventType !== "view_submission") {
406
+ outcome = "ignored_unknown_event";
407
+ span.setAttribute(SLACK_SPAN_KEYS.OUTCOME, outcome);
408
+ logger.info({ eventType }, `Ignoring unhandled Slack event type: ${eventType}`);
409
+ }
410
+ span.end();
411
+ return c.json({ ok: true });
412
+ } catch (error) {
413
+ outcome = "error";
414
+ span.setAttribute(SLACK_SPAN_KEYS.OUTCOME, outcome);
415
+ span.setStatus({
416
+ code: SpanStatusCode.ERROR,
417
+ message: String(error)
270
418
  });
271
- return new Response(null, { status: 200 });
419
+ if (error instanceof Error) span.recordException(error);
420
+ span.end();
421
+ throw error;
272
422
  }
273
- }
274
- return c.json({ ok: true });
423
+ });
275
424
  });
276
425
  app.post("/nango-webhook", async (c) => {
277
426
  const body = await c.req.text();