@inkeep/agents-work-apps 0.0.0-dev-20260224193013 → 0.0.0-dev-20260224195557
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/mcp/schemas.d.ts +1 -1
- 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/users.js +29 -3
- package/dist/slack/services/blocks/index.d.ts +2 -2
- package/dist/slack/services/blocks/index.js +4 -4
- package/dist/slack/services/commands/index.d.ts +1 -1
- package/dist/slack/services/commands/index.js +32 -122
- package/dist/slack/services/events/app-mention.js +27 -15
- package/dist/slack/services/events/utils.js +10 -8
- package/dist/slack/services/index.d.ts +2 -2
- package/dist/slack/services/index.js +2 -2
- package/dist/slack/services/link-prompt.d.ts +27 -0
- package/dist/slack/services/link-prompt.js +142 -0
- package/dist/slack/services/resume-intent.d.ts +15 -0
- package/dist/slack/services/resume-intent.js +338 -0
- package/package.json +2 -2
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_types0 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_types0.BlankEnv, hono_types0.BlankSchema, "/">;
|
|
11
|
+
declare const githubRoutes: Hono<hono_types0.BlankEnv, hono_types0.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_types0 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_types0.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_types6 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_types6.BlankEnv, hono_types6.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_types4 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_types4.BlankEnv, hono_types4.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_types8 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_types8.BlankEnv, hono_types8.BlankSchema, "/">;
|
|
11
11
|
//#endregion
|
|
12
12
|
export { WebhookVerificationResult, app as default, verifyWebhookSignature };
|
|
@@ -2,8 +2,9 @@ import { getLogger } from "../../logger.js";
|
|
|
2
2
|
import runDbClient_default from "../../db/runDbClient.js";
|
|
3
3
|
import { createConnectSession } from "../services/nango.js";
|
|
4
4
|
import "../services/index.js";
|
|
5
|
+
import { resumeSmartLinkIntent } from "../services/resume-intent.js";
|
|
5
6
|
import { OpenAPIHono, z } from "@hono/zod-openapi";
|
|
6
|
-
import { createWorkAppSlackUserMapping, deleteWorkAppSlackUserMapping, findWorkAppSlackUserMapping, findWorkAppSlackUserMappingByInkeepUserId, isUniqueConstraintError, verifySlackLinkToken } from "@inkeep/agents-core";
|
|
7
|
+
import { createWorkAppSlackUserMapping, deleteWorkAppSlackUserMapping, findWorkAppSlackUserMapping, findWorkAppSlackUserMappingByInkeepUserId, flushTraces, getWaitUntil, isUniqueConstraintError, verifySlackLinkToken } from "@inkeep/agents-core";
|
|
7
8
|
import { createProtectedRoute, inheritedWorkAppsAuth } from "@inkeep/agents-core/middleware";
|
|
8
9
|
|
|
9
10
|
//#region src/slack/routes/users.ts
|
|
@@ -105,8 +106,13 @@ app.openapi(createProtectedRoute({
|
|
|
105
106
|
try {
|
|
106
107
|
const verifyResult = await verifySlackLinkToken(body.token);
|
|
107
108
|
if (!verifyResult.valid || !verifyResult.payload) {
|
|
108
|
-
|
|
109
|
-
|
|
109
|
+
const isExpired = verifyResult.error?.includes("\"exp\" claim timestamp check failed");
|
|
110
|
+
const errorMessage = isExpired ? "Token expired. Please run /inkeep link in Slack to get a new one." : verifyResult.error || "Invalid or expired link token. Please run /inkeep link in Slack to get a new one.";
|
|
111
|
+
logger.warn({
|
|
112
|
+
error: verifyResult.error,
|
|
113
|
+
isExpired
|
|
114
|
+
}, "Invalid link token");
|
|
115
|
+
return c.json({ error: errorMessage }, 400);
|
|
110
116
|
}
|
|
111
117
|
const { tenantId, slack } = verifyResult.payload;
|
|
112
118
|
const { teamId, userId: slackUserId, enterpriseId, username } = slack;
|
|
@@ -157,6 +163,26 @@ app.openapi(createProtectedRoute({
|
|
|
157
163
|
inkeepUserId: body.userId,
|
|
158
164
|
linkId: slackUserMapping.id
|
|
159
165
|
}, "Successfully linked Slack user to Inkeep account via JWT token");
|
|
166
|
+
const { intent } = verifyResult.payload;
|
|
167
|
+
if (intent) {
|
|
168
|
+
logger.info({
|
|
169
|
+
event: "smart_link_intent_resume_triggered",
|
|
170
|
+
entryPoint: intent.entryPoint,
|
|
171
|
+
questionLength: intent.question.length
|
|
172
|
+
}, "Smart link intent detected in verify-token");
|
|
173
|
+
const resumeWork = resumeSmartLinkIntent({
|
|
174
|
+
intent,
|
|
175
|
+
teamId,
|
|
176
|
+
slackUserId,
|
|
177
|
+
inkeepUserId,
|
|
178
|
+
tenantId,
|
|
179
|
+
slackEnterpriseId: enterpriseId,
|
|
180
|
+
slackUsername: username
|
|
181
|
+
}).catch((error) => logger.error({ error }, "Resume smart link intent failed")).finally(() => flushTraces());
|
|
182
|
+
const waitUntil = await getWaitUntil();
|
|
183
|
+
if (waitUntil) waitUntil(resumeWork);
|
|
184
|
+
else logger.warn({ entryPoint: intent.entryPoint }, "waitUntil not available, resume work may not complete");
|
|
185
|
+
}
|
|
160
186
|
return c.json({
|
|
161
187
|
success: true,
|
|
162
188
|
linkId: slackUserMapping.id,
|
|
@@ -71,6 +71,7 @@ interface AgentConfigSources {
|
|
|
71
71
|
} | null;
|
|
72
72
|
}
|
|
73
73
|
declare function createStatusMessage(email: string, linkedAt: string, dashboardUrl: string, agentConfigs: AgentConfigSources): Readonly<slack_block_builder0.SlackMessageDto>;
|
|
74
|
+
declare function createSmartLinkMessage(linkUrl: string): Readonly<slack_block_builder0.SlackMessageDto>;
|
|
74
75
|
interface ToolApprovalButtonValue {
|
|
75
76
|
toolCallId: string;
|
|
76
77
|
conversationId: string;
|
|
@@ -149,7 +150,6 @@ declare function buildCitationsBlock(citations: Array<{
|
|
|
149
150
|
title?: string;
|
|
150
151
|
url?: string;
|
|
151
152
|
}>): any[];
|
|
152
|
-
declare function createJwtLinkMessage(linkUrl: string, expiresInMinutes: number): Readonly<slack_block_builder0.SlackMessageDto>;
|
|
153
153
|
declare function createCreateInkeepAccountMessage(acceptUrl: string, expiresInMinutes: number): Readonly<slack_block_builder0.SlackMessageDto>;
|
|
154
154
|
//#endregion
|
|
155
|
-
export { AgentConfigSources, ContextBlockParams, FollowUpButtonParams, ToolApprovalButtonValue, ToolApprovalButtonValueSchema, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage,
|
|
155
|
+
export { AgentConfigSources, ContextBlockParams, FollowUpButtonParams, ToolApprovalButtonValue, ToolApprovalButtonValueSchema, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createNotLinkedMessage, createSmartLinkMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage };
|
|
@@ -91,6 +91,9 @@ function createStatusMessage(email, linkedAt, dashboardUrl, agentConfigs) {
|
|
|
91
91
|
if (projectLine) lines.push(projectLine);
|
|
92
92
|
return Message().blocks(Blocks.Section().text(lines.join("\n")), Blocks.Actions().elements(Elements.Button().text(SlackStrings.buttons.openDashboard).url(dashboardUrl).actionId("open_dashboard"))).buildToObject();
|
|
93
93
|
}
|
|
94
|
+
function createSmartLinkMessage(linkUrl) {
|
|
95
|
+
return Message().blocks(Blocks.Section().text("To get started, let's connect your Inkeep account with Slack."), Blocks.Actions().elements(Elements.Button().text("Link Account").url(linkUrl).actionId("smart_link_account").primary()), Blocks.Context().elements("🕐 This only needs to happen once.")).buildToObject();
|
|
96
|
+
}
|
|
94
97
|
const ToolApprovalButtonValueSchema = z.object({
|
|
95
98
|
toolCallId: z.string(),
|
|
96
99
|
conversationId: z.string(),
|
|
@@ -316,12 +319,9 @@ function buildCitationsBlock(citations) {
|
|
|
316
319
|
}
|
|
317
320
|
}];
|
|
318
321
|
}
|
|
319
|
-
function createJwtLinkMessage(linkUrl, expiresInMinutes) {
|
|
320
|
-
return Message().blocks(Blocks.Section().text(`${Md.bold("Link your Inkeep account")}\n\nConnect your Slack and Inkeep accounts to use Inkeep agents.`), Blocks.Actions().elements(Elements.Button().text("Link Account").url(linkUrl).actionId("link_account").primary()), Blocks.Context().elements(`This link expires in ${expiresInMinutes} minutes.`)).buildToObject();
|
|
321
|
-
}
|
|
322
322
|
function createCreateInkeepAccountMessage(acceptUrl, expiresInMinutes) {
|
|
323
323
|
return Message().blocks(Blocks.Section().text(`${Md.bold("Create your Inkeep account")}\n\nYou've been invited to join Inkeep. Create an account to start using Inkeep agents in Slack.`), Blocks.Actions().elements(Elements.Button().text("Create Account").url(acceptUrl).actionId("create_account").primary()), Blocks.Context().elements(`This link expires in ${expiresInMinutes} minutes.`)).buildToObject();
|
|
324
324
|
}
|
|
325
325
|
|
|
326
326
|
//#endregion
|
|
327
|
-
export { ToolApprovalButtonValueSchema, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage,
|
|
327
|
+
export { ToolApprovalButtonValueSchema, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createNotLinkedMessage, createSmartLinkMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage };
|
|
@@ -11,7 +11,7 @@ declare function handleHelpCommand(): Promise<SlackCommandResponse>;
|
|
|
11
11
|
* Similar to @mention behavior in channels
|
|
12
12
|
*/
|
|
13
13
|
declare function handleAgentPickerCommand(payload: SlackCommandPayload, tenantId: string, workspaceConnection?: SlackWorkspaceConnection | null): Promise<SlackCommandResponse>;
|
|
14
|
-
declare function handleQuestionCommand(payload: SlackCommandPayload, question: string, _dashboardUrl: string, tenantId: string): Promise<SlackCommandResponse>;
|
|
14
|
+
declare function handleQuestionCommand(payload: SlackCommandPayload, question: string, _dashboardUrl: string, tenantId: string, botToken: string): Promise<SlackCommandResponse>;
|
|
15
15
|
declare function handleCommand(payload: SlackCommandPayload): Promise<SlackCommandResponse>;
|
|
16
16
|
//#endregion
|
|
17
17
|
export { handleAgentPickerCommand, handleCommand, handleHelpCommand, handleLinkCommand, handleQuestionCommand, handleStatusCommand, handleUnlinkCommand };
|
|
@@ -5,131 +5,32 @@ import { findWorkspaceConnectionByTeamId } from "../nango.js";
|
|
|
5
5
|
import { extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, getChannelAgentConfig, sendResponseUrlMessage } from "../events/utils.js";
|
|
6
6
|
import { resolveEffectiveAgent } from "../agent-resolution.js";
|
|
7
7
|
import { SlackStrings } from "../../i18n/strings.js";
|
|
8
|
-
import { createAlreadyLinkedMessage, createContextBlock,
|
|
8
|
+
import { createAlreadyLinkedMessage, createContextBlock, createErrorMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage } from "../blocks/index.js";
|
|
9
9
|
import { getSlackClient } from "../client.js";
|
|
10
|
+
import { buildLinkPromptMessage, resolveUnlinkedUserAction } from "../link-prompt.js";
|
|
10
11
|
import { buildAgentSelectorModal } from "../modals.js";
|
|
11
|
-
import {
|
|
12
|
+
import { deleteWorkAppSlackUserMapping, findWorkAppSlackUserMapping, findWorkAppSlackUserMappingBySlackUser, flushTraces, getInProcessFetch, getWaitUntil, signSlackUserToken } from "@inkeep/agents-core";
|
|
12
13
|
|
|
13
14
|
//#region src/slack/services/commands/index.ts
|
|
14
15
|
const DEFAULT_CLIENT_ID = "work-apps-slack";
|
|
15
|
-
const LINK_CODE_TTL_MINUTES = 10;
|
|
16
16
|
const logger = getLogger("slack-commands");
|
|
17
|
-
/**
|
|
18
|
-
* Create an invitation for a Slack user who doesn't have an Inkeep account yet.
|
|
19
|
-
* Returns the invitation ID and email so the caller can direct the user
|
|
20
|
-
* to the accept-invitation page.
|
|
21
|
-
*
|
|
22
|
-
* Returns null if:
|
|
23
|
-
* - Workspace doesn't have shouldAllowJoinFromWorkspace enabled
|
|
24
|
-
* - User already has an Inkeep account (JWT link flow is sufficient)
|
|
25
|
-
* - Service account is not configured
|
|
26
|
-
*/
|
|
27
|
-
async function tryAutoInvite(payload, tenantId, botToken) {
|
|
28
|
-
try {
|
|
29
|
-
if (!(await findWorkAppSlackWorkspaceByTeamId(runDbClient_default)(tenantId, payload.teamId))?.shouldAllowJoinFromWorkspace) return null;
|
|
30
|
-
const slackClient = getSlackClient(botToken);
|
|
31
|
-
let userEmail;
|
|
32
|
-
try {
|
|
33
|
-
userEmail = (await slackClient.users.info({ user: payload.userId })).user?.profile?.email;
|
|
34
|
-
} catch (error) {
|
|
35
|
-
logger.warn({
|
|
36
|
-
error,
|
|
37
|
-
userId: payload.userId
|
|
38
|
-
}, "Failed to get user info from Slack");
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
if (!userEmail) {
|
|
42
|
-
logger.warn({ userId: payload.userId }, "No email found in Slack user profile");
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
if (await getOrganizationMemberByEmail(runDbClient_default)(tenantId, userEmail)) {
|
|
46
|
-
logger.debug({
|
|
47
|
-
userId: payload.userId,
|
|
48
|
-
email: userEmail
|
|
49
|
-
}, "User already has Inkeep account, skipping auto-invite");
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
const existingInvitation = (await getPendingInvitationsByEmail(runDbClient_default)(userEmail)).find((inv) => inv.organizationId === tenantId);
|
|
53
|
-
if (existingInvitation) {
|
|
54
|
-
logger.info({
|
|
55
|
-
userId: payload.userId,
|
|
56
|
-
tenantId,
|
|
57
|
-
invitationId: existingInvitation.id,
|
|
58
|
-
email: userEmail
|
|
59
|
-
}, "Reusing existing pending invitation for Slack user");
|
|
60
|
-
return {
|
|
61
|
-
invitationId: existingInvitation.id,
|
|
62
|
-
email: userEmail
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
const invitation = await createInvitationInDb(runDbClient_default)({
|
|
66
|
-
organizationId: tenantId,
|
|
67
|
-
email: userEmail
|
|
68
|
-
});
|
|
69
|
-
logger.info({
|
|
70
|
-
userId: payload.userId,
|
|
71
|
-
tenantId,
|
|
72
|
-
invitationId: invitation.id,
|
|
73
|
-
email: userEmail
|
|
74
|
-
}, "Invitation created for Slack user without Inkeep account");
|
|
75
|
-
return {
|
|
76
|
-
invitationId: invitation.id,
|
|
77
|
-
email: userEmail
|
|
78
|
-
};
|
|
79
|
-
} catch (error) {
|
|
80
|
-
logger.warn({
|
|
81
|
-
error,
|
|
82
|
-
userId: payload.userId,
|
|
83
|
-
tenantId
|
|
84
|
-
}, "Auto-invite attempt failed");
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
17
|
async function handleLinkCommand(payload, dashboardUrl, tenantId, botToken) {
|
|
89
18
|
const existingLink = await findWorkAppSlackUserMapping(runDbClient_default)(tenantId, payload.userId, payload.teamId, DEFAULT_CLIENT_ID);
|
|
90
19
|
if (existingLink) return {
|
|
91
20
|
response_type: "ephemeral",
|
|
92
21
|
...createAlreadyLinkedMessage(existingLink.slackEmail || existingLink.slackUsername || "Unknown", existingLink.linkedAt, dashboardUrl)
|
|
93
22
|
};
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const linkToken = await signSlackLinkToken({
|
|
23
|
+
try {
|
|
24
|
+
return {
|
|
25
|
+
response_type: "ephemeral",
|
|
26
|
+
...buildLinkPromptMessage(await resolveUnlinkedUserAction({
|
|
99
27
|
tenantId,
|
|
100
|
-
|
|
28
|
+
teamId: payload.teamId,
|
|
101
29
|
slackUserId: payload.userId,
|
|
30
|
+
botToken: botToken || "",
|
|
102
31
|
slackEnterpriseId: payload.enterpriseId,
|
|
103
32
|
slackUsername: payload.userName
|
|
104
|
-
})
|
|
105
|
-
const linkReturnUrl = `/link?token=${encodeURIComponent(linkToken)}`;
|
|
106
|
-
const acceptUrl = `${manageUiUrl}/accept-invitation/${autoInvite.invitationId}?email=${encodeURIComponent(autoInvite.email)}&returnUrl=${encodeURIComponent(linkReturnUrl)}`;
|
|
107
|
-
logger.info({
|
|
108
|
-
invitationId: autoInvite.invitationId,
|
|
109
|
-
email: autoInvite.email
|
|
110
|
-
}, "Directing new user to accept-invitation page with link returnUrl");
|
|
111
|
-
return {
|
|
112
|
-
response_type: "ephemeral",
|
|
113
|
-
...createCreateInkeepAccountMessage(acceptUrl, LINK_CODE_TTL_MINUTES)
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
try {
|
|
118
|
-
const linkToken = await signSlackLinkToken({
|
|
119
|
-
tenantId,
|
|
120
|
-
slackTeamId: payload.teamId,
|
|
121
|
-
slackUserId: payload.userId,
|
|
122
|
-
slackEnterpriseId: payload.enterpriseId,
|
|
123
|
-
slackUsername: payload.userName
|
|
124
|
-
});
|
|
125
|
-
const linkUrl = `${env.INKEEP_AGENTS_MANAGE_UI_URL || "http://localhost:3000"}/link?token=${encodeURIComponent(linkToken)}`;
|
|
126
|
-
logger.info({
|
|
127
|
-
slackUserId: payload.userId,
|
|
128
|
-
tenantId
|
|
129
|
-
}, "Generated JWT link token");
|
|
130
|
-
return {
|
|
131
|
-
response_type: "ephemeral",
|
|
132
|
-
...createJwtLinkMessage(linkUrl, LINK_CODE_TTL_MINUTES)
|
|
33
|
+
}))
|
|
133
34
|
};
|
|
134
35
|
} catch (error) {
|
|
135
36
|
logger.error({
|
|
@@ -278,23 +179,27 @@ async function handleAgentPickerCommand(payload, tenantId, workspaceConnection)
|
|
|
278
179
|
};
|
|
279
180
|
}
|
|
280
181
|
}
|
|
281
|
-
async function generateLinkCodeWithIntent(payload, tenantId) {
|
|
182
|
+
async function generateLinkCodeWithIntent(payload, tenantId, botToken, intent) {
|
|
282
183
|
try {
|
|
283
|
-
const
|
|
184
|
+
const linkResult = await resolveUnlinkedUserAction({
|
|
284
185
|
tenantId,
|
|
285
|
-
|
|
186
|
+
teamId: payload.teamId,
|
|
286
187
|
slackUserId: payload.userId,
|
|
188
|
+
botToken,
|
|
287
189
|
slackEnterpriseId: payload.enterpriseId,
|
|
288
|
-
slackUsername: payload.userName
|
|
190
|
+
slackUsername: payload.userName,
|
|
191
|
+
intent
|
|
289
192
|
});
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
193
|
+
if (!!intent) logger.info({
|
|
194
|
+
event: "smart_link_intent_captured",
|
|
195
|
+
entryPoint: intent.entryPoint,
|
|
196
|
+
linkType: linkResult.type,
|
|
197
|
+
questionLength: intent.question.length,
|
|
198
|
+
channelId: payload.channelId
|
|
199
|
+
}, "Smart link intent captured");
|
|
295
200
|
return {
|
|
296
201
|
response_type: "ephemeral",
|
|
297
|
-
...
|
|
202
|
+
...buildLinkPromptMessage(linkResult)
|
|
298
203
|
};
|
|
299
204
|
} catch (error) {
|
|
300
205
|
logger.error({
|
|
@@ -308,9 +213,14 @@ async function generateLinkCodeWithIntent(payload, tenantId) {
|
|
|
308
213
|
};
|
|
309
214
|
}
|
|
310
215
|
}
|
|
311
|
-
async function handleQuestionCommand(payload, question, _dashboardUrl, tenantId) {
|
|
216
|
+
async function handleQuestionCommand(payload, question, _dashboardUrl, tenantId, botToken) {
|
|
312
217
|
const existingLink = await findWorkAppSlackUserMappingBySlackUser(runDbClient_default)(payload.userId, payload.teamId, DEFAULT_CLIENT_ID);
|
|
313
|
-
if (!existingLink) return generateLinkCodeWithIntent(payload, tenantId
|
|
218
|
+
if (!existingLink) return generateLinkCodeWithIntent(payload, tenantId, botToken, {
|
|
219
|
+
entryPoint: "question_command",
|
|
220
|
+
question: question.slice(0, 2e3),
|
|
221
|
+
channelId: payload.channelId,
|
|
222
|
+
responseUrl: payload.responseUrl
|
|
223
|
+
});
|
|
314
224
|
const userTenantId = existingLink.tenantId;
|
|
315
225
|
const resolvedAgent = await resolveEffectiveAgent({
|
|
316
226
|
tenantId: userTenantId,
|
|
@@ -464,7 +374,7 @@ async function handleCommand(payload) {
|
|
|
464
374
|
case "disconnect": return handleUnlinkCommand(payload, tenantId);
|
|
465
375
|
case "help": return handleHelpCommand();
|
|
466
376
|
case "": return handleAgentPickerCommand(payload, tenantId, workspaceConnection);
|
|
467
|
-
default: return handleQuestionCommand(payload, text, dashboardUrl, tenantId);
|
|
377
|
+
default: return handleQuestionCommand(payload, text, dashboardUrl, tenantId, workspaceConnection.botToken);
|
|
468
378
|
}
|
|
469
379
|
}
|
|
470
380
|
|
|
@@ -5,25 +5,12 @@ import { checkIfBotThread, classifyError, findCachedUserMapping, formatChannelCo
|
|
|
5
5
|
import { resolveEffectiveAgent } from "../agent-resolution.js";
|
|
6
6
|
import { SlackStrings } from "../../i18n/strings.js";
|
|
7
7
|
import { getSlackChannelInfo, getSlackClient, getSlackUserInfo, postMessageInThread } from "../client.js";
|
|
8
|
+
import { buildLinkPromptMessage, resolveUnlinkedUserAction } from "../link-prompt.js";
|
|
8
9
|
import { SLACK_SPAN_KEYS, SLACK_SPAN_NAMES, setSpanWithError, tracer } from "../../tracer.js";
|
|
9
10
|
import { streamAgentResponse } from "./streaming.js";
|
|
10
11
|
import { signSlackUserToken } from "@inkeep/agents-core";
|
|
11
12
|
|
|
12
13
|
//#region src/slack/services/events/app-mention.ts
|
|
13
|
-
/**
|
|
14
|
-
* Handler for Slack @mention events
|
|
15
|
-
*
|
|
16
|
-
* Flow:
|
|
17
|
-
* 1. Resolve workspace connection (single lookup, cached)
|
|
18
|
-
* 2. Parallel: resolve agent config + check user link
|
|
19
|
-
* 3. If no agent configured → prompt to set up in dashboard
|
|
20
|
-
* 4. If not linked → prompt to link account
|
|
21
|
-
* 5. Handle based on context:
|
|
22
|
-
* - Channel + no query → Show usage hint
|
|
23
|
-
* - Channel + query → Execute agent with streaming response
|
|
24
|
-
* - Thread + no query → Auto-execute agent with thread context as query
|
|
25
|
-
* - Thread + query → Execute agent with thread context included
|
|
26
|
-
*/
|
|
27
14
|
const logger = getLogger("slack-app-mention");
|
|
28
15
|
/**
|
|
29
16
|
* Main handler for @mention events in Slack
|
|
@@ -123,11 +110,36 @@ async function handleAppMention(params) {
|
|
|
123
110
|
teamId,
|
|
124
111
|
channel
|
|
125
112
|
}, "User not linked — prompting account link");
|
|
113
|
+
const intent = {
|
|
114
|
+
entryPoint: "mention",
|
|
115
|
+
question: text.slice(0, 2e3),
|
|
116
|
+
channelId: channel,
|
|
117
|
+
threadTs: isInThread ? threadTs : void 0,
|
|
118
|
+
messageTs,
|
|
119
|
+
agentId: agentConfig.agentId,
|
|
120
|
+
projectId: agentConfig.projectId
|
|
121
|
+
};
|
|
122
|
+
const linkResult = await resolveUnlinkedUserAction({
|
|
123
|
+
tenantId,
|
|
124
|
+
teamId,
|
|
125
|
+
slackUserId,
|
|
126
|
+
botToken,
|
|
127
|
+
intent
|
|
128
|
+
});
|
|
129
|
+
const message = buildLinkPromptMessage(linkResult);
|
|
130
|
+
logger.info({
|
|
131
|
+
event: "smart_link_intent_captured",
|
|
132
|
+
entryPoint: "mention",
|
|
133
|
+
linkType: linkResult.type,
|
|
134
|
+
questionLength: intent.question.length,
|
|
135
|
+
channelId: channel
|
|
136
|
+
}, "Smart link intent captured");
|
|
126
137
|
await slackClient.chat.postEphemeral({
|
|
127
138
|
channel,
|
|
128
139
|
user: slackUserId,
|
|
129
140
|
thread_ts: isInThread ? threadTs : void 0,
|
|
130
|
-
text: "
|
|
141
|
+
text: "To get started, let's connect your Inkeep account with Slack.",
|
|
142
|
+
blocks: message.blocks
|
|
131
143
|
});
|
|
132
144
|
span.end();
|
|
133
145
|
return;
|
|
@@ -34,17 +34,19 @@ async function findCachedUserMapping(tenantId, slackUserId, teamId, clientId = "
|
|
|
34
34
|
const cached = userMappingCache.get(cacheKey);
|
|
35
35
|
if (cached && cached.expiresAt > Date.now()) return cached.mapping;
|
|
36
36
|
const mapping = await findWorkAppSlackUserMapping(runDbClient_default)(tenantId, slackUserId, teamId, clientId);
|
|
37
|
-
if (
|
|
38
|
-
evictExpiredEntries();
|
|
37
|
+
if (mapping) {
|
|
39
38
|
if (userMappingCache.size >= USER_MAPPING_CACHE_MAX_SIZE) {
|
|
40
|
-
|
|
41
|
-
if (
|
|
39
|
+
evictExpiredEntries();
|
|
40
|
+
if (userMappingCache.size >= USER_MAPPING_CACHE_MAX_SIZE) {
|
|
41
|
+
const oldestKey = userMappingCache.keys().next().value;
|
|
42
|
+
if (oldestKey) userMappingCache.delete(oldestKey);
|
|
43
|
+
}
|
|
42
44
|
}
|
|
45
|
+
userMappingCache.set(cacheKey, {
|
|
46
|
+
mapping,
|
|
47
|
+
expiresAt: Date.now() + USER_MAPPING_CACHE_TTL_MS
|
|
48
|
+
});
|
|
43
49
|
}
|
|
44
|
-
userMappingCache.set(cacheKey, {
|
|
45
|
-
mapping,
|
|
46
|
-
expiresAt: Date.now() + USER_MAPPING_CACHE_TTL_MS
|
|
47
|
-
});
|
|
48
50
|
return mapping;
|
|
49
51
|
}
|
|
50
52
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AgentResolutionParams, ResolvedAgentConfig, getAgentConfigSources, resolveEffectiveAgent } from "./agent-resolution.js";
|
|
2
|
-
import { AgentConfigSources, ContextBlockParams, FollowUpButtonParams, ToolApprovalButtonValue, ToolApprovalButtonValueSchema, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage,
|
|
2
|
+
import { AgentConfigSources, ContextBlockParams, FollowUpButtonParams, ToolApprovalButtonValue, ToolApprovalButtonValueSchema, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createNotLinkedMessage, createSmartLinkMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage } from "./blocks/index.js";
|
|
3
3
|
import { checkUserIsChannelMember, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackTeamInfo, getSlackUserInfo, postMessage, postMessageInThread, revokeSlackToken } from "./client.js";
|
|
4
4
|
import { DefaultAgentConfig, SlackWorkspaceConnection, WorkspaceInstallData, clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createConnectSession, deleteWorkspaceInstallation, findWorkspaceConnectionByTeamId, getConnectionAccessToken, getSlackIntegrationId, getSlackNango, getWorkspaceDefaultAgentFromNango, listWorkspaceInstallations, setWorkspaceDefaultAgent, storeWorkspaceInstallation, updateConnectionMetadata } from "./nango.js";
|
|
5
5
|
import { SlackCommandPayload, SlackCommandResponse } from "./types.js";
|
|
@@ -13,4 +13,4 @@ import { StreamResult, streamAgentResponse } from "./events/streaming.js";
|
|
|
13
13
|
import "./events/index.js";
|
|
14
14
|
import { parseSlackCommandBody, parseSlackEventBody, verifySlackRequest } from "./security.js";
|
|
15
15
|
import { getBotTokenForTeam, setBotTokenForTeam } from "./workspace-tokens.js";
|
|
16
|
-
export { AgentConfigSources, AgentOption, AgentResolutionParams, BuildAgentSelectorModalParams, BuildMessageShortcutModalParams, ContextBlockParams, DefaultAgentConfig, FollowUpButtonParams, FollowUpModalMetadata, InlineSelectorMetadata, ModalMetadata, ResolvedAgentConfig, SlackCommandPayload, SlackCommandResponse, SlackErrorType, SlackWorkspaceConnection, StreamResult, ToolApprovalButtonValue, ToolApprovalButtonValueSchema, WorkspaceInstallData, buildAgentSelectorModal, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildFollowUpModal, buildMessageShortcutModal, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, checkIfBotThread, checkUserIsChannelMember, classifyError, clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createAlreadyLinkedMessage, createConnectSession, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage,
|
|
16
|
+
export { AgentConfigSources, AgentOption, AgentResolutionParams, BuildAgentSelectorModalParams, BuildMessageShortcutModalParams, ContextBlockParams, DefaultAgentConfig, FollowUpButtonParams, FollowUpModalMetadata, InlineSelectorMetadata, ModalMetadata, ResolvedAgentConfig, SlackCommandPayload, SlackCommandResponse, SlackErrorType, SlackWorkspaceConnection, StreamResult, ToolApprovalButtonValue, ToolApprovalButtonValueSchema, WorkspaceInstallData, buildAgentSelectorModal, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildFollowUpModal, buildMessageShortcutModal, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, checkIfBotThread, checkUserIsChannelMember, classifyError, clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createAlreadyLinkedMessage, createConnectSession, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createNotLinkedMessage, createSmartLinkMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage, deleteWorkspaceInstallation, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, findWorkspaceConnectionByTeamId, generateSlackConversationId, getAgentConfigSources, getBotTokenForTeam, getChannelAgentConfig, getConnectionAccessToken, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackIntegrationId, getSlackNango, getSlackTeamInfo, getSlackUserInfo, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, getWorkspaceDefaultAgentFromNango, handleAgentPickerCommand, handleAppMention, handleCommand, handleFollowUpSubmission, handleHelpCommand, handleLinkCommand, handleMessageShortcut, handleModalSubmission, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleQuestionCommand, handleStatusCommand, handleToolApproval, handleUnlinkCommand, listWorkspaceInstallations, markdownToMrkdwn, parseSlackCommandBody, parseSlackEventBody, postMessage, postMessageInThread, resolveEffectiveAgent, revokeSlackToken, sendResponseUrlMessage, setBotTokenForTeam, setWorkspaceDefaultAgent, storeWorkspaceInstallation, streamAgentResponse, updateConnectionMetadata, verifySlackRequest };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createConnectSession, deleteWorkspaceInstallation, findWorkspaceConnectionByTeamId, getConnectionAccessToken, getSlackIntegrationId, getSlackNango, getWorkspaceDefaultAgentFromNango, listWorkspaceInstallations, setWorkspaceDefaultAgent, storeWorkspaceInstallation, updateConnectionMetadata } from "./nango.js";
|
|
2
2
|
import { SlackErrorType, checkIfBotThread, classifyError, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, generateSlackConversationId, getChannelAgentConfig, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, markdownToMrkdwn, sendResponseUrlMessage } from "./events/utils.js";
|
|
3
3
|
import { getAgentConfigSources, resolveEffectiveAgent } from "./agent-resolution.js";
|
|
4
|
-
import { ToolApprovalButtonValueSchema, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage,
|
|
4
|
+
import { ToolApprovalButtonValueSchema, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createNotLinkedMessage, createSmartLinkMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage } from "./blocks/index.js";
|
|
5
5
|
import { checkUserIsChannelMember, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackTeamInfo, getSlackUserInfo, postMessage, postMessageInThread, revokeSlackToken } from "./client.js";
|
|
6
6
|
import { buildAgentSelectorModal, buildFollowUpModal, buildMessageShortcutModal } from "./modals.js";
|
|
7
7
|
import { handleAgentPickerCommand, handleCommand, handleHelpCommand, handleLinkCommand, handleQuestionCommand, handleStatusCommand, handleUnlinkCommand } from "./commands/index.js";
|
|
@@ -13,4 +13,4 @@ import "./events/index.js";
|
|
|
13
13
|
import { parseSlackCommandBody, parseSlackEventBody, verifySlackRequest } from "./security.js";
|
|
14
14
|
import { getBotTokenForTeam, setBotTokenForTeam } from "./workspace-tokens.js";
|
|
15
15
|
|
|
16
|
-
export { SlackErrorType, ToolApprovalButtonValueSchema, buildAgentSelectorModal, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildFollowUpModal, buildMessageShortcutModal, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, checkIfBotThread, checkUserIsChannelMember, classifyError, clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createAlreadyLinkedMessage, createConnectSession, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage,
|
|
16
|
+
export { SlackErrorType, ToolApprovalButtonValueSchema, buildAgentSelectorModal, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildFollowUpModal, buildMessageShortcutModal, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, checkIfBotThread, checkUserIsChannelMember, classifyError, clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createAlreadyLinkedMessage, createConnectSession, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createNotLinkedMessage, createSmartLinkMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage, deleteWorkspaceInstallation, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, findWorkspaceConnectionByTeamId, generateSlackConversationId, getAgentConfigSources, getBotTokenForTeam, getChannelAgentConfig, getConnectionAccessToken, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackIntegrationId, getSlackNango, getSlackTeamInfo, getSlackUserInfo, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, getWorkspaceDefaultAgentFromNango, handleAgentPickerCommand, handleAppMention, handleCommand, handleFollowUpSubmission, handleHelpCommand, handleLinkCommand, handleMessageShortcut, handleModalSubmission, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleQuestionCommand, handleStatusCommand, handleToolApproval, handleUnlinkCommand, listWorkspaceInstallations, markdownToMrkdwn, parseSlackCommandBody, parseSlackEventBody, postMessage, postMessageInThread, resolveEffectiveAgent, revokeSlackToken, sendResponseUrlMessage, setBotTokenForTeam, setWorkspaceDefaultAgent, storeWorkspaceInstallation, streamAgentResponse, updateConnectionMetadata, verifySlackRequest };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { SlackLinkIntent } from "@inkeep/agents-core";
|
|
2
|
+
import * as slack_block_builder0 from "slack-block-builder";
|
|
3
|
+
|
|
4
|
+
//#region src/slack/services/link-prompt.d.ts
|
|
5
|
+
type LinkPromptResult = {
|
|
6
|
+
type: 'auto_invite';
|
|
7
|
+
url: string;
|
|
8
|
+
email: string;
|
|
9
|
+
expiresInMinutes: number;
|
|
10
|
+
} | {
|
|
11
|
+
type: 'jwt_link';
|
|
12
|
+
url: string;
|
|
13
|
+
expiresInMinutes: number;
|
|
14
|
+
};
|
|
15
|
+
interface ResolveLinkActionParams {
|
|
16
|
+
tenantId: string;
|
|
17
|
+
teamId: string;
|
|
18
|
+
slackUserId: string;
|
|
19
|
+
botToken: string;
|
|
20
|
+
slackEnterpriseId?: string;
|
|
21
|
+
slackUsername?: string;
|
|
22
|
+
intent?: SlackLinkIntent;
|
|
23
|
+
}
|
|
24
|
+
declare function resolveUnlinkedUserAction(params: ResolveLinkActionParams): Promise<LinkPromptResult>;
|
|
25
|
+
declare function buildLinkPromptMessage(result: LinkPromptResult): Readonly<slack_block_builder0.SlackMessageDto>;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { LinkPromptResult, ResolveLinkActionParams, buildLinkPromptMessage, resolveUnlinkedUserAction };
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { env } from "../../env.js";
|
|
2
|
+
import { getLogger } from "../../logger.js";
|
|
3
|
+
import runDbClient_default from "../../db/runDbClient.js";
|
|
4
|
+
import { createCreateInkeepAccountMessage, createSmartLinkMessage } from "./blocks/index.js";
|
|
5
|
+
import { getSlackClient } from "./client.js";
|
|
6
|
+
import { createInvitationInDb, findWorkAppSlackWorkspaceByTeamId, getOrganizationMemberByEmail, getPendingInvitationsByEmail, signSlackLinkToken } from "@inkeep/agents-core";
|
|
7
|
+
|
|
8
|
+
//#region src/slack/services/link-prompt.ts
|
|
9
|
+
const logger = getLogger("slack-link-prompt");
|
|
10
|
+
const LINK_CODE_TTL_MINUTES = 10;
|
|
11
|
+
async function resolveUnlinkedUserAction(params) {
|
|
12
|
+
const { tenantId, teamId, slackUserId, botToken, slackEnterpriseId, slackUsername, intent } = params;
|
|
13
|
+
const manageUiUrl = env.INKEEP_AGENTS_MANAGE_UI_URL || "http://localhost:3000";
|
|
14
|
+
const autoInvite = await tryAutoInvite({
|
|
15
|
+
tenantId,
|
|
16
|
+
teamId,
|
|
17
|
+
slackUserId,
|
|
18
|
+
botToken
|
|
19
|
+
});
|
|
20
|
+
if (autoInvite) {
|
|
21
|
+
const linkToken$1 = await signSlackLinkToken({
|
|
22
|
+
tenantId,
|
|
23
|
+
slackTeamId: teamId,
|
|
24
|
+
slackUserId,
|
|
25
|
+
slackEnterpriseId,
|
|
26
|
+
slackUsername,
|
|
27
|
+
intent
|
|
28
|
+
});
|
|
29
|
+
const authMethod = autoInvite.authMethod;
|
|
30
|
+
const linkReturnUrl = `/link?token=${encodeURIComponent(linkToken$1)}`;
|
|
31
|
+
const acceptUrl = authMethod === "email-password" ? `${manageUiUrl}/accept-invitation/${autoInvite.invitationId}?email=${encodeURIComponent(autoInvite.email)}&returnUrl=${encodeURIComponent(linkReturnUrl)}` : `${manageUiUrl}/login?invitation=${encodeURIComponent(autoInvite.invitationId)}&returnUrl=${encodeURIComponent(linkReturnUrl)}&email=${encodeURIComponent(autoInvite.email)}&authMethod=${encodeURIComponent(authMethod)}`;
|
|
32
|
+
logger.info({
|
|
33
|
+
invitationId: autoInvite.invitationId,
|
|
34
|
+
email: autoInvite.email,
|
|
35
|
+
hasIntent: !!intent
|
|
36
|
+
}, "Directing unlinked user to accept-invitation page");
|
|
37
|
+
return {
|
|
38
|
+
type: "auto_invite",
|
|
39
|
+
url: acceptUrl,
|
|
40
|
+
email: autoInvite.email,
|
|
41
|
+
expiresInMinutes: LINK_CODE_TTL_MINUTES
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const linkToken = await signSlackLinkToken({
|
|
45
|
+
tenantId,
|
|
46
|
+
slackTeamId: teamId,
|
|
47
|
+
slackUserId,
|
|
48
|
+
slackEnterpriseId,
|
|
49
|
+
slackUsername,
|
|
50
|
+
intent
|
|
51
|
+
});
|
|
52
|
+
const linkUrl = `${manageUiUrl}/link?token=${encodeURIComponent(linkToken)}`;
|
|
53
|
+
logger.info({
|
|
54
|
+
slackUserId,
|
|
55
|
+
tenantId,
|
|
56
|
+
hasIntent: !!intent
|
|
57
|
+
}, "Generated JWT link token for unlinked user");
|
|
58
|
+
return {
|
|
59
|
+
type: "jwt_link",
|
|
60
|
+
url: linkUrl,
|
|
61
|
+
expiresInMinutes: LINK_CODE_TTL_MINUTES
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function buildLinkPromptMessage(result) {
|
|
65
|
+
if (result.type === "auto_invite") return createCreateInkeepAccountMessage(result.url, result.expiresInMinutes);
|
|
66
|
+
return createSmartLinkMessage(result.url);
|
|
67
|
+
}
|
|
68
|
+
async function tryAutoInvite(params) {
|
|
69
|
+
const { tenantId, teamId, slackUserId, botToken } = params;
|
|
70
|
+
if (!botToken) return null;
|
|
71
|
+
try {
|
|
72
|
+
if (!(await findWorkAppSlackWorkspaceByTeamId(runDbClient_default)(tenantId, teamId))?.shouldAllowJoinFromWorkspace) {
|
|
73
|
+
logger.warn({
|
|
74
|
+
userId: slackUserId,
|
|
75
|
+
tenantId,
|
|
76
|
+
teamId
|
|
77
|
+
}, "Workspace should not allow join from workspace");
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
const slackClient = getSlackClient(botToken);
|
|
81
|
+
let userEmail;
|
|
82
|
+
try {
|
|
83
|
+
userEmail = (await slackClient.users.info({ user: slackUserId })).user?.profile?.email;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
logger.warn({
|
|
86
|
+
error,
|
|
87
|
+
userId: slackUserId
|
|
88
|
+
}, "Failed to get user info from Slack");
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
if (!userEmail) {
|
|
92
|
+
logger.warn({ userId: slackUserId }, "No email found in Slack user profile");
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
if (await getOrganizationMemberByEmail(runDbClient_default)(tenantId, userEmail)) {
|
|
96
|
+
logger.debug({
|
|
97
|
+
userId: slackUserId,
|
|
98
|
+
email: userEmail
|
|
99
|
+
}, "User already has Inkeep account, skipping auto-invite");
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
const existingInvitation = (await getPendingInvitationsByEmail(runDbClient_default)(userEmail)).find((inv) => inv.organizationId === tenantId);
|
|
103
|
+
if (existingInvitation) {
|
|
104
|
+
logger.info({
|
|
105
|
+
userId: slackUserId,
|
|
106
|
+
tenantId,
|
|
107
|
+
invitationId: existingInvitation.id,
|
|
108
|
+
email: userEmail
|
|
109
|
+
}, "Reusing existing pending invitation for Slack user");
|
|
110
|
+
return {
|
|
111
|
+
invitationId: existingInvitation.id,
|
|
112
|
+
email: userEmail,
|
|
113
|
+
authMethod: existingInvitation.authMethod ?? "email-password"
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const invitation = await createInvitationInDb(runDbClient_default)({
|
|
117
|
+
organizationId: tenantId,
|
|
118
|
+
email: userEmail
|
|
119
|
+
});
|
|
120
|
+
logger.info({
|
|
121
|
+
userId: slackUserId,
|
|
122
|
+
tenantId,
|
|
123
|
+
invitationId: invitation.id,
|
|
124
|
+
email: userEmail
|
|
125
|
+
}, "Invitation created for Slack user without Inkeep account");
|
|
126
|
+
return {
|
|
127
|
+
invitationId: invitation.id,
|
|
128
|
+
email: userEmail,
|
|
129
|
+
authMethod: invitation.authMethod
|
|
130
|
+
};
|
|
131
|
+
} catch (error) {
|
|
132
|
+
logger.warn({
|
|
133
|
+
error,
|
|
134
|
+
userId: slackUserId,
|
|
135
|
+
tenantId
|
|
136
|
+
}, "Auto-invite attempt failed");
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
//#endregion
|
|
142
|
+
export { buildLinkPromptMessage, resolveUnlinkedUserAction };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { SlackLinkIntent } from "@inkeep/agents-core";
|
|
2
|
+
|
|
3
|
+
//#region src/slack/services/resume-intent.d.ts
|
|
4
|
+
interface ResumeSmartLinkIntentParams {
|
|
5
|
+
intent: SlackLinkIntent;
|
|
6
|
+
teamId: string;
|
|
7
|
+
slackUserId: string;
|
|
8
|
+
inkeepUserId: string;
|
|
9
|
+
tenantId: string;
|
|
10
|
+
slackEnterpriseId?: string;
|
|
11
|
+
slackUsername?: string;
|
|
12
|
+
}
|
|
13
|
+
declare function resumeSmartLinkIntent(params: ResumeSmartLinkIntentParams): Promise<void>;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { ResumeSmartLinkIntentParams, resumeSmartLinkIntent };
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { env } from "../../env.js";
|
|
2
|
+
import { getLogger } from "../../logger.js";
|
|
3
|
+
import { findWorkspaceConnectionByTeamId } from "./nango.js";
|
|
4
|
+
import { generateSlackConversationId, sendResponseUrlMessage } from "./events/utils.js";
|
|
5
|
+
import { resolveEffectiveAgent } from "./agent-resolution.js";
|
|
6
|
+
import { createContextBlock } from "./blocks/index.js";
|
|
7
|
+
import { getSlackClient } from "./client.js";
|
|
8
|
+
import { streamAgentResponse } from "./events/streaming.js";
|
|
9
|
+
import { signSlackUserToken } from "@inkeep/agents-core";
|
|
10
|
+
|
|
11
|
+
//#region src/slack/services/resume-intent.ts
|
|
12
|
+
const logger = getLogger("slack-resume-intent");
|
|
13
|
+
function getChannelAuthClaims(agentConfig, channelId) {
|
|
14
|
+
return {
|
|
15
|
+
slackAuthorized: agentConfig?.grantAccessToMembers ?? false,
|
|
16
|
+
slackAuthSource: agentConfig?.source && agentConfig.source !== "none" ? agentConfig.source : void 0,
|
|
17
|
+
slackChannelId: channelId,
|
|
18
|
+
slackAuthorizedProjectId: agentConfig?.projectId
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
async function resumeSmartLinkIntent(params) {
|
|
22
|
+
const { intent, teamId, slackUserId, inkeepUserId, tenantId, slackEnterpriseId } = params;
|
|
23
|
+
const startTime = Date.now();
|
|
24
|
+
try {
|
|
25
|
+
const botToken = (await findWorkspaceConnectionByTeamId(teamId))?.botToken;
|
|
26
|
+
if (!botToken) {
|
|
27
|
+
logger.error({
|
|
28
|
+
teamId,
|
|
29
|
+
entryPoint: intent.entryPoint
|
|
30
|
+
}, "No bot token available for resume");
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const slackClient = getSlackClient(botToken);
|
|
34
|
+
const tokenCtx = {
|
|
35
|
+
inkeepUserId,
|
|
36
|
+
tenantId,
|
|
37
|
+
slackTeamId: teamId,
|
|
38
|
+
slackUserId,
|
|
39
|
+
slackEnterpriseId
|
|
40
|
+
};
|
|
41
|
+
let resolvedAgentId;
|
|
42
|
+
let deliveryMethod;
|
|
43
|
+
switch (intent.entryPoint) {
|
|
44
|
+
case "mention":
|
|
45
|
+
resolvedAgentId = intent.agentId;
|
|
46
|
+
deliveryMethod = "streaming";
|
|
47
|
+
await resumeMention(intent, slackClient, tokenCtx, teamId, tenantId);
|
|
48
|
+
break;
|
|
49
|
+
case "question_command":
|
|
50
|
+
deliveryMethod = intent.responseUrl ? "response_url" : "bot_token";
|
|
51
|
+
await resumeCommand(intent, slackClient, tokenCtx, teamId, tenantId);
|
|
52
|
+
break;
|
|
53
|
+
case "run_command":
|
|
54
|
+
deliveryMethod = intent.responseUrl ? "response_url" : "bot_token";
|
|
55
|
+
await resumeRunCommand(intent, slackClient, tokenCtx, teamId, tenantId);
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
const durationMs = Date.now() - startTime;
|
|
59
|
+
logger.info({
|
|
60
|
+
event: "smart_link_intent_resumed",
|
|
61
|
+
entryPoint: intent.entryPoint,
|
|
62
|
+
channelId: intent.channelId,
|
|
63
|
+
agentId: resolvedAgentId || intent.agentId,
|
|
64
|
+
deliveryMethod,
|
|
65
|
+
durationMs
|
|
66
|
+
}, "Smart link intent resumed");
|
|
67
|
+
} catch (error) {
|
|
68
|
+
logger.error({
|
|
69
|
+
event: "smart_link_intent_failed",
|
|
70
|
+
entryPoint: intent.entryPoint,
|
|
71
|
+
error: error instanceof Error ? error.message : String(error)
|
|
72
|
+
}, "Smart link intent resume failed");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async function resumeMention(intent, slackClient, tokenCtx, teamId, tenantId) {
|
|
76
|
+
const { slackUserId } = tokenCtx;
|
|
77
|
+
if (!intent.agentId || !intent.projectId) {
|
|
78
|
+
logger.error({
|
|
79
|
+
entryPoint: intent.entryPoint,
|
|
80
|
+
channelId: intent.channelId,
|
|
81
|
+
hasAgentId: !!intent.agentId,
|
|
82
|
+
hasProjectId: !!intent.projectId
|
|
83
|
+
}, "Mention intent missing agentId or projectId");
|
|
84
|
+
await postErrorToChannel(slackClient, intent.channelId, slackUserId, intent.threadTs);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const replyThreadTs = intent.threadTs || intent.messageTs;
|
|
88
|
+
if (!replyThreadTs) {
|
|
89
|
+
logger.error({
|
|
90
|
+
entryPoint: intent.entryPoint,
|
|
91
|
+
channelId: intent.channelId
|
|
92
|
+
}, "Mention intent missing threadTs and messageTs");
|
|
93
|
+
await postErrorToChannel(slackClient, intent.channelId, slackUserId, void 0, "We couldn't resume your question due to a technical issue. Please try mentioning @Inkeep again.");
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const agentConfig = await resolveEffectiveAgent({
|
|
97
|
+
tenantId,
|
|
98
|
+
teamId,
|
|
99
|
+
channelId: intent.channelId
|
|
100
|
+
});
|
|
101
|
+
const slackUserToken = await signSlackUserToken({
|
|
102
|
+
...tokenCtx,
|
|
103
|
+
...getChannelAuthClaims(agentConfig, intent.channelId)
|
|
104
|
+
});
|
|
105
|
+
const ackMessage = await slackClient.chat.postMessage({
|
|
106
|
+
channel: intent.channelId,
|
|
107
|
+
thread_ts: replyThreadTs,
|
|
108
|
+
text: "_Answering your question..._"
|
|
109
|
+
});
|
|
110
|
+
const conversationId = generateSlackConversationId({
|
|
111
|
+
teamId,
|
|
112
|
+
threadTs: replyThreadTs,
|
|
113
|
+
channel: intent.channelId,
|
|
114
|
+
isDM: false,
|
|
115
|
+
agentId: intent.agentId
|
|
116
|
+
});
|
|
117
|
+
await streamAgentResponse({
|
|
118
|
+
slackClient,
|
|
119
|
+
channel: intent.channelId,
|
|
120
|
+
threadTs: replyThreadTs,
|
|
121
|
+
thinkingMessageTs: ackMessage.ts || "",
|
|
122
|
+
slackUserId,
|
|
123
|
+
teamId,
|
|
124
|
+
jwtToken: slackUserToken,
|
|
125
|
+
projectId: intent.projectId,
|
|
126
|
+
agentId: intent.agentId,
|
|
127
|
+
question: intent.question,
|
|
128
|
+
agentName: intent.agentId,
|
|
129
|
+
conversationId
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
async function resumeCommand(intent, slackClient, tokenCtx, teamId, tenantId) {
|
|
133
|
+
const { slackUserId } = tokenCtx;
|
|
134
|
+
const resolvedAgent = await resolveEffectiveAgent({
|
|
135
|
+
tenantId,
|
|
136
|
+
teamId,
|
|
137
|
+
channelId: intent.channelId,
|
|
138
|
+
userId: slackUserId
|
|
139
|
+
});
|
|
140
|
+
if (!resolvedAgent) {
|
|
141
|
+
await postErrorToChannel(slackClient, intent.channelId, slackUserId, void 0, "The agent couldn't be found. Try asking your question again.");
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
await executeAndDeliver({
|
|
145
|
+
intent,
|
|
146
|
+
slackClient,
|
|
147
|
+
slackUserToken: await signSlackUserToken({
|
|
148
|
+
...tokenCtx,
|
|
149
|
+
...getChannelAuthClaims(resolvedAgent, intent.channelId)
|
|
150
|
+
}),
|
|
151
|
+
slackUserId,
|
|
152
|
+
teamId,
|
|
153
|
+
agentId: resolvedAgent.agentId,
|
|
154
|
+
agentName: resolvedAgent.agentName || resolvedAgent.agentId,
|
|
155
|
+
projectId: resolvedAgent.projectId
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
async function resumeRunCommand(intent, slackClient, tokenCtx, teamId, tenantId) {
|
|
159
|
+
const { slackUserId } = tokenCtx;
|
|
160
|
+
if (!intent.agentIdentifier) {
|
|
161
|
+
logger.error({
|
|
162
|
+
entryPoint: intent.entryPoint,
|
|
163
|
+
channelId: intent.channelId
|
|
164
|
+
}, "Run command intent missing agentIdentifier");
|
|
165
|
+
await postErrorToChannel(slackClient, intent.channelId, slackUserId);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const agentConfig = await resolveEffectiveAgent({
|
|
169
|
+
tenantId,
|
|
170
|
+
teamId,
|
|
171
|
+
channelId: intent.channelId
|
|
172
|
+
});
|
|
173
|
+
const slackUserToken = await signSlackUserToken({
|
|
174
|
+
...tokenCtx,
|
|
175
|
+
...getChannelAuthClaims(agentConfig, intent.channelId)
|
|
176
|
+
});
|
|
177
|
+
const agentInfo = await findAgentByIdentifierViaApi(tenantId, intent.agentIdentifier, slackUserToken);
|
|
178
|
+
if (!agentInfo) {
|
|
179
|
+
await postErrorToChannel(slackClient, intent.channelId, slackUserId, void 0, `Agent "${intent.agentIdentifier}" couldn't be found. Try asking your question again.`);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
await executeAndDeliver({
|
|
183
|
+
intent,
|
|
184
|
+
slackClient,
|
|
185
|
+
slackUserToken,
|
|
186
|
+
slackUserId,
|
|
187
|
+
teamId,
|
|
188
|
+
agentId: agentInfo.id,
|
|
189
|
+
agentName: agentInfo.name || agentInfo.id,
|
|
190
|
+
projectId: agentInfo.projectId
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
async function findAgentByIdentifierViaApi(tenantId, identifier, authToken) {
|
|
194
|
+
const apiBaseUrl = env.INKEEP_AGENTS_API_URL || "http://localhost:3002";
|
|
195
|
+
const controller = new AbortController();
|
|
196
|
+
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
197
|
+
try {
|
|
198
|
+
const projectsResponse = await fetch(`${apiBaseUrl}/manage/tenants/${tenantId}/projects`, {
|
|
199
|
+
method: "GET",
|
|
200
|
+
headers: {
|
|
201
|
+
"Content-Type": "application/json",
|
|
202
|
+
Authorization: `Bearer ${authToken}`
|
|
203
|
+
},
|
|
204
|
+
signal: controller.signal
|
|
205
|
+
});
|
|
206
|
+
if (!projectsResponse.ok) return null;
|
|
207
|
+
const projectsData = await projectsResponse.json();
|
|
208
|
+
const projects = projectsData.data || projectsData || [];
|
|
209
|
+
return (await Promise.all(projects.map(async (project) => {
|
|
210
|
+
try {
|
|
211
|
+
const agentsResponse = await fetch(`${apiBaseUrl}/manage/tenants/${tenantId}/projects/${project.id}/agents`, {
|
|
212
|
+
method: "GET",
|
|
213
|
+
headers: {
|
|
214
|
+
"Content-Type": "application/json",
|
|
215
|
+
Authorization: `Bearer ${authToken}`
|
|
216
|
+
},
|
|
217
|
+
signal: controller.signal
|
|
218
|
+
});
|
|
219
|
+
if (!agentsResponse.ok) return [];
|
|
220
|
+
const agentsData = await agentsResponse.json();
|
|
221
|
+
return (agentsData.data || agentsData || []).map((agent) => ({
|
|
222
|
+
id: agent.id,
|
|
223
|
+
name: agent.name,
|
|
224
|
+
projectId: project.id
|
|
225
|
+
}));
|
|
226
|
+
} catch (error) {
|
|
227
|
+
logger.warn({
|
|
228
|
+
error: error instanceof Error ? error.message : String(error),
|
|
229
|
+
projectId: project.id
|
|
230
|
+
}, "Failed to fetch agents for project during identifier lookup");
|
|
231
|
+
return [];
|
|
232
|
+
}
|
|
233
|
+
}))).flat().find((a) => a.id === identifier || a.name?.toLowerCase() === identifier.toLowerCase()) || null;
|
|
234
|
+
} catch (error) {
|
|
235
|
+
const isTimeout = error instanceof Error && error.name === "AbortError";
|
|
236
|
+
logger.warn({
|
|
237
|
+
error: error instanceof Error ? error.message : String(error),
|
|
238
|
+
tenantId,
|
|
239
|
+
identifier,
|
|
240
|
+
isTimeout
|
|
241
|
+
}, "Failed to find agent by identifier");
|
|
242
|
+
return null;
|
|
243
|
+
} finally {
|
|
244
|
+
clearTimeout(timeout);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
async function executeAndDeliver(params) {
|
|
248
|
+
const { intent, slackClient, slackUserToken, slackUserId, teamId, agentId, agentName, projectId } = params;
|
|
249
|
+
const apiBaseUrl = env.INKEEP_AGENTS_API_URL || "http://localhost:3002";
|
|
250
|
+
const controller = new AbortController();
|
|
251
|
+
const timeout = setTimeout(() => controller.abort(), 3e4);
|
|
252
|
+
let response;
|
|
253
|
+
try {
|
|
254
|
+
response = await fetch(`${apiBaseUrl}/run/api/chat`, {
|
|
255
|
+
method: "POST",
|
|
256
|
+
headers: {
|
|
257
|
+
"Content-Type": "application/json",
|
|
258
|
+
Authorization: `Bearer ${slackUserToken}`,
|
|
259
|
+
"x-inkeep-project-id": projectId,
|
|
260
|
+
"x-inkeep-agent-id": agentId
|
|
261
|
+
},
|
|
262
|
+
body: JSON.stringify({
|
|
263
|
+
messages: [{
|
|
264
|
+
role: "user",
|
|
265
|
+
content: intent.question
|
|
266
|
+
}],
|
|
267
|
+
stream: false
|
|
268
|
+
}),
|
|
269
|
+
signal: controller.signal
|
|
270
|
+
});
|
|
271
|
+
} catch (error) {
|
|
272
|
+
clearTimeout(timeout);
|
|
273
|
+
if (error.name === "AbortError") logger.warn({
|
|
274
|
+
teamId,
|
|
275
|
+
timeoutMs: 3e4
|
|
276
|
+
}, "Resume agent execution timed out");
|
|
277
|
+
throw error;
|
|
278
|
+
} finally {
|
|
279
|
+
clearTimeout(timeout);
|
|
280
|
+
}
|
|
281
|
+
if (!response.ok) {
|
|
282
|
+
logger.error({
|
|
283
|
+
status: response.status,
|
|
284
|
+
agentId,
|
|
285
|
+
projectId
|
|
286
|
+
}, "Resume run API call failed");
|
|
287
|
+
await postErrorToChannel(slackClient, intent.channelId, slackUserId, void 0, "Something went wrong while answering your question. Please try again.");
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
const result = await response.json();
|
|
291
|
+
const assistantMessage = result.choices?.[0]?.message?.content || result.message?.content || "No response received";
|
|
292
|
+
const contextBlock = createContextBlock({ agentName });
|
|
293
|
+
if (intent.responseUrl) try {
|
|
294
|
+
await sendResponseUrlMessage(intent.responseUrl, {
|
|
295
|
+
response_type: "ephemeral",
|
|
296
|
+
text: assistantMessage,
|
|
297
|
+
blocks: [{
|
|
298
|
+
type: "section",
|
|
299
|
+
text: {
|
|
300
|
+
type: "mrkdwn",
|
|
301
|
+
text: assistantMessage
|
|
302
|
+
}
|
|
303
|
+
}, contextBlock]
|
|
304
|
+
});
|
|
305
|
+
return;
|
|
306
|
+
} catch {
|
|
307
|
+
logger.warn({ channelId: intent.channelId }, "response_url delivery failed, falling back to bot channel post");
|
|
308
|
+
}
|
|
309
|
+
await slackClient.chat.postMessage({
|
|
310
|
+
channel: intent.channelId,
|
|
311
|
+
text: assistantMessage,
|
|
312
|
+
blocks: [{
|
|
313
|
+
type: "section",
|
|
314
|
+
text: {
|
|
315
|
+
type: "mrkdwn",
|
|
316
|
+
text: assistantMessage
|
|
317
|
+
}
|
|
318
|
+
}, contextBlock]
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
async function postErrorToChannel(slackClient, channelId, slackUserId, threadTs, message = "The agent couldn't be found. Try asking your question again.") {
|
|
322
|
+
try {
|
|
323
|
+
await slackClient.chat.postEphemeral({
|
|
324
|
+
channel: channelId,
|
|
325
|
+
user: slackUserId,
|
|
326
|
+
thread_ts: threadTs,
|
|
327
|
+
text: message
|
|
328
|
+
});
|
|
329
|
+
} catch (error) {
|
|
330
|
+
logger.warn({
|
|
331
|
+
error,
|
|
332
|
+
channelId
|
|
333
|
+
}, "Failed to post error message to Slack");
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
//#endregion
|
|
338
|
+
export { resumeSmartLinkIntent };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inkeep/agents-work-apps",
|
|
3
|
-
"version": "0.0.0-dev-
|
|
3
|
+
"version": "0.0.0-dev-20260224195557",
|
|
4
4
|
"description": "First party integrations for Inkeep Agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"jose": "^6.1.0",
|
|
34
34
|
"minimatch": "^10.1.1",
|
|
35
35
|
"slack-block-builder": "^2.8.0",
|
|
36
|
-
"@inkeep/agents-core": "0.0.0-dev-
|
|
36
|
+
"@inkeep/agents-core": "0.0.0-dev-20260224195557"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@hono/zod-openapi": "^1.1.5",
|