@inkeep/agents-work-apps 0.0.0-dev-20260212220816 → 0.0.0-dev-20260213181729

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,6 +4,7 @@ import { SlackStrings } from "../../i18n/strings.js";
4
4
  import { getSlackClient } from "../client.js";
5
5
  import { fetchAgentsForProject, fetchProjectsForTenant, getChannelAgentConfig, sendResponseUrlMessage } from "./utils.js";
6
6
  import { buildAgentSelectorModal, buildFollowUpModal, buildMessageShortcutModal } from "../modals.js";
7
+ import { SLACK_SPAN_KEYS, SLACK_SPAN_NAMES, setSpanWithError, tracer } from "../../tracer.js";
7
8
 
8
9
  //#region src/slack/services/events/block-actions.ts
9
10
  /**
@@ -15,206 +16,249 @@ const logger = getLogger("slack-block-actions");
15
16
  * Handle opening the agent selector modal when user clicks "Select Agent" button
16
17
  */
17
18
  async function handleOpenAgentSelectorModal(params) {
18
- const { triggerId, actionValue, teamId, responseUrl } = params;
19
- try {
20
- const { channel, threadTs, messageTs, slackUserId, tenantId } = JSON.parse(actionValue);
21
- const isInThread = Boolean(threadTs && threadTs !== messageTs);
22
- const workspaceConnection = await findWorkspaceConnectionByTeamId(teamId);
23
- if (!workspaceConnection?.botToken) {
24
- logger.error({ teamId }, "No bot token for modal");
25
- return;
26
- }
27
- const slackClient = getSlackClient(workspaceConnection.botToken);
28
- let projectList = await fetchProjectsForTenant(tenantId);
29
- if (projectList.length === 0) {
30
- const defaultAgent = await getChannelAgentConfig(teamId, channel);
31
- if (defaultAgent) projectList = [{
32
- id: defaultAgent.projectId,
33
- name: defaultAgent.projectName || defaultAgent.projectId
34
- }];
35
- }
36
- if (projectList.length === 0) {
37
- await slackClient.chat.postEphemeral({
19
+ return tracer.startActiveSpan(SLACK_SPAN_NAMES.OPEN_AGENT_SELECTOR_MODAL, async (span) => {
20
+ const { triggerId, actionValue, teamId, responseUrl } = params;
21
+ span.setAttribute(SLACK_SPAN_KEYS.TEAM_ID, teamId);
22
+ try {
23
+ const { channel, threadTs, messageTs, slackUserId, tenantId } = JSON.parse(actionValue);
24
+ span.setAttribute(SLACK_SPAN_KEYS.CHANNEL_ID, channel);
25
+ span.setAttribute(SLACK_SPAN_KEYS.USER_ID, slackUserId);
26
+ span.setAttribute(SLACK_SPAN_KEYS.TENANT_ID, tenantId);
27
+ const isInThread = Boolean(threadTs && threadTs !== messageTs);
28
+ const workspaceConnection = await findWorkspaceConnectionByTeamId(teamId);
29
+ if (!workspaceConnection?.botToken) {
30
+ logger.error({ teamId }, "No bot token for modal");
31
+ span.end();
32
+ return;
33
+ }
34
+ const slackClient = getSlackClient(workspaceConnection.botToken);
35
+ let projectList = await fetchProjectsForTenant(tenantId);
36
+ if (projectList.length === 0) {
37
+ const defaultAgent = await getChannelAgentConfig(teamId, channel);
38
+ if (defaultAgent) projectList = [{
39
+ id: defaultAgent.projectId,
40
+ name: defaultAgent.projectName || defaultAgent.projectId
41
+ }];
42
+ }
43
+ if (projectList.length === 0) {
44
+ logger.info({
45
+ teamId,
46
+ channel,
47
+ tenantId
48
+ }, "No projects configured — cannot open selector");
49
+ await slackClient.chat.postEphemeral({
50
+ channel,
51
+ user: slackUserId,
52
+ thread_ts: isInThread ? threadTs : void 0,
53
+ text: SlackStrings.status.noProjectsConfigured
54
+ });
55
+ span.end();
56
+ return;
57
+ }
58
+ const firstProject = projectList[0];
59
+ let agentList = await fetchAgentsForProject(tenantId, firstProject.id);
60
+ if (agentList.length === 0) {
61
+ const defaultAgent = await getChannelAgentConfig(teamId, channel);
62
+ if (defaultAgent && defaultAgent.projectId === firstProject.id) agentList = [{
63
+ id: defaultAgent.agentId,
64
+ name: defaultAgent.agentName || defaultAgent.agentId,
65
+ projectId: defaultAgent.projectId,
66
+ projectName: defaultAgent.projectName || defaultAgent.projectId
67
+ }];
68
+ }
69
+ const modalMetadata = {
38
70
  channel,
39
- user: slackUserId,
40
- thread_ts: isInThread ? threadTs : void 0,
41
- text: SlackStrings.status.noProjectsConfigured
71
+ threadTs: isInThread ? threadTs : void 0,
72
+ messageTs,
73
+ teamId,
74
+ slackUserId,
75
+ tenantId,
76
+ isInThread,
77
+ buttonResponseUrl: responseUrl
78
+ };
79
+ const modal = buildAgentSelectorModal({
80
+ projects: projectList,
81
+ agents: agentList.map((a) => ({
82
+ id: a.id,
83
+ name: a.name,
84
+ projectId: a.projectId,
85
+ projectName: a.projectName || a.projectId
86
+ })),
87
+ metadata: modalMetadata,
88
+ selectedProjectId: firstProject.id
42
89
  });
43
- return;
44
- }
45
- const firstProject = projectList[0];
46
- let agentList = await fetchAgentsForProject(tenantId, firstProject.id);
47
- if (agentList.length === 0) {
48
- const defaultAgent = await getChannelAgentConfig(teamId, channel);
49
- if (defaultAgent && defaultAgent.projectId === firstProject.id) agentList = [{
50
- id: defaultAgent.agentId,
51
- name: defaultAgent.agentName || defaultAgent.agentId,
52
- projectId: defaultAgent.projectId,
53
- projectName: defaultAgent.projectName || defaultAgent.projectId
54
- }];
90
+ await slackClient.views.open({
91
+ trigger_id: triggerId,
92
+ view: modal
93
+ });
94
+ logger.info({
95
+ teamId,
96
+ channel,
97
+ threadTs,
98
+ projectCount: projectList.length,
99
+ agentCount: agentList.length
100
+ }, "Opened agent selector modal");
101
+ span.end();
102
+ } catch (error) {
103
+ if (error instanceof Error) setSpanWithError(span, error);
104
+ logger.error({
105
+ error,
106
+ teamId
107
+ }, "Failed to open agent selector modal");
108
+ if (responseUrl) await sendResponseUrlMessage(responseUrl, {
109
+ text: SlackStrings.errors.failedToOpenSelector,
110
+ response_type: "ephemeral"
111
+ }).catch((e) => logger.warn({ error: e }, "Failed to send selector error notification"));
112
+ span.end();
55
113
  }
56
- const modalMetadata = {
57
- channel,
58
- threadTs: isInThread ? threadTs : void 0,
59
- messageTs,
60
- teamId,
61
- slackUserId,
62
- tenantId,
63
- isInThread,
64
- buttonResponseUrl: responseUrl
65
- };
66
- const modal = buildAgentSelectorModal({
67
- projects: projectList,
68
- agents: agentList.map((a) => ({
69
- id: a.id,
70
- name: a.name,
71
- projectId: a.projectId,
72
- projectName: a.projectName || a.projectId
73
- })),
74
- metadata: modalMetadata,
75
- selectedProjectId: firstProject.id
76
- });
77
- await slackClient.views.open({
78
- trigger_id: triggerId,
79
- view: modal
80
- });
81
- logger.info({
82
- teamId,
83
- channel,
84
- threadTs,
85
- projectCount: projectList.length,
86
- agentCount: agentList.length
87
- }, "Opened agent selector modal");
88
- } catch (error) {
89
- logger.error({
90
- error,
91
- teamId
92
- }, "Failed to open agent selector modal");
93
- if (responseUrl) await sendResponseUrlMessage(responseUrl, {
94
- text: SlackStrings.errors.failedToOpenSelector,
95
- response_type: "ephemeral"
96
- }).catch((e) => logger.warn({ error: e }, "Failed to send selector error notification"));
97
- }
114
+ });
98
115
  }
99
116
  /**
100
117
  * Handle "Follow Up" button click.
101
118
  * Opens a prompt-only modal that carries the conversationId for multi-turn context.
102
119
  */
103
120
  async function handleOpenFollowUpModal(params) {
104
- const { triggerId, actionValue, teamId, responseUrl } = params;
105
- try {
106
- const metadata = JSON.parse(actionValue);
107
- const workspaceConnection = await findWorkspaceConnectionByTeamId(teamId);
108
- if (!workspaceConnection?.botToken) {
109
- logger.error({ teamId }, "No bot token for follow-up modal");
110
- return;
121
+ return tracer.startActiveSpan(SLACK_SPAN_NAMES.OPEN_FOLLOW_UP_MODAL, async (span) => {
122
+ const { triggerId, actionValue, teamId, responseUrl } = params;
123
+ span.setAttribute(SLACK_SPAN_KEYS.TEAM_ID, teamId);
124
+ try {
125
+ const metadata = JSON.parse(actionValue);
126
+ span.setAttribute(SLACK_SPAN_KEYS.CONVERSATION_ID, metadata.conversationId || "");
127
+ span.setAttribute(SLACK_SPAN_KEYS.AGENT_ID, metadata.agentId || "");
128
+ const workspaceConnection = await findWorkspaceConnectionByTeamId(teamId);
129
+ if (!workspaceConnection?.botToken) {
130
+ logger.error({ teamId }, "No bot token for follow-up modal");
131
+ span.end();
132
+ return;
133
+ }
134
+ const slackClient = getSlackClient(workspaceConnection.botToken);
135
+ const modal = buildFollowUpModal(metadata);
136
+ await slackClient.views.open({
137
+ trigger_id: triggerId,
138
+ view: modal
139
+ });
140
+ logger.info({
141
+ teamId,
142
+ conversationId: metadata.conversationId,
143
+ agentId: metadata.agentId
144
+ }, "Opened follow-up modal");
145
+ span.end();
146
+ } catch (error) {
147
+ if (error instanceof Error) setSpanWithError(span, error);
148
+ logger.error({
149
+ error,
150
+ teamId
151
+ }, "Failed to open follow-up modal");
152
+ if (responseUrl) await sendResponseUrlMessage(responseUrl, {
153
+ text: "Failed to open follow-up dialog. Please try again.",
154
+ response_type: "ephemeral"
155
+ }).catch((e) => logger.warn({ error: e }, "Failed to send follow-up error notification"));
156
+ span.end();
111
157
  }
112
- const slackClient = getSlackClient(workspaceConnection.botToken);
113
- const modal = buildFollowUpModal(metadata);
114
- await slackClient.views.open({
115
- trigger_id: triggerId,
116
- view: modal
117
- });
118
- logger.info({
119
- teamId,
120
- conversationId: metadata.conversationId,
121
- agentId: metadata.agentId
122
- }, "Opened follow-up modal");
123
- } catch (error) {
124
- logger.error({
125
- error,
126
- teamId
127
- }, "Failed to open follow-up modal");
128
- if (responseUrl) await sendResponseUrlMessage(responseUrl, {
129
- text: "Failed to open follow-up dialog. Please try again.",
130
- response_type: "ephemeral"
131
- }).catch((e) => logger.warn({ error: e }, "Failed to send follow-up error notification"));
132
- }
158
+ });
133
159
  }
134
160
  /**
135
161
  * Handle message shortcut (context menu action on a message)
136
162
  * Opens a modal with the message content pre-filled as context
137
163
  */
138
164
  async function handleMessageShortcut(params) {
139
- const { triggerId, teamId, channelId, userId, messageTs, messageText, threadTs, responseUrl } = params;
140
- try {
141
- const workspaceConnection = await findWorkspaceConnectionByTeamId(teamId);
142
- if (!workspaceConnection?.botToken) {
143
- logger.error({ teamId }, "No bot token for message shortcut modal");
144
- return;
145
- }
146
- const tenantId = workspaceConnection.tenantId;
147
- const slackClient = getSlackClient(workspaceConnection.botToken);
148
- let projectList = await fetchProjectsForTenant(tenantId);
149
- if (projectList.length === 0) {
150
- const defaultAgent = await getChannelAgentConfig(teamId, channelId);
151
- if (defaultAgent) projectList = [{
152
- id: defaultAgent.projectId,
153
- name: defaultAgent.projectName || defaultAgent.projectId
154
- }];
155
- }
156
- if (projectList.length === 0) {
157
- await slackClient.chat.postEphemeral({
165
+ return tracer.startActiveSpan(SLACK_SPAN_NAMES.MESSAGE_SHORTCUT, async (span) => {
166
+ const { triggerId, teamId, channelId, userId, messageTs, messageText, threadTs, responseUrl } = params;
167
+ span.setAttribute(SLACK_SPAN_KEYS.TEAM_ID, teamId);
168
+ span.setAttribute(SLACK_SPAN_KEYS.CHANNEL_ID, channelId);
169
+ span.setAttribute(SLACK_SPAN_KEYS.USER_ID, userId);
170
+ span.setAttribute(SLACK_SPAN_KEYS.MESSAGE_TS, messageTs);
171
+ if (threadTs) span.setAttribute(SLACK_SPAN_KEYS.THREAD_TS, threadTs);
172
+ try {
173
+ const workspaceConnection = await findWorkspaceConnectionByTeamId(teamId);
174
+ if (!workspaceConnection?.botToken) {
175
+ logger.error({ teamId }, "No bot token for message shortcut modal");
176
+ span.end();
177
+ return;
178
+ }
179
+ const tenantId = workspaceConnection.tenantId;
180
+ span.setAttribute(SLACK_SPAN_KEYS.TENANT_ID, tenantId);
181
+ const slackClient = getSlackClient(workspaceConnection.botToken);
182
+ let projectList = await fetchProjectsForTenant(tenantId);
183
+ if (projectList.length === 0) {
184
+ const defaultAgent = await getChannelAgentConfig(teamId, channelId);
185
+ if (defaultAgent) projectList = [{
186
+ id: defaultAgent.projectId,
187
+ name: defaultAgent.projectName || defaultAgent.projectId
188
+ }];
189
+ }
190
+ if (projectList.length === 0) {
191
+ logger.info({
192
+ teamId,
193
+ channelId,
194
+ tenantId
195
+ }, "No projects configured — cannot open message shortcut modal");
196
+ await slackClient.chat.postEphemeral({
197
+ channel: channelId,
198
+ user: userId,
199
+ text: SlackStrings.status.noProjectsConfigured
200
+ });
201
+ span.end();
202
+ return;
203
+ }
204
+ const firstProject = projectList[0];
205
+ let agentList = await fetchAgentsForProject(tenantId, firstProject.id);
206
+ if (agentList.length === 0) {
207
+ const defaultAgent = await getChannelAgentConfig(teamId, channelId);
208
+ if (defaultAgent && defaultAgent.projectId === firstProject.id) agentList = [{
209
+ id: defaultAgent.agentId,
210
+ name: defaultAgent.agentName || defaultAgent.agentId,
211
+ projectId: defaultAgent.projectId,
212
+ projectName: defaultAgent.projectName || defaultAgent.projectId
213
+ }];
214
+ }
215
+ const modalMetadata = {
158
216
  channel: channelId,
159
- user: userId,
160
- text: SlackStrings.status.noProjectsConfigured
217
+ threadTs,
218
+ messageTs,
219
+ teamId,
220
+ slackUserId: userId,
221
+ tenantId,
222
+ isInThread: Boolean(threadTs),
223
+ messageContext: messageText
224
+ };
225
+ const modal = buildMessageShortcutModal({
226
+ projects: projectList,
227
+ agents: agentList.map((a) => ({
228
+ id: a.id,
229
+ name: a.name,
230
+ projectId: a.projectId,
231
+ projectName: a.projectName || a.projectId
232
+ })),
233
+ metadata: modalMetadata,
234
+ selectedProjectId: firstProject.id,
235
+ messageContext: messageText
161
236
  });
162
- return;
163
- }
164
- const firstProject = projectList[0];
165
- let agentList = await fetchAgentsForProject(tenantId, firstProject.id);
166
- if (agentList.length === 0) {
167
- const defaultAgent = await getChannelAgentConfig(teamId, channelId);
168
- if (defaultAgent && defaultAgent.projectId === firstProject.id) agentList = [{
169
- id: defaultAgent.agentId,
170
- name: defaultAgent.agentName || defaultAgent.agentId,
171
- projectId: defaultAgent.projectId,
172
- projectName: defaultAgent.projectName || defaultAgent.projectId
173
- }];
237
+ await slackClient.views.open({
238
+ trigger_id: triggerId,
239
+ view: modal
240
+ });
241
+ logger.info({
242
+ teamId,
243
+ channelId,
244
+ messageTs,
245
+ projectCount: projectList.length,
246
+ agentCount: agentList.length
247
+ }, "Opened message shortcut modal");
248
+ span.end();
249
+ } catch (error) {
250
+ if (error instanceof Error) setSpanWithError(span, error);
251
+ logger.error({
252
+ error,
253
+ teamId
254
+ }, "Failed to open message shortcut modal");
255
+ if (responseUrl) await sendResponseUrlMessage(responseUrl, {
256
+ text: SlackStrings.errors.failedToOpenSelector,
257
+ response_type: "ephemeral"
258
+ }).catch((e) => logger.warn({ error: e }, "Failed to send shortcut error notification"));
259
+ span.end();
174
260
  }
175
- const modalMetadata = {
176
- channel: channelId,
177
- threadTs,
178
- messageTs,
179
- teamId,
180
- slackUserId: userId,
181
- tenantId,
182
- isInThread: Boolean(threadTs),
183
- messageContext: messageText
184
- };
185
- const modal = buildMessageShortcutModal({
186
- projects: projectList,
187
- agents: agentList.map((a) => ({
188
- id: a.id,
189
- name: a.name,
190
- projectId: a.projectId,
191
- projectName: a.projectName || a.projectId
192
- })),
193
- metadata: modalMetadata,
194
- selectedProjectId: firstProject.id,
195
- messageContext: messageText
196
- });
197
- await slackClient.views.open({
198
- trigger_id: triggerId,
199
- view: modal
200
- });
201
- logger.info({
202
- teamId,
203
- channelId,
204
- messageTs,
205
- projectCount: projectList.length,
206
- agentCount: agentList.length
207
- }, "Opened message shortcut modal");
208
- } catch (error) {
209
- logger.error({
210
- error,
211
- teamId
212
- }, "Failed to open message shortcut modal");
213
- if (responseUrl) await sendResponseUrlMessage(responseUrl, {
214
- text: SlackStrings.errors.failedToOpenSelector,
215
- response_type: "ephemeral"
216
- }).catch((e) => logger.warn({ error: e }, "Failed to send shortcut error notification"));
217
- }
261
+ });
218
262
  }
219
263
 
220
264
  //#endregion