@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.
- package/dist/github/index.d.ts +3 -3
- package/dist/github/mcp/auth.d.ts +2 -2
- package/dist/github/mcp/index.d.ts +2 -2
- package/dist/github/routes/setup.d.ts +2 -2
- package/dist/github/routes/tokenExchange.d.ts +2 -2
- package/dist/github/routes/webhooks.d.ts +2 -2
- package/dist/slack/routes/events.js +352 -203
- package/dist/slack/services/events/app-mention.js +211 -160
- package/dist/slack/services/events/block-actions.js +225 -181
- package/dist/slack/services/events/modal-submission.js +309 -258
- package/dist/slack/services/events/streaming.js +193 -171
- package/dist/slack/tracer.d.ts +40 -0
- package/dist/slack/tracer.js +39 -0
- package/package.json +4 -3
package/dist/github/index.d.ts
CHANGED
|
@@ -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
|
|
7
|
+
import * as hono_types3 from "hono/types";
|
|
8
8
|
|
|
9
9
|
//#region src/github/index.d.ts
|
|
10
|
-
declare function createGithubRoutes(): Hono<
|
|
11
|
-
declare const githubRoutes: Hono<
|
|
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
|
|
1
|
+
import * as hono1 from "hono";
|
|
2
2
|
|
|
3
3
|
//#region src/github/mcp/auth.d.ts
|
|
4
|
-
declare const githubMcpAuth: () =>
|
|
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
|
|
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
|
-
},
|
|
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
|
|
2
|
+
import * as hono_types8 from "hono/types";
|
|
3
3
|
|
|
4
4
|
//#region src/github/routes/setup.d.ts
|
|
5
|
-
declare const app: Hono<
|
|
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
|
|
2
|
+
import * as hono_types0 from "hono/types";
|
|
3
3
|
|
|
4
4
|
//#region src/github/routes/tokenExchange.d.ts
|
|
5
|
-
declare const app: Hono<
|
|
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
|
|
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<
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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 (
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
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();
|