@agent-native/dispatch 0.13.13 → 0.14.1

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.
Files changed (74) hide show
  1. package/dist/actions/delete-destination.d.ts +12 -12
  2. package/dist/actions/navigate.js +1 -1
  3. package/dist/actions/navigate.js.map +1 -1
  4. package/dist/actions/provider-api-audit.d.ts +7 -0
  5. package/dist/actions/provider-api-audit.d.ts.map +1 -0
  6. package/dist/actions/provider-api-audit.js +74 -0
  7. package/dist/actions/provider-api-audit.js.map +1 -0
  8. package/dist/actions/provider-api-catalog.d.ts +1 -1
  9. package/dist/actions/provider-api-request.js +10 -0
  10. package/dist/actions/provider-api-request.js.map +1 -1
  11. package/dist/actions/send-platform-message.d.ts +1 -0
  12. package/dist/actions/send-platform-message.js +43 -22
  13. package/dist/actions/send-platform-message.js.map +1 -1
  14. package/dist/actions/view-screen.js +9 -0
  15. package/dist/actions/view-screen.js.map +1 -1
  16. package/dist/components/layout/Layout.d.ts.map +1 -1
  17. package/dist/components/layout/Layout.js +75 -70
  18. package/dist/components/layout/Layout.js.map +1 -1
  19. package/dist/components/messaging-setup-panel.d.ts.map +1 -1
  20. package/dist/components/messaging-setup-panel.js +203 -127
  21. package/dist/components/messaging-setup-panel.js.map +1 -1
  22. package/dist/routes/index.d.ts.map +1 -1
  23. package/dist/routes/index.js +1 -0
  24. package/dist/routes/index.js.map +1 -1
  25. package/dist/routes/pages/messaging.js +1 -1
  26. package/dist/routes/pages/messaging.js.map +1 -1
  27. package/dist/routes/pages/operations.d.ts +5 -0
  28. package/dist/routes/pages/operations.d.ts.map +1 -0
  29. package/dist/routes/pages/operations.js +51 -0
  30. package/dist/routes/pages/operations.js.map +1 -0
  31. package/dist/routes/pages/overview.d.ts +2 -2
  32. package/dist/server/lib/dispatch-integrations.d.ts +7 -1
  33. package/dist/server/lib/dispatch-integrations.d.ts.map +1 -1
  34. package/dist/server/lib/dispatch-integrations.js +178 -8
  35. package/dist/server/lib/dispatch-integrations.js.map +1 -1
  36. package/dist/server/lib/dispatch-routing.d.ts +6 -0
  37. package/dist/server/lib/dispatch-routing.d.ts.map +1 -0
  38. package/dist/server/lib/dispatch-routing.js +30 -0
  39. package/dist/server/lib/dispatch-routing.js.map +1 -0
  40. package/dist/server/lib/env-config.d.ts.map +1 -1
  41. package/dist/server/lib/env-config.js +50 -0
  42. package/dist/server/lib/env-config.js.map +1 -1
  43. package/dist/server/lib/onboarding-steps.d.ts.map +1 -1
  44. package/dist/server/lib/onboarding-steps.js +25 -0
  45. package/dist/server/lib/onboarding-steps.js.map +1 -1
  46. package/dist/server/lib/provider-api.d.ts +2 -2
  47. package/dist/server/lib/provider-api.d.ts.map +1 -1
  48. package/dist/server/plugins/integrations.d.ts.map +1 -1
  49. package/dist/server/plugins/integrations.js +5 -2
  50. package/dist/server/plugins/integrations.js.map +1 -1
  51. package/package.json +2 -2
  52. package/src/actions/navigate.ts +1 -1
  53. package/src/actions/provider-api-audit.spec.ts +52 -0
  54. package/src/actions/provider-api-audit.ts +88 -0
  55. package/src/actions/provider-api-request.ts +10 -0
  56. package/src/actions/send-platform-message.spec.ts +108 -0
  57. package/src/actions/send-platform-message.ts +73 -25
  58. package/src/actions/view-screen.ts +11 -0
  59. package/src/components/layout/Layout.spec.tsx +154 -0
  60. package/src/components/layout/Layout.tsx +196 -189
  61. package/src/components/messaging-setup-panel.spec.tsx +191 -0
  62. package/src/components/messaging-setup-panel.tsx +472 -193
  63. package/src/routes/index.spec.ts +23 -0
  64. package/src/routes/index.ts +1 -0
  65. package/src/routes/pages/messaging.tsx +1 -1
  66. package/src/routes/pages/operations.tsx +134 -0
  67. package/src/server/lib/dispatch-integrations.spec.ts +113 -0
  68. package/src/server/lib/dispatch-integrations.ts +239 -6
  69. package/src/server/lib/dispatch-routing.spec.ts +44 -0
  70. package/src/server/lib/dispatch-routing.ts +42 -0
  71. package/src/server/lib/env-config.ts +50 -0
  72. package/src/server/lib/onboarding-steps.ts +32 -0
  73. package/src/server/plugins/integrations.ts +5 -2
  74. package/src/styles/dispatch-css.spec.ts +11 -0
@@ -1,7 +1,9 @@
1
1
  import crypto from "node:crypto";
2
+ import { evaluateIntegrationScopePolicy, getActiveIntegrationInstallationByKey, getIntegrationScope, resolveIntegrationTokenBundle, saveIntegrationScope, slackInstallationKey, } from "@agent-native/core/integrations";
2
3
  import { resolveOrgIdForEmail } from "@agent-native/core/org";
3
- import { withConfiguredAppBasePath } from "@agent-native/core/server";
4
+ import { readDeployCredentialEnv, resolveSecret, withConfiguredAppBasePath, } from "@agent-native/core/server";
4
5
  import { handleRemoteCodeCommand } from "./dispatch-remote-commands.js";
6
+ import { dispatchIntegrationRoutingHint, } from "./dispatch-routing.js";
5
7
  import { consumeLinkToken, resolveLinkedOwner } from "./dispatch-store.js";
6
8
  const slackProfileCache = new Map();
7
9
  const SLACK_PROFILE_CACHE_TTL = 10 * 60 * 1000;
@@ -76,6 +78,27 @@ function configuredDispatchIdentitiesUrl() {
76
78
  return null;
77
79
  }
78
80
  }
81
+ async function resolveManagedSlackInstallation(incoming) {
82
+ if (incoming.platform !== "slack")
83
+ return null;
84
+ const teamId = contextString(incoming.platformContext.teamId);
85
+ const apiAppId = contextString(incoming.platformContext.apiAppId);
86
+ if (!teamId)
87
+ return null;
88
+ try {
89
+ return await getActiveIntegrationInstallationByKey("slack", slackInstallationKey({ teamId, apiAppId }));
90
+ }
91
+ catch {
92
+ return null;
93
+ }
94
+ }
95
+ async function resolveManagedSlackToken(incoming) {
96
+ const installation = await resolveManagedSlackInstallation(incoming);
97
+ if (!installation)
98
+ return null;
99
+ const bundle = await resolveIntegrationTokenBundle("slack", installation.installationKey);
100
+ return bundle?.accessToken ?? null;
101
+ }
79
102
  function formatTelegramLinkRequiredMessage() {
80
103
  const identitiesUrl = configuredDispatchIdentitiesUrl();
81
104
  const linkStep = identitiesUrl
@@ -83,14 +106,26 @@ function formatTelegramLinkRequiredMessage() {
83
106
  : "Open Dispatch while signed in, create a Telegram link token, then send `/link <token>` here.";
84
107
  return `Telegram is connected, but this Telegram account is not linked to an Agent-Native user yet. ${linkStep} After that I can use your Builder.io org and connected apps.`;
85
108
  }
109
+ function formatSlackLinkRequiredMessage() {
110
+ const identitiesUrl = configuredDispatchIdentitiesUrl();
111
+ const linkStep = identitiesUrl
112
+ ? `Open ${identitiesUrl}, create a Slack link token, then send \`/link <token>\` in this DM.`
113
+ : "Open Dispatch while signed in, create a Slack link token, then send `/link <token>` in this DM.";
114
+ return `Agent Native is ready, but this Slack account is not linked to an Agent Native user yet. ${linkStep}`;
115
+ }
86
116
  async function resolveSlackSenderProfile(incoming) {
87
- if (incoming.platform !== "slack")
88
- return { email: null, name: null };
89
- const token = process.env.SLACK_BOT_TOKEN;
117
+ if (incoming.platform !== "slack") {
118
+ return { email: null, name: null, trust: "unknown" };
119
+ }
120
+ const managedToken = await resolveManagedSlackToken(incoming);
121
+ const token = managedToken ??
122
+ (await resolveSecret("SLACK_BOT_TOKEN")) ??
123
+ readDeployCredentialEnv("SLACK_BOT_TOKEN");
90
124
  const userId = contextString(incoming.senderId);
91
125
  const teamId = contextString(incoming.platformContext.teamId);
92
- if (!token || !userId)
93
- return { email: null, name: null };
126
+ if (!token || !userId) {
127
+ return { email: null, name: null, trust: "unknown" };
128
+ }
94
129
  // Slack user IDs are scoped per workspace, so without a teamId we can't
95
130
  // safely cache: two installs of the bot in different workspaces could
96
131
  // share user-id strings and collide on a single "default" key. Skip the
@@ -115,8 +150,13 @@ async function resolveSlackSenderProfile(incoming) {
115
150
  data.user?.real_name?.trim() ||
116
151
  data.user?.name?.trim() ||
117
152
  null,
153
+ trust: data.user?.is_ultra_restricted === true
154
+ ? "external_shared"
155
+ : data.user?.is_restricted === true
156
+ ? "guest"
157
+ : "trusted",
118
158
  }
119
- : { email: null, name: null };
159
+ : { email: null, name: null, trust: "unknown" };
120
160
  if (cacheKey) {
121
161
  slackProfileCache.set(cacheKey, {
122
162
  profile,
@@ -126,7 +166,7 @@ async function resolveSlackSenderProfile(incoming) {
126
166
  return profile;
127
167
  }
128
168
  catch {
129
- return { email: null, name: null };
169
+ return { email: null, name: null, trust: "unknown" };
130
170
  }
131
171
  }
132
172
  async function resolveSlackOwnerFromVerifiedEmail(incoming) {
@@ -142,6 +182,35 @@ async function resolveSlackOwnerFromVerifiedEmail(incoming) {
142
182
  const orgId = await resolveOrgIdForEmail(profile.email);
143
183
  return orgId ? profile.email : null;
144
184
  }
185
+ async function resolveSlackConversationTrust(incoming, actorTrust) {
186
+ if (incoming.triggerKind === "dm") {
187
+ return { trust: actorTrust, conversationType: "direct_message" };
188
+ }
189
+ const token = await resolveManagedSlackToken(incoming);
190
+ const channel = contextString(incoming.platformContext.channelId);
191
+ if (!token || !channel) {
192
+ return { trust: "unknown", conversationType: "channel" };
193
+ }
194
+ try {
195
+ const params = new URLSearchParams({ channel });
196
+ const response = await fetch(`https://slack.com/api/conversations.info?${params}`, { headers: { Authorization: `Bearer ${token}` } });
197
+ const data = (await response.json());
198
+ if (!data.ok) {
199
+ return { trust: "unknown", conversationType: "channel" };
200
+ }
201
+ return {
202
+ trust: data.channel?.is_ext_shared ? "external_shared" : actorTrust,
203
+ conversationType: data.channel?.is_im
204
+ ? "direct_message"
205
+ : data.channel?.is_mpim
206
+ ? "group_direct_message"
207
+ : "channel",
208
+ };
209
+ }
210
+ catch {
211
+ return { trust: "unknown", conversationType: "channel" };
212
+ }
213
+ }
145
214
  export async function resolveDispatchOwner(incoming) {
146
215
  try {
147
216
  const externalUserId = identityKeyForIncoming(incoming);
@@ -204,11 +273,112 @@ export async function resolveDispatchOwner(incoming) {
204
273
  return fallbackOwnerForIncoming(incoming);
205
274
  }
206
275
  }
276
+ /**
277
+ * Resolve a personal DM user or a workspace-qualified channel service
278
+ * principal. Managed Slack channels never silently inherit one requester's
279
+ * personal credentials or memory.
280
+ */
281
+ export async function resolveDispatchExecutionContext(incoming) {
282
+ if (incoming.platform !== "slack" || incoming.triggerKind === "dm") {
283
+ const ownerEmail = await resolveDispatchOwner(incoming);
284
+ if (incoming.platform === "slack" &&
285
+ ownerEmail.endsWith("@integration.local")) {
286
+ // Preserve a credential-less principal long enough for beforeProcess to
287
+ // deliver linking guidance or consume `/link <token>`. Never run the
288
+ // agent under this synthetic owner.
289
+ incoming.platformContext.identityLinkRequired = true;
290
+ }
291
+ return {
292
+ ownerEmail,
293
+ orgId: (await resolveOrgIdForEmail(ownerEmail)) ?? null,
294
+ principalType: "user",
295
+ };
296
+ }
297
+ const installation = await resolveManagedSlackInstallation(incoming);
298
+ if (!installation) {
299
+ // Preserve the legacy manually configured app path while managed installs
300
+ // roll out. Its behavior remains explicit and visible in Settings.
301
+ const ownerEmail = await resolveDispatchOwner(incoming);
302
+ return {
303
+ ownerEmail,
304
+ orgId: (await resolveOrgIdForEmail(ownerEmail)) ?? null,
305
+ principalType: "user",
306
+ };
307
+ }
308
+ const teamId = contextString(incoming.platformContext.teamId);
309
+ const channelId = contextString(incoming.platformContext.channelId);
310
+ if (!teamId || !channelId) {
311
+ throw new Error("Slack channel identity is incomplete");
312
+ }
313
+ const profile = await resolveSlackSenderProfile(incoming);
314
+ const conversation = await resolveSlackConversationTrust(incoming, profile.trust);
315
+ const access = {
316
+ ownerEmail: installation.ownerEmail,
317
+ orgId: installation.orgId,
318
+ };
319
+ const key = {
320
+ platform: "slack",
321
+ tenantId: teamId,
322
+ conversationId: channelId,
323
+ };
324
+ let scope = await getIntegrationScope(key, access);
325
+ if (!scope && incoming.triggerKind === "mention") {
326
+ scope = await saveIntegrationScope({
327
+ ...key,
328
+ conversationType: conversation.conversationType,
329
+ trust: conversation.trust,
330
+ orgId: installation.orgId,
331
+ installationId: installation.id,
332
+ }, access);
333
+ }
334
+ if (!scope)
335
+ throw new Error("Slack channel is not enabled for Agent Native");
336
+ const decision = evaluateIntegrationScopePolicy(scope, {
337
+ // Thread replies reached this point only after the adapter's active-task
338
+ // gate, so they are continuation steering rather than ambient chatter.
339
+ mentioned: incoming.triggerKind === "mention" ||
340
+ incoming.triggerKind === "thread_reply",
341
+ });
342
+ if (!decision.allowed) {
343
+ throw new Error(`Slack channel policy denied: ${decision.reason}`);
344
+ }
345
+ incoming.integrationScopeId = scope.id;
346
+ if (scope.defaultModel) {
347
+ incoming.platformContext.defaultModel = scope.defaultModel;
348
+ }
349
+ incoming.actorTrust = {
350
+ memberType: conversation.trust === "guest"
351
+ ? "guest"
352
+ : conversation.trust === "external_shared"
353
+ ? "external"
354
+ : conversation.trust === "trusted"
355
+ ? "member"
356
+ : "unknown",
357
+ verified: conversation.trust !== "unknown",
358
+ };
359
+ return {
360
+ ownerEmail: scope.serviceOwnerEmail,
361
+ orgId: scope.orgId,
362
+ principalType: "service",
363
+ installationId: installation.id,
364
+ scopeId: scope.id,
365
+ };
366
+ }
207
367
  export async function beforeDispatchProcess(incoming, adapter) {
208
368
  const trimmed = incoming.text.trim();
209
369
  const commandText = contextString(incoming.platformContext.rawText) || trimmed;
210
370
  const match = commandText.match(/^\/link(?:@\w+)?\s+([a-zA-Z0-9_-]+)$/);
211
371
  if (!match) {
372
+ const routedIncoming = incoming;
373
+ routedIncoming.routingHint ??= dispatchIntegrationRoutingHint(trimmed);
374
+ if (incoming.platform === "slack" &&
375
+ incoming.triggerKind === "dm" &&
376
+ incoming.platformContext.identityLinkRequired === true) {
377
+ return {
378
+ handled: true,
379
+ responseText: formatSlackLinkRequiredMessage(),
380
+ };
381
+ }
212
382
  if (platformRequiresExplicitLink(incoming)) {
213
383
  const owner = await resolveLinkedOwner(incoming.platform, identityKeyForIncoming(incoming), { allowAnyOrgFallback: true });
214
384
  if (!owner) {
@@ -1 +1 @@
1
- {"version":3,"file":"dispatch-integrations.js","sourceRoot":"","sources":["../../../src/server/lib/dispatch-integrations.ts"],"names":[],"mappings":"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","sourcesContent":["import crypto from \"node:crypto\";\n\nimport { resolveOrgIdForEmail } from \"@agent-native/core/org\";\nimport { withConfiguredAppBasePath } from \"@agent-native/core/server\";\nimport type {\n IncomingMessage,\n PlatformAdapter,\n} from \"@agent-native/core/server\";\n\nimport { handleRemoteCodeCommand } from \"./dispatch-remote-commands.js\";\nimport { consumeLinkToken, resolveLinkedOwner } from \"./dispatch-store.js\";\n\ntype SlackSenderProfile = {\n email: string | null;\n name: string | null;\n};\n\nconst slackProfileCache = new Map<\n string,\n { profile: SlackSenderProfile; expiresAt: number }\n>();\nconst SLACK_PROFILE_CACHE_TTL = 10 * 60 * 1000;\n\nfunction contextString(value: unknown): string | null {\n if (typeof value === \"string\" && value.trim()) return value.trim();\n if (typeof value === \"number\" && Number.isFinite(value)) return String(value);\n return null;\n}\n\nexport function identityKeyForIncoming(\n incoming: IncomingMessage,\n): string | null {\n const senderId = contextString(incoming.senderId);\n if (!senderId) return null;\n\n if (incoming.platform === \"slack\") {\n const teamId = contextString(incoming.platformContext.teamId);\n return teamId ? `${teamId}:${senderId}` : senderId;\n }\n\n if (incoming.platform === \"whatsapp\") {\n const phoneNumberId = contextString(incoming.platformContext.phoneNumberId);\n return phoneNumberId ? `${phoneNumberId}:${senderId}` : senderId;\n }\n\n if (incoming.platform === \"email\") {\n return senderId.toLowerCase();\n }\n\n return senderId;\n}\n\nfunction fallbackOwnerForIncoming(incoming: IncomingMessage): string {\n const tenant =\n contextString(incoming.platformContext.teamId) ||\n contextString(incoming.platformContext.phoneNumberId) ||\n contextString(incoming.platformContext.chatId) ||\n contextString(incoming.platformContext.from) ||\n incoming.externalThreadId;\n const raw = `${incoming.platform}:${tenant}:${incoming.senderId || \"\"}`;\n const hash = crypto\n .createHash(\"sha256\")\n .update(raw)\n .digest(\"hex\")\n .slice(0, 16);\n return `dispatch+${hash}@integration.local`;\n}\n\nfunction configuredDefaultOwnerForIncoming(\n incoming: IncomingMessage,\n): string | null {\n // This is intentionally Slack-only: a deployment-wide default owner grants\n // that Slack workspace access to the owner's connected agents and org\n // credentials, so other platforms should opt in with explicit identity links.\n if (incoming.platform !== \"slack\") return null;\n const email = process.env.DISPATCH_DEFAULT_OWNER_EMAIL?.trim();\n if (!email) return null;\n return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email) ? email : null;\n}\n\nfunction platformRequiresExplicitLink(incoming: IncomingMessage): boolean {\n // Telegram does not provide a verified email address. Require an explicit\n // identity link before it can act as a Builder/Agent-Native user.\n return incoming.platform === \"telegram\";\n}\n\nfunction configuredDispatchIdentitiesUrl(): string | null {\n const raw =\n process.env.WORKSPACE_GATEWAY_URL ||\n process.env.APP_URL ||\n process.env.URL ||\n process.env.DEPLOY_URL ||\n process.env.BETTER_AUTH_URL ||\n \"\";\n if (!raw.trim()) return null;\n const base = /^https?:\\/\\//i.test(raw) ? raw : `https://${raw}`;\n try {\n return new URL(\n \"identities\",\n `${withConfiguredAppBasePath(base).replace(/\\/+$/, \"\")}/`,\n ).toString();\n } catch {\n return null;\n }\n}\n\nfunction formatTelegramLinkRequiredMessage(): string {\n const identitiesUrl = configuredDispatchIdentitiesUrl();\n const linkStep = identitiesUrl\n ? `Tap ${identitiesUrl}, create a Telegram link token, then send \\`/link <token>\\` here.`\n : \"Open Dispatch while signed in, create a Telegram link token, then send `/link <token>` here.\";\n return `Telegram is connected, but this Telegram account is not linked to an Agent-Native user yet. ${linkStep} After that I can use your Builder.io org and connected apps.`;\n}\n\nasync function resolveSlackSenderProfile(\n incoming: IncomingMessage,\n): Promise<SlackSenderProfile> {\n if (incoming.platform !== \"slack\") return { email: null, name: null };\n const token = process.env.SLACK_BOT_TOKEN;\n const userId = contextString(incoming.senderId);\n const teamId = contextString(incoming.platformContext.teamId);\n if (!token || !userId) return { email: null, name: null };\n\n // Slack user IDs are scoped per workspace, so without a teamId we can't\n // safely cache: two installs of the bot in different workspaces could\n // share user-id strings and collide on a single \"default\" key. Skip the\n // cache (and lookup on every request) when teamId is missing.\n const cacheKey = teamId ? `${teamId}:${userId}` : null;\n if (cacheKey) {\n const cached = slackProfileCache.get(cacheKey);\n if (cached && cached.expiresAt > Date.now()) return cached.profile;\n }\n\n try {\n const params = new URLSearchParams({ user: userId });\n const res = await fetch(`https://slack.com/api/users.info?${params}`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n const data = (await res.json()) as {\n ok?: boolean;\n user?: {\n real_name?: string;\n name?: string;\n profile?: {\n email?: string;\n real_name?: string;\n display_name?: string;\n };\n };\n };\n const profile = data.ok\n ? {\n email: data.user?.profile?.email?.trim().toLowerCase() || null,\n name:\n data.user?.profile?.real_name?.trim() ||\n data.user?.profile?.display_name?.trim() ||\n data.user?.real_name?.trim() ||\n data.user?.name?.trim() ||\n null,\n }\n : { email: null, name: null };\n if (cacheKey) {\n slackProfileCache.set(cacheKey, {\n profile,\n expiresAt: Date.now() + SLACK_PROFILE_CACHE_TTL,\n });\n }\n return profile;\n } catch {\n return { email: null, name: null };\n }\n}\n\nasync function resolveSlackOwnerFromVerifiedEmail(\n incoming: IncomingMessage,\n): Promise<string | null> {\n const profile = await resolveSlackSenderProfile(incoming);\n if (!profile.email) return null;\n\n incoming.senderEmail = profile.email;\n incoming.platformContext.senderEmail = profile.email;\n if (profile.name) {\n incoming.senderName = profile.name;\n incoming.platformContext.senderName = profile.name;\n }\n\n const orgId = await resolveOrgIdForEmail(profile.email);\n return orgId ? profile.email : null;\n}\n\nexport async function resolveDispatchOwner(\n incoming: IncomingMessage,\n): Promise<string> {\n try {\n const externalUserId = identityKeyForIncoming(incoming);\n\n // Webhooks do not have the browser request's org context, so allow a safe\n // cross-org fallback when the linked platform identity maps to one owner.\n const owner = await resolveLinkedOwner(incoming.platform, externalUserId, {\n allowAnyOrgFallback: true,\n });\n if (owner) return owner;\n\n // For email, the sender's `From:` address is attacker-settable: SMTP lets\n // anyone claim any From, and our inbound webhook secret only authenticates\n // the provider→app hop, not the original sender. So we must NOT grant a\n // real user's identity (their API keys, org secrets, personal\n // instructions, ownable data) off the bare From. Mirror the Slack gate:\n // only return the sender email as the acting owner when BOTH\n // (a) the message is DKIM/SPF-verified for the From domain, AND\n // (b) that email maps to a real org member.\n // Otherwise fall through to the synthetic, credential-less fallback owner.\n // (A linked identity, handled by resolveLinkedOwner above, remains an\n // always-allowed way to bind an address regardless of verification.)\n //\n // Escape hatch: set DISPATCH_TRUST_UNVERIFIED_EMAIL_SENDER=1 to restore\n // the legacy \"trust the From header\" behavior. OFF by default; only use\n // this if you fully control the inbound mail path and accept that a\n // spoofed From can act as any org member. See FINDING 3 (inbound-email\n // impersonation) in the webhook security audit.\n if (\n incoming.platform === \"email\" &&\n incoming.senderId &&\n incoming.senderId.includes(\"@\")\n ) {\n if (process.env.DISPATCH_TRUST_UNVERIFIED_EMAIL_SENDER === \"1\") {\n return incoming.senderId;\n }\n if (incoming.senderVerified) {\n const orgId = await resolveOrgIdForEmail(incoming.senderId);\n if (orgId) return incoming.senderId;\n }\n // Unverified or not an org member — do not impersonate. Fall through to\n // the synthetic fallback owner below.\n }\n\n // Slack gives us a user id in the event payload. Resolve it to a verified\n // workspace email and use that user's own org context when they are an\n // Agent-Native member, so artifacts created via @agent-native are visible\n // when they open the target app.\n if (incoming.platform === \"slack\") {\n const slackOwner = await resolveSlackOwnerFromVerifiedEmail(incoming);\n if (slackOwner) return slackOwner;\n }\n\n const defaultOwner = configuredDefaultOwnerForIncoming(incoming);\n if (defaultOwner) return defaultOwner;\n\n return fallbackOwnerForIncoming(incoming);\n } catch {\n const defaultOwner = configuredDefaultOwnerForIncoming(incoming);\n if (defaultOwner) return defaultOwner;\n return fallbackOwnerForIncoming(incoming);\n }\n}\n\nexport async function beforeDispatchProcess(\n incoming: IncomingMessage,\n adapter: PlatformAdapter,\n): Promise<{ handled: true; responseText?: string } | { handled: false }> {\n const trimmed = incoming.text.trim();\n const commandText =\n contextString(incoming.platformContext.rawText) || trimmed;\n const match = commandText.match(/^\\/link(?:@\\w+)?\\s+([a-zA-Z0-9_-]+)$/);\n if (!match) {\n if (platformRequiresExplicitLink(incoming)) {\n const owner = await resolveLinkedOwner(\n incoming.platform,\n identityKeyForIncoming(incoming),\n { allowAnyOrgFallback: true },\n );\n if (!owner) {\n return {\n handled: true,\n responseText: formatTelegramLinkRequiredMessage(),\n };\n }\n }\n return handleRemoteCodeCommand(incoming, adapter, {\n resolveOwner: () => resolveDispatchOwner(incoming),\n });\n }\n\n try {\n const owner = await consumeLinkToken({\n platform: incoming.platform,\n token: match[1],\n externalUserId: identityKeyForIncoming(incoming),\n externalUserName: incoming.senderName || null,\n });\n return {\n handled: true,\n responseText: `Linked successfully. Future ${incoming.platform} messages will use ${owner}'s personal dispatch context.`,\n };\n } catch (error) {\n return {\n handled: true,\n responseText:\n error instanceof Error ? error.message : \"Failed to link this account.\",\n };\n }\n}\n"]}
1
+ {"version":3,"file":"dispatch-integrations.js","sourceRoot":"","sources":["../../../src/server/lib/dispatch-integrations.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,aAAa,CAAC;AAEjC,OAAO,EACL,8BAA8B,EAC9B,qCAAqC,EACrC,mBAAmB,EACnB,6BAA6B,EAC7B,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EACL,uBAAuB,EACvB,aAAa,EACb,yBAAyB,GAC1B,MAAM,2BAA2B,CAAC;AAOnC,OAAO,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AACxE,OAAO,EACL,8BAA8B,GAE/B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAQ3E,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAG9B,CAAC;AACJ,MAAM,uBAAuB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAE/C,SAAS,aAAa,CAAC,KAAc;IACnC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE;QAAE,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;IACnE,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9E,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,sBAAsB,CACpC,QAAyB;IAEzB,MAAM,QAAQ,GAAG,aAAa,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAClD,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAE3B,IAAI,QAAQ,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,aAAa,CAAC,QAAQ,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;QAC9D,OAAO,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,QAAQ,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;IACrD,CAAC;IAED,IAAI,QAAQ,CAAC,QAAQ,KAAK,UAAU,EAAE,CAAC;QACrC,MAAM,aAAa,GAAG,aAAa,CAAC,QAAQ,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC;QAC5E,OAAO,aAAa,CAAC,CAAC,CAAC,GAAG,aAAa,IAAI,QAAQ,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;IACnE,CAAC;IAED,IAAI,QAAQ,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAClC,OAAO,QAAQ,CAAC,WAAW,EAAE,CAAC;IAChC,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,wBAAwB,CAAC,QAAyB;IACzD,MAAM,MAAM,GACV,aAAa,CAAC,QAAQ,CAAC,eAAe,CAAC,MAAM,CAAC;QAC9C,aAAa,CAAC,QAAQ,CAAC,eAAe,CAAC,aAAa,CAAC;QACrD,aAAa,CAAC,QAAQ,CAAC,eAAe,CAAC,MAAM,CAAC;QAC9C,aAAa,CAAC,QAAQ,CAAC,eAAe,CAAC,IAAI,CAAC;QAC5C,QAAQ,CAAC,gBAAgB,CAAC;IAC5B,MAAM,GAAG,GAAG,GAAG,QAAQ,CAAC,QAAQ,IAAI,MAAM,IAAI,QAAQ,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;IACxE,MAAM,IAAI,GAAG,MAAM;SAChB,UAAU,CAAC,QAAQ,CAAC;SACpB,MAAM,CAAC,GAAG,CAAC;SACX,MAAM,CAAC,KAAK,CAAC;SACb,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAChB,OAAO,YAAY,IAAI,oBAAoB,CAAC;AAC9C,CAAC;AAED,SAAS,iCAAiC,CACxC,QAAyB;IAEzB,2EAA2E;IAC3E,sEAAsE;IACtE,8EAA8E;IAC9E,IAAI,QAAQ,CAAC,QAAQ,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC;IAC/C,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,4BAA4B,EAAE,IAAI,EAAE,CAAC;IAC/D,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,OAAO,4BAA4B,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AACjE,CAAC;AAED,SAAS,4BAA4B,CAAC,QAAyB;IAC7D,0EAA0E;IAC1E,kEAAkE;IAClE,OAAO,QAAQ,CAAC,QAAQ,KAAK,UAAU,CAAC;AAC1C,CAAC;AAED,SAAS,+BAA+B;IACtC,MAAM,GAAG,GACP,OAAO,CAAC,GAAG,CAAC,qBAAqB;QACjC,OAAO,CAAC,GAAG,CAAC,OAAO;QACnB,OAAO,CAAC,GAAG,CAAC,GAAG;QACf,OAAO,CAAC,GAAG,CAAC,UAAU;QACtB,OAAO,CAAC,GAAG,CAAC,eAAe;QAC3B,EAAE,CAAC;IACL,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE;QAAE,OAAO,IAAI,CAAC;IAC7B,MAAM,IAAI,GAAG,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,GAAG,EAAE,CAAC;IAChE,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CACZ,YAAY,EACZ,GAAG,yBAAyB,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,CAC1D,CAAC,QAAQ,EAAE,CAAC;IACf,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,KAAK,UAAU,+BAA+B,CAAC,QAAyB;IACtE,IAAI,QAAQ,CAAC,QAAQ,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC;IAC/C,MAAM,MAAM,GAAG,aAAa,CAAC,QAAQ,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;IAC9D,MAAM,QAAQ,GAAG,aAAa,CAAC,QAAQ,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;IAClE,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACzB,IAAI,CAAC;QACH,OAAO,MAAM,qCAAqC,CAChD,OAAO,EACP,oBAAoB,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAC3C,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,KAAK,UAAU,wBAAwB,CACrC,QAAyB;IAEzB,MAAM,YAAY,GAAG,MAAM,+BAA+B,CAAC,QAAQ,CAAC,CAAC;IACrE,IAAI,CAAC,YAAY;QAAE,OAAO,IAAI,CAAC;IAC/B,MAAM,MAAM,GAAG,MAAM,6BAA6B,CAChD,OAAO,EACP,YAAY,CAAC,eAAe,CAC7B,CAAC;IACF,OAAO,MAAM,EAAE,WAAW,IAAI,IAAI,CAAC;AACrC,CAAC;AAED,SAAS,iCAAiC;IACxC,MAAM,aAAa,GAAG,+BAA+B,EAAE,CAAC;IACxD,MAAM,QAAQ,GAAG,aAAa;QAC5B,CAAC,CAAC,OAAO,aAAa,mEAAmE;QACzF,CAAC,CAAC,8FAA8F,CAAC;IACnG,OAAO,+FAA+F,QAAQ,+DAA+D,CAAC;AAChL,CAAC;AAED,SAAS,8BAA8B;IACrC,MAAM,aAAa,GAAG,+BAA+B,EAAE,CAAC;IACxD,MAAM,QAAQ,GAAG,aAAa;QAC5B,CAAC,CAAC,QAAQ,aAAa,sEAAsE;QAC7F,CAAC,CAAC,iGAAiG,CAAC;IACtG,OAAO,4FAA4F,QAAQ,EAAE,CAAC;AAChH,CAAC;AAED,KAAK,UAAU,yBAAyB,CACtC,QAAyB;IAEzB,IAAI,QAAQ,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAClC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IACvD,CAAC;IACD,MAAM,YAAY,GAAG,MAAM,wBAAwB,CAAC,QAAQ,CAAC,CAAC;IAC9D,MAAM,KAAK,GACT,YAAY;QACZ,CAAC,MAAM,aAAa,CAAC,iBAAiB,CAAC,CAAC;QACxC,uBAAuB,CAAC,iBAAiB,CAAC,CAAC;IAC7C,MAAM,MAAM,GAAG,aAAa,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAChD,MAAM,MAAM,GAAG,aAAa,CAAC,QAAQ,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;IAC9D,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;QACtB,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IACvD,CAAC;IAED,wEAAwE;IACxE,sEAAsE;IACtE,wEAAwE;IACxE,8DAA8D;IAC9D,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACvD,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,MAAM,GAAG,iBAAiB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC/C,IAAI,MAAM,IAAI,MAAM,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE;YAAE,OAAO,MAAM,CAAC,OAAO,CAAC;IACrE,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QACrD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,oCAAoC,MAAM,EAAE,EAAE;YACpE,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,KAAK,EAAE,EAAE;SAC9C,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAa7B,CAAC;QACF,MAAM,OAAO,GAAG,IAAI,CAAC,EAAE;YACrB,CAAC,CAAC;gBACE,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,IAAI,IAAI;gBAC9D,IAAI,EACF,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE;oBACrC,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE;oBACxC,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE;oBAC5B,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE;oBACvB,IAAI;gBACN,KAAK,EACH,IAAI,CAAC,IAAI,EAAE,mBAAmB,KAAK,IAAI;oBACrC,CAAC,CAAE,iBAA2B;oBAC9B,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI;wBACjC,CAAC,CAAE,OAAiB;wBACpB,CAAC,CAAE,SAAmB;aAC7B;YACH,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,SAAkB,EAAE,CAAC;QAC3D,IAAI,QAAQ,EAAE,CAAC;YACb,iBAAiB,CAAC,GAAG,CAAC,QAAQ,EAAE;gBAC9B,OAAO;gBACP,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,uBAAuB;aAChD,CAAC,CAAC;QACL,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IACvD,CAAC;AACH,CAAC;AAED,KAAK,UAAU,kCAAkC,CAC/C,QAAyB;IAEzB,MAAM,OAAO,GAAG,MAAM,yBAAyB,CAAC,QAAQ,CAAC,CAAC;IAC1D,IAAI,CAAC,OAAO,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAEhC,QAAQ,CAAC,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC;IACrC,QAAQ,CAAC,eAAe,CAAC,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC;IACrD,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;QACjB,QAAQ,CAAC,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;QACnC,QAAQ,CAAC,eAAe,CAAC,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IACrD,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,oBAAoB,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACxD,OAAO,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AACtC,CAAC;AAED,KAAK,UAAU,6BAA6B,CAC1C,QAAyB,EACzB,UAAuC;IAKvC,IAAI,QAAQ,CAAC,WAAW,KAAK,IAAI,EAAE,CAAC;QAClC,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,CAAC;IACnE,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,wBAAwB,CAAC,QAAQ,CAAC,CAAC;IACvD,MAAM,OAAO,GAAG,aAAa,CAAC,QAAQ,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC;IAClE,IAAI,CAAC,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC;QACvB,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,SAAS,EAAE,CAAC;IAC3D,CAAC;IACD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;QAChD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAC1B,4CAA4C,MAAM,EAAE,EACpD,EAAE,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,KAAK,EAAE,EAAE,EAAE,CAClD,CAAC;QACF,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAOlC,CAAC;QACF,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;YACb,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,SAAS,EAAE,CAAC;QAC3D,CAAC;QACD,OAAO;YACL,KAAK,EAAE,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,UAAU;YACnE,gBAAgB,EAAE,IAAI,CAAC,OAAO,EAAE,KAAK;gBACnC,CAAC,CAAC,gBAAgB;gBAClB,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO;oBACrB,CAAC,CAAC,sBAAsB;oBACxB,CAAC,CAAC,SAAS;SAChB,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,SAAS,EAAE,CAAC;IAC3D,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,QAAyB;IAEzB,IAAI,CAAC;QACH,MAAM,cAAc,GAAG,sBAAsB,CAAC,QAAQ,CAAC,CAAC;QAExD,0EAA0E;QAC1E,0EAA0E;QAC1E,MAAM,KAAK,GAAG,MAAM,kBAAkB,CAAC,QAAQ,CAAC,QAAQ,EAAE,cAAc,EAAE;YACxE,mBAAmB,EAAE,IAAI;SAC1B,CAAC,CAAC;QACH,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;QAExB,0EAA0E;QAC1E,2EAA2E;QAC3E,wEAAwE;QACxE,8DAA8D;QAC9D,wEAAwE;QACxE,6DAA6D;QAC7D,kEAAkE;QAClE,8CAA8C;QAC9C,2EAA2E;QAC3E,sEAAsE;QACtE,qEAAqE;QACrE,EAAE;QACF,wEAAwE;QACxE,wEAAwE;QACxE,oEAAoE;QACpE,uEAAuE;QACvE,gDAAgD;QAChD,IACE,QAAQ,CAAC,QAAQ,KAAK,OAAO;YAC7B,QAAQ,CAAC,QAAQ;YACjB,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,EAC/B,CAAC;YACD,IAAI,OAAO,CAAC,GAAG,CAAC,sCAAsC,KAAK,GAAG,EAAE,CAAC;gBAC/D,OAAO,QAAQ,CAAC,QAAQ,CAAC;YAC3B,CAAC;YACD,IAAI,QAAQ,CAAC,cAAc,EAAE,CAAC;gBAC5B,MAAM,KAAK,GAAG,MAAM,oBAAoB,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;gBAC5D,IAAI,KAAK;oBAAE,OAAO,QAAQ,CAAC,QAAQ,CAAC;YACtC,CAAC;YACD,wEAAwE;YACxE,sCAAsC;QACxC,CAAC;QAED,0EAA0E;QAC1E,uEAAuE;QACvE,0EAA0E;QAC1E,iCAAiC;QACjC,IAAI,QAAQ,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;YAClC,MAAM,UAAU,GAAG,MAAM,kCAAkC,CAAC,QAAQ,CAAC,CAAC;YACtE,IAAI,UAAU;gBAAE,OAAO,UAAU,CAAC;QACpC,CAAC;QAED,MAAM,YAAY,GAAG,iCAAiC,CAAC,QAAQ,CAAC,CAAC;QACjE,IAAI,YAAY;YAAE,OAAO,YAAY,CAAC;QAEtC,OAAO,wBAAwB,CAAC,QAAQ,CAAC,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,YAAY,GAAG,iCAAiC,CAAC,QAAQ,CAAC,CAAC;QACjE,IAAI,YAAY;YAAE,OAAO,YAAY,CAAC;QACtC,OAAO,wBAAwB,CAAC,QAAQ,CAAC,CAAC;IAC5C,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,+BAA+B,CACnD,QAAyB;IAEzB,IAAI,QAAQ,CAAC,QAAQ,KAAK,OAAO,IAAI,QAAQ,CAAC,WAAW,KAAK,IAAI,EAAE,CAAC;QACnE,MAAM,UAAU,GAAG,MAAM,oBAAoB,CAAC,QAAQ,CAAC,CAAC;QACxD,IACE,QAAQ,CAAC,QAAQ,KAAK,OAAO;YAC7B,UAAU,CAAC,QAAQ,CAAC,oBAAoB,CAAC,EACzC,CAAC;YACD,wEAAwE;YACxE,qEAAqE;YACrE,oCAAoC;YACpC,QAAQ,CAAC,eAAe,CAAC,oBAAoB,GAAG,IAAI,CAAC;QACvD,CAAC;QACD,OAAO;YACL,UAAU;YACV,KAAK,EAAE,CAAC,MAAM,oBAAoB,CAAC,UAAU,CAAC,CAAC,IAAI,IAAI;YACvD,aAAa,EAAE,MAAM;SACtB,CAAC;IACJ,CAAC;IAED,MAAM,YAAY,GAAG,MAAM,+BAA+B,CAAC,QAAQ,CAAC,CAAC;IACrE,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,0EAA0E;QAC1E,mEAAmE;QACnE,MAAM,UAAU,GAAG,MAAM,oBAAoB,CAAC,QAAQ,CAAC,CAAC;QACxD,OAAO;YACL,UAAU;YACV,KAAK,EAAE,CAAC,MAAM,oBAAoB,CAAC,UAAU,CAAC,CAAC,IAAI,IAAI;YACvD,aAAa,EAAE,MAAM;SACtB,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,aAAa,CAAC,QAAQ,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;IAC9D,MAAM,SAAS,GAAG,aAAa,CAAC,QAAQ,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC;IACpE,IAAI,CAAC,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC1D,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,yBAAyB,CAAC,QAAQ,CAAC,CAAC;IAC1D,MAAM,YAAY,GAAG,MAAM,6BAA6B,CACtD,QAAQ,EACR,OAAO,CAAC,KAAK,CACd,CAAC;IACF,MAAM,MAAM,GAAG;QACb,UAAU,EAAE,YAAY,CAAC,UAAU;QACnC,KAAK,EAAE,YAAY,CAAC,KAAK;KAC1B,CAAC;IACF,MAAM,GAAG,GAAG;QACV,QAAQ,EAAE,OAAO;QACjB,QAAQ,EAAE,MAAM;QAChB,cAAc,EAAE,SAAS;KAC1B,CAAC;IACF,IAAI,KAAK,GAAG,MAAM,mBAAmB,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACnD,IAAI,CAAC,KAAK,IAAI,QAAQ,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;QACjD,KAAK,GAAG,MAAM,oBAAoB,CAChC;YACE,GAAG,GAAG;YACN,gBAAgB,EAAE,YAAY,CAAC,gBAAgB;YAC/C,KAAK,EAAE,YAAY,CAAC,KAAK;YACzB,KAAK,EAAE,YAAY,CAAC,KAAK;YACzB,cAAc,EAAE,YAAY,CAAC,EAAE;SAChC,EACD,MAAM,CACP,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;IAC7E,MAAM,QAAQ,GAAG,8BAA8B,CAAC,KAAK,EAAE;QACrD,yEAAyE;QACzE,uEAAuE;QACvE,SAAS,EACP,QAAQ,CAAC,WAAW,KAAK,SAAS;YAClC,QAAQ,CAAC,WAAW,KAAK,cAAc;KAC1C,CAAC,CAAC;IACH,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,gCAAgC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IACrE,CAAC;IACD,QAAQ,CAAC,kBAAkB,GAAG,KAAK,CAAC,EAAE,CAAC;IACvC,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;QACvB,QAAQ,CAAC,eAAe,CAAC,YAAY,GAAG,KAAK,CAAC,YAAY,CAAC;IAC7D,CAAC;IACD,QAAQ,CAAC,UAAU,GAAG;QACpB,UAAU,EACR,YAAY,CAAC,KAAK,KAAK,OAAO;YAC5B,CAAC,CAAC,OAAO;YACT,CAAC,CAAC,YAAY,CAAC,KAAK,KAAK,iBAAiB;gBACxC,CAAC,CAAC,UAAU;gBACZ,CAAC,CAAC,YAAY,CAAC,KAAK,KAAK,SAAS;oBAChC,CAAC,CAAC,QAAQ;oBACV,CAAC,CAAC,SAAS;QACnB,QAAQ,EAAE,YAAY,CAAC,KAAK,KAAK,SAAS;KAC3C,CAAC;IACF,OAAO;QACL,UAAU,EAAE,KAAK,CAAC,iBAAiB;QACnC,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,aAAa,EAAE,SAAS;QACxB,cAAc,EAAE,YAAY,CAAC,EAAE;QAC/B,OAAO,EAAE,KAAK,CAAC,EAAE;KAClB,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,QAAyB,EACzB,OAAwB;IAExB,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IACrC,MAAM,WAAW,GACf,aAAa,CAAC,QAAQ,CAAC,eAAe,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC;IAC7D,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;IACxE,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,cAAc,GAAG,QAEtB,CAAC;QACF,cAAc,CAAC,WAAW,KAAK,8BAA8B,CAAC,OAAO,CAAC,CAAC;QACvE,IACE,QAAQ,CAAC,QAAQ,KAAK,OAAO;YAC7B,QAAQ,CAAC,WAAW,KAAK,IAAI;YAC7B,QAAQ,CAAC,eAAe,CAAC,oBAAoB,KAAK,IAAI,EACtD,CAAC;YACD,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,YAAY,EAAE,8BAA8B,EAAE;aAC/C,CAAC;QACJ,CAAC;QACD,IAAI,4BAA4B,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC3C,MAAM,KAAK,GAAG,MAAM,kBAAkB,CACpC,QAAQ,CAAC,QAAQ,EACjB,sBAAsB,CAAC,QAAQ,CAAC,EAChC,EAAE,mBAAmB,EAAE,IAAI,EAAE,CAC9B,CAAC;YACF,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,OAAO;oBACL,OAAO,EAAE,IAAI;oBACb,YAAY,EAAE,iCAAiC,EAAE;iBAClD,CAAC;YACJ,CAAC;QACH,CAAC;QACD,OAAO,uBAAuB,CAAC,QAAQ,EAAE,OAAO,EAAE;YAChD,YAAY,EAAE,GAAG,EAAE,CAAC,oBAAoB,CAAC,QAAQ,CAAC;SACnD,CAAC,CAAC;IACL,CAAC;IAED,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,gBAAgB,CAAC;YACnC,QAAQ,EAAE,QAAQ,CAAC,QAAQ;YAC3B,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;YACf,cAAc,EAAE,sBAAsB,CAAC,QAAQ,CAAC;YAChD,gBAAgB,EAAE,QAAQ,CAAC,UAAU,IAAI,IAAI;SAC9C,CAAC,CAAC;QACH,OAAO;YACL,OAAO,EAAE,IAAI;YACb,YAAY,EAAE,+BAA+B,QAAQ,CAAC,QAAQ,sBAAsB,KAAK,+BAA+B;SACzH,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,OAAO,EAAE,IAAI;YACb,YAAY,EACV,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,8BAA8B;SAC1E,CAAC;IACJ,CAAC;AACH,CAAC","sourcesContent":["import crypto from \"node:crypto\";\n\nimport {\n evaluateIntegrationScopePolicy,\n getActiveIntegrationInstallationByKey,\n getIntegrationScope,\n resolveIntegrationTokenBundle,\n saveIntegrationScope,\n slackInstallationKey,\n} from \"@agent-native/core/integrations\";\nimport { resolveOrgIdForEmail } from \"@agent-native/core/org\";\nimport {\n readDeployCredentialEnv,\n resolveSecret,\n withConfiguredAppBasePath,\n} from \"@agent-native/core/server\";\nimport type {\n IncomingMessage,\n IntegrationExecutionContext,\n PlatformAdapter,\n} from \"@agent-native/core/server\";\n\nimport { handleRemoteCodeCommand } from \"./dispatch-remote-commands.js\";\nimport {\n dispatchIntegrationRoutingHint,\n type DispatchIntegrationRoutingHint,\n} from \"./dispatch-routing.js\";\nimport { consumeLinkToken, resolveLinkedOwner } from \"./dispatch-store.js\";\n\ntype SlackSenderProfile = {\n email: string | null;\n name: string | null;\n trust: \"trusted\" | \"guest\" | \"external_shared\" | \"unknown\";\n};\n\nconst slackProfileCache = new Map<\n string,\n { profile: SlackSenderProfile; expiresAt: number }\n>();\nconst SLACK_PROFILE_CACHE_TTL = 10 * 60 * 1000;\n\nfunction contextString(value: unknown): string | null {\n if (typeof value === \"string\" && value.trim()) return value.trim();\n if (typeof value === \"number\" && Number.isFinite(value)) return String(value);\n return null;\n}\n\nexport function identityKeyForIncoming(\n incoming: IncomingMessage,\n): string | null {\n const senderId = contextString(incoming.senderId);\n if (!senderId) return null;\n\n if (incoming.platform === \"slack\") {\n const teamId = contextString(incoming.platformContext.teamId);\n return teamId ? `${teamId}:${senderId}` : senderId;\n }\n\n if (incoming.platform === \"whatsapp\") {\n const phoneNumberId = contextString(incoming.platformContext.phoneNumberId);\n return phoneNumberId ? `${phoneNumberId}:${senderId}` : senderId;\n }\n\n if (incoming.platform === \"email\") {\n return senderId.toLowerCase();\n }\n\n return senderId;\n}\n\nfunction fallbackOwnerForIncoming(incoming: IncomingMessage): string {\n const tenant =\n contextString(incoming.platformContext.teamId) ||\n contextString(incoming.platformContext.phoneNumberId) ||\n contextString(incoming.platformContext.chatId) ||\n contextString(incoming.platformContext.from) ||\n incoming.externalThreadId;\n const raw = `${incoming.platform}:${tenant}:${incoming.senderId || \"\"}`;\n const hash = crypto\n .createHash(\"sha256\")\n .update(raw)\n .digest(\"hex\")\n .slice(0, 16);\n return `dispatch+${hash}@integration.local`;\n}\n\nfunction configuredDefaultOwnerForIncoming(\n incoming: IncomingMessage,\n): string | null {\n // This is intentionally Slack-only: a deployment-wide default owner grants\n // that Slack workspace access to the owner's connected agents and org\n // credentials, so other platforms should opt in with explicit identity links.\n if (incoming.platform !== \"slack\") return null;\n const email = process.env.DISPATCH_DEFAULT_OWNER_EMAIL?.trim();\n if (!email) return null;\n return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email) ? email : null;\n}\n\nfunction platformRequiresExplicitLink(incoming: IncomingMessage): boolean {\n // Telegram does not provide a verified email address. Require an explicit\n // identity link before it can act as a Builder/Agent-Native user.\n return incoming.platform === \"telegram\";\n}\n\nfunction configuredDispatchIdentitiesUrl(): string | null {\n const raw =\n process.env.WORKSPACE_GATEWAY_URL ||\n process.env.APP_URL ||\n process.env.URL ||\n process.env.DEPLOY_URL ||\n process.env.BETTER_AUTH_URL ||\n \"\";\n if (!raw.trim()) return null;\n const base = /^https?:\\/\\//i.test(raw) ? raw : `https://${raw}`;\n try {\n return new URL(\n \"identities\",\n `${withConfiguredAppBasePath(base).replace(/\\/+$/, \"\")}/`,\n ).toString();\n } catch {\n return null;\n }\n}\n\nasync function resolveManagedSlackInstallation(incoming: IncomingMessage) {\n if (incoming.platform !== \"slack\") return null;\n const teamId = contextString(incoming.platformContext.teamId);\n const apiAppId = contextString(incoming.platformContext.apiAppId);\n if (!teamId) return null;\n try {\n return await getActiveIntegrationInstallationByKey(\n \"slack\",\n slackInstallationKey({ teamId, apiAppId }),\n );\n } catch {\n return null;\n }\n}\n\nasync function resolveManagedSlackToken(\n incoming: IncomingMessage,\n): Promise<string | null> {\n const installation = await resolveManagedSlackInstallation(incoming);\n if (!installation) return null;\n const bundle = await resolveIntegrationTokenBundle(\n \"slack\",\n installation.installationKey,\n );\n return bundle?.accessToken ?? null;\n}\n\nfunction formatTelegramLinkRequiredMessage(): string {\n const identitiesUrl = configuredDispatchIdentitiesUrl();\n const linkStep = identitiesUrl\n ? `Tap ${identitiesUrl}, create a Telegram link token, then send \\`/link <token>\\` here.`\n : \"Open Dispatch while signed in, create a Telegram link token, then send `/link <token>` here.\";\n return `Telegram is connected, but this Telegram account is not linked to an Agent-Native user yet. ${linkStep} After that I can use your Builder.io org and connected apps.`;\n}\n\nfunction formatSlackLinkRequiredMessage(): string {\n const identitiesUrl = configuredDispatchIdentitiesUrl();\n const linkStep = identitiesUrl\n ? `Open ${identitiesUrl}, create a Slack link token, then send \\`/link <token>\\` in this DM.`\n : \"Open Dispatch while signed in, create a Slack link token, then send `/link <token>` in this DM.\";\n return `Agent Native is ready, but this Slack account is not linked to an Agent Native user yet. ${linkStep}`;\n}\n\nasync function resolveSlackSenderProfile(\n incoming: IncomingMessage,\n): Promise<SlackSenderProfile> {\n if (incoming.platform !== \"slack\") {\n return { email: null, name: null, trust: \"unknown\" };\n }\n const managedToken = await resolveManagedSlackToken(incoming);\n const token =\n managedToken ??\n (await resolveSecret(\"SLACK_BOT_TOKEN\")) ??\n readDeployCredentialEnv(\"SLACK_BOT_TOKEN\");\n const userId = contextString(incoming.senderId);\n const teamId = contextString(incoming.platformContext.teamId);\n if (!token || !userId) {\n return { email: null, name: null, trust: \"unknown\" };\n }\n\n // Slack user IDs are scoped per workspace, so without a teamId we can't\n // safely cache: two installs of the bot in different workspaces could\n // share user-id strings and collide on a single \"default\" key. Skip the\n // cache (and lookup on every request) when teamId is missing.\n const cacheKey = teamId ? `${teamId}:${userId}` : null;\n if (cacheKey) {\n const cached = slackProfileCache.get(cacheKey);\n if (cached && cached.expiresAt > Date.now()) return cached.profile;\n }\n\n try {\n const params = new URLSearchParams({ user: userId });\n const res = await fetch(`https://slack.com/api/users.info?${params}`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n const data = (await res.json()) as {\n ok?: boolean;\n user?: {\n real_name?: string;\n name?: string;\n profile?: {\n email?: string;\n real_name?: string;\n display_name?: string;\n };\n is_restricted?: boolean;\n is_ultra_restricted?: boolean;\n };\n };\n const profile = data.ok\n ? {\n email: data.user?.profile?.email?.trim().toLowerCase() || null,\n name:\n data.user?.profile?.real_name?.trim() ||\n data.user?.profile?.display_name?.trim() ||\n data.user?.real_name?.trim() ||\n data.user?.name?.trim() ||\n null,\n trust:\n data.user?.is_ultra_restricted === true\n ? (\"external_shared\" as const)\n : data.user?.is_restricted === true\n ? (\"guest\" as const)\n : (\"trusted\" as const),\n }\n : { email: null, name: null, trust: \"unknown\" as const };\n if (cacheKey) {\n slackProfileCache.set(cacheKey, {\n profile,\n expiresAt: Date.now() + SLACK_PROFILE_CACHE_TTL,\n });\n }\n return profile;\n } catch {\n return { email: null, name: null, trust: \"unknown\" };\n }\n}\n\nasync function resolveSlackOwnerFromVerifiedEmail(\n incoming: IncomingMessage,\n): Promise<string | null> {\n const profile = await resolveSlackSenderProfile(incoming);\n if (!profile.email) return null;\n\n incoming.senderEmail = profile.email;\n incoming.platformContext.senderEmail = profile.email;\n if (profile.name) {\n incoming.senderName = profile.name;\n incoming.platformContext.senderName = profile.name;\n }\n\n const orgId = await resolveOrgIdForEmail(profile.email);\n return orgId ? profile.email : null;\n}\n\nasync function resolveSlackConversationTrust(\n incoming: IncomingMessage,\n actorTrust: SlackSenderProfile[\"trust\"],\n): Promise<{\n trust: SlackSenderProfile[\"trust\"];\n conversationType: \"channel\" | \"direct_message\" | \"group_direct_message\";\n}> {\n if (incoming.triggerKind === \"dm\") {\n return { trust: actorTrust, conversationType: \"direct_message\" };\n }\n const token = await resolveManagedSlackToken(incoming);\n const channel = contextString(incoming.platformContext.channelId);\n if (!token || !channel) {\n return { trust: \"unknown\", conversationType: \"channel\" };\n }\n try {\n const params = new URLSearchParams({ channel });\n const response = await fetch(\n `https://slack.com/api/conversations.info?${params}`,\n { headers: { Authorization: `Bearer ${token}` } },\n );\n const data = (await response.json()) as {\n ok?: boolean;\n channel?: {\n is_ext_shared?: boolean;\n is_im?: boolean;\n is_mpim?: boolean;\n };\n };\n if (!data.ok) {\n return { trust: \"unknown\", conversationType: \"channel\" };\n }\n return {\n trust: data.channel?.is_ext_shared ? \"external_shared\" : actorTrust,\n conversationType: data.channel?.is_im\n ? \"direct_message\"\n : data.channel?.is_mpim\n ? \"group_direct_message\"\n : \"channel\",\n };\n } catch {\n return { trust: \"unknown\", conversationType: \"channel\" };\n }\n}\n\nexport async function resolveDispatchOwner(\n incoming: IncomingMessage,\n): Promise<string> {\n try {\n const externalUserId = identityKeyForIncoming(incoming);\n\n // Webhooks do not have the browser request's org context, so allow a safe\n // cross-org fallback when the linked platform identity maps to one owner.\n const owner = await resolveLinkedOwner(incoming.platform, externalUserId, {\n allowAnyOrgFallback: true,\n });\n if (owner) return owner;\n\n // For email, the sender's `From:` address is attacker-settable: SMTP lets\n // anyone claim any From, and our inbound webhook secret only authenticates\n // the provider→app hop, not the original sender. So we must NOT grant a\n // real user's identity (their API keys, org secrets, personal\n // instructions, ownable data) off the bare From. Mirror the Slack gate:\n // only return the sender email as the acting owner when BOTH\n // (a) the message is DKIM/SPF-verified for the From domain, AND\n // (b) that email maps to a real org member.\n // Otherwise fall through to the synthetic, credential-less fallback owner.\n // (A linked identity, handled by resolveLinkedOwner above, remains an\n // always-allowed way to bind an address regardless of verification.)\n //\n // Escape hatch: set DISPATCH_TRUST_UNVERIFIED_EMAIL_SENDER=1 to restore\n // the legacy \"trust the From header\" behavior. OFF by default; only use\n // this if you fully control the inbound mail path and accept that a\n // spoofed From can act as any org member. See FINDING 3 (inbound-email\n // impersonation) in the webhook security audit.\n if (\n incoming.platform === \"email\" &&\n incoming.senderId &&\n incoming.senderId.includes(\"@\")\n ) {\n if (process.env.DISPATCH_TRUST_UNVERIFIED_EMAIL_SENDER === \"1\") {\n return incoming.senderId;\n }\n if (incoming.senderVerified) {\n const orgId = await resolveOrgIdForEmail(incoming.senderId);\n if (orgId) return incoming.senderId;\n }\n // Unverified or not an org member — do not impersonate. Fall through to\n // the synthetic fallback owner below.\n }\n\n // Slack gives us a user id in the event payload. Resolve it to a verified\n // workspace email and use that user's own org context when they are an\n // Agent-Native member, so artifacts created via @agent-native are visible\n // when they open the target app.\n if (incoming.platform === \"slack\") {\n const slackOwner = await resolveSlackOwnerFromVerifiedEmail(incoming);\n if (slackOwner) return slackOwner;\n }\n\n const defaultOwner = configuredDefaultOwnerForIncoming(incoming);\n if (defaultOwner) return defaultOwner;\n\n return fallbackOwnerForIncoming(incoming);\n } catch {\n const defaultOwner = configuredDefaultOwnerForIncoming(incoming);\n if (defaultOwner) return defaultOwner;\n return fallbackOwnerForIncoming(incoming);\n }\n}\n\n/**\n * Resolve a personal DM user or a workspace-qualified channel service\n * principal. Managed Slack channels never silently inherit one requester's\n * personal credentials or memory.\n */\nexport async function resolveDispatchExecutionContext(\n incoming: IncomingMessage,\n): Promise<IntegrationExecutionContext> {\n if (incoming.platform !== \"slack\" || incoming.triggerKind === \"dm\") {\n const ownerEmail = await resolveDispatchOwner(incoming);\n if (\n incoming.platform === \"slack\" &&\n ownerEmail.endsWith(\"@integration.local\")\n ) {\n // Preserve a credential-less principal long enough for beforeProcess to\n // deliver linking guidance or consume `/link <token>`. Never run the\n // agent under this synthetic owner.\n incoming.platformContext.identityLinkRequired = true;\n }\n return {\n ownerEmail,\n orgId: (await resolveOrgIdForEmail(ownerEmail)) ?? null,\n principalType: \"user\",\n };\n }\n\n const installation = await resolveManagedSlackInstallation(incoming);\n if (!installation) {\n // Preserve the legacy manually configured app path while managed installs\n // roll out. Its behavior remains explicit and visible in Settings.\n const ownerEmail = await resolveDispatchOwner(incoming);\n return {\n ownerEmail,\n orgId: (await resolveOrgIdForEmail(ownerEmail)) ?? null,\n principalType: \"user\",\n };\n }\n\n const teamId = contextString(incoming.platformContext.teamId);\n const channelId = contextString(incoming.platformContext.channelId);\n if (!teamId || !channelId) {\n throw new Error(\"Slack channel identity is incomplete\");\n }\n const profile = await resolveSlackSenderProfile(incoming);\n const conversation = await resolveSlackConversationTrust(\n incoming,\n profile.trust,\n );\n const access = {\n ownerEmail: installation.ownerEmail,\n orgId: installation.orgId,\n };\n const key = {\n platform: \"slack\",\n tenantId: teamId,\n conversationId: channelId,\n };\n let scope = await getIntegrationScope(key, access);\n if (!scope && incoming.triggerKind === \"mention\") {\n scope = await saveIntegrationScope(\n {\n ...key,\n conversationType: conversation.conversationType,\n trust: conversation.trust,\n orgId: installation.orgId,\n installationId: installation.id,\n },\n access,\n );\n }\n if (!scope) throw new Error(\"Slack channel is not enabled for Agent Native\");\n const decision = evaluateIntegrationScopePolicy(scope, {\n // Thread replies reached this point only after the adapter's active-task\n // gate, so they are continuation steering rather than ambient chatter.\n mentioned:\n incoming.triggerKind === \"mention\" ||\n incoming.triggerKind === \"thread_reply\",\n });\n if (!decision.allowed) {\n throw new Error(`Slack channel policy denied: ${decision.reason}`);\n }\n incoming.integrationScopeId = scope.id;\n if (scope.defaultModel) {\n incoming.platformContext.defaultModel = scope.defaultModel;\n }\n incoming.actorTrust = {\n memberType:\n conversation.trust === \"guest\"\n ? \"guest\"\n : conversation.trust === \"external_shared\"\n ? \"external\"\n : conversation.trust === \"trusted\"\n ? \"member\"\n : \"unknown\",\n verified: conversation.trust !== \"unknown\",\n };\n return {\n ownerEmail: scope.serviceOwnerEmail,\n orgId: scope.orgId,\n principalType: \"service\",\n installationId: installation.id,\n scopeId: scope.id,\n };\n}\n\nexport async function beforeDispatchProcess(\n incoming: IncomingMessage,\n adapter: PlatformAdapter,\n): Promise<{ handled: true; responseText?: string } | { handled: false }> {\n const trimmed = incoming.text.trim();\n const commandText =\n contextString(incoming.platformContext.rawText) || trimmed;\n const match = commandText.match(/^\\/link(?:@\\w+)?\\s+([a-zA-Z0-9_-]+)$/);\n if (!match) {\n const routedIncoming = incoming as IncomingMessage & {\n routingHint?: DispatchIntegrationRoutingHint;\n };\n routedIncoming.routingHint ??= dispatchIntegrationRoutingHint(trimmed);\n if (\n incoming.platform === \"slack\" &&\n incoming.triggerKind === \"dm\" &&\n incoming.platformContext.identityLinkRequired === true\n ) {\n return {\n handled: true,\n responseText: formatSlackLinkRequiredMessage(),\n };\n }\n if (platformRequiresExplicitLink(incoming)) {\n const owner = await resolveLinkedOwner(\n incoming.platform,\n identityKeyForIncoming(incoming),\n { allowAnyOrgFallback: true },\n );\n if (!owner) {\n return {\n handled: true,\n responseText: formatTelegramLinkRequiredMessage(),\n };\n }\n }\n return handleRemoteCodeCommand(incoming, adapter, {\n resolveOwner: () => resolveDispatchOwner(incoming),\n });\n }\n\n try {\n const owner = await consumeLinkToken({\n platform: incoming.platform,\n token: match[1],\n externalUserId: identityKeyForIncoming(incoming),\n externalUserName: incoming.senderName || null,\n });\n return {\n handled: true,\n responseText: `Linked successfully. Future ${incoming.platform} messages will use ${owner}'s personal dispatch context.`,\n };\n } catch (error) {\n return {\n handled: true,\n responseText:\n error instanceof Error ? error.message : \"Failed to link this account.\",\n };\n }\n}\n"]}
@@ -0,0 +1,6 @@
1
+ export interface DispatchIntegrationRoutingHint {
2
+ targetAgent?: string;
3
+ instruction: string;
4
+ }
5
+ export declare function dispatchIntegrationRoutingHint(text: string): DispatchIntegrationRoutingHint | undefined;
6
+ //# sourceMappingURL=dispatch-routing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatch-routing.d.ts","sourceRoot":"","sources":["../../../src/server/lib/dispatch-routing.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,8BAA8B;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,MAAM,GACX,8BAA8B,GAAG,SAAS,CAuB5C"}
@@ -0,0 +1,30 @@
1
+ const STRUCTURED_INTAKE_PATTERNS = [
2
+ /\b(?:file|submit|add|log|track|triage|prioriti[sz]e|create)\b.{0,64}\b(?:asks?|requests?|tickets?|tasks?|intake)\b/i,
3
+ /\b(?:asks?|requests?|tickets?|intake)\b.{0,64}\b(?:database|table|board|form|queue|priority|deadline|urgency)\b/i,
4
+ /\b(?:database|table|board|form|queue)\b.{0,64}\b(?:asks?|requests?|tickets?|intake|priority|deadline|urgency)\b/i,
5
+ ];
6
+ const VISUAL_DESIGN_PATTERNS = [
7
+ /\b(?:design|redesign|create|make|generate|mock(?:\s+up)?)\b.{0,64}\b(?:visual|mockup|wireframe|screen|interface|ui|website|landing\s+page|homepage|logo|graphic|illustration)\b/i,
8
+ /\b(?:visual|ui|website|product|brand)\s+design\b/i,
9
+ ];
10
+ export function dispatchIntegrationRoutingHint(text) {
11
+ const normalized = text.replace(/\s+/g, " ").trim();
12
+ if (!normalized)
13
+ return undefined;
14
+ // Route by the requested artifact type, not organization-specific names.
15
+ // Exact destinations, schemas, and required fields come from workspace
16
+ // resources such as shared LEARNINGS.md rather than this classifier.
17
+ if (STRUCTURED_INTAKE_PATTERNS.some((pattern) => pattern.test(normalized))) {
18
+ return {
19
+ instruction: "Resolve this structured-intake request from loaded workspace instructions/resources and discovered app capabilities. Follow any workspace-defined canonical destination and form contract; do not assume a particular app, database, schema, or owner. Preserve the source thread URL, submit once, verify the saved record, and return its exact link.",
20
+ };
21
+ }
22
+ if (VISUAL_DESIGN_PATTERNS.some((pattern) => pattern.test(normalized))) {
23
+ return {
24
+ targetAgent: "design",
25
+ instruction: "Delegate to Design because the requested output is a visual design, mockup, or interface rather than an intake record.",
26
+ };
27
+ }
28
+ return undefined;
29
+ }
30
+ //# sourceMappingURL=dispatch-routing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatch-routing.js","sourceRoot":"","sources":["../../../src/server/lib/dispatch-routing.ts"],"names":[],"mappings":"AAAA,MAAM,0BAA0B,GAAG;IACjC,qHAAqH;IACrH,kHAAkH;IAClH,kHAAkH;CACnH,CAAC;AAEF,MAAM,sBAAsB,GAAG;IAC7B,kLAAkL;IAClL,mDAAmD;CACpD,CAAC;AAOF,MAAM,UAAU,8BAA8B,CAC5C,IAAY;IAEZ,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACpD,IAAI,CAAC,UAAU;QAAE,OAAO,SAAS,CAAC;IAElC,yEAAyE;IACzE,uEAAuE;IACvE,qEAAqE;IACrE,IAAI,0BAA0B,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC;QAC3E,OAAO;YACL,WAAW,EACT,yVAAyV;SAC5V,CAAC;IACJ,CAAC;IAED,IAAI,sBAAsB,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC;QACvE,OAAO;YACL,WAAW,EAAE,QAAQ;YACrB,WAAW,EACT,wHAAwH;SAC3H,CAAC;IACJ,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC","sourcesContent":["const STRUCTURED_INTAKE_PATTERNS = [\n /\\b(?:file|submit|add|log|track|triage|prioriti[sz]e|create)\\b.{0,64}\\b(?:asks?|requests?|tickets?|tasks?|intake)\\b/i,\n /\\b(?:asks?|requests?|tickets?|intake)\\b.{0,64}\\b(?:database|table|board|form|queue|priority|deadline|urgency)\\b/i,\n /\\b(?:database|table|board|form|queue)\\b.{0,64}\\b(?:asks?|requests?|tickets?|intake|priority|deadline|urgency)\\b/i,\n];\n\nconst VISUAL_DESIGN_PATTERNS = [\n /\\b(?:design|redesign|create|make|generate|mock(?:\\s+up)?)\\b.{0,64}\\b(?:visual|mockup|wireframe|screen|interface|ui|website|landing\\s+page|homepage|logo|graphic|illustration)\\b/i,\n /\\b(?:visual|ui|website|product|brand)\\s+design\\b/i,\n];\n\nexport interface DispatchIntegrationRoutingHint {\n targetAgent?: string;\n instruction: string;\n}\n\nexport function dispatchIntegrationRoutingHint(\n text: string,\n): DispatchIntegrationRoutingHint | undefined {\n const normalized = text.replace(/\\s+/g, \" \").trim();\n if (!normalized) return undefined;\n\n // Route by the requested artifact type, not organization-specific names.\n // Exact destinations, schemas, and required fields come from workspace\n // resources such as shared LEARNINGS.md rather than this classifier.\n if (STRUCTURED_INTAKE_PATTERNS.some((pattern) => pattern.test(normalized))) {\n return {\n instruction:\n \"Resolve this structured-intake request from loaded workspace instructions/resources and discovered app capabilities. Follow any workspace-defined canonical destination and form contract; do not assume a particular app, database, schema, or owner. Preserve the source thread URL, submit once, verify the saved record, and return its exact link.\",\n };\n }\n\n if (VISUAL_DESIGN_PATTERNS.some((pattern) => pattern.test(normalized))) {\n return {\n targetAgent: \"design\",\n instruction:\n \"Delegate to Design because the requested output is a visual design, mockup, or interface rather than an intake record.\",\n };\n }\n\n return undefined;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"env-config.d.ts","sourceRoot":"","sources":["../../../src/server/lib/env-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAE9D,eAAO,MAAM,OAAO,EAAE,YAAY,EA8CjC,CAAC"}
1
+ {"version":3,"file":"env-config.d.ts","sourceRoot":"","sources":["../../../src/server/lib/env-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAE9D,eAAO,MAAM,OAAO,EAAE,YAAY,EAgGjC,CAAC"}
@@ -2,6 +2,16 @@ export const envKeys = [
2
2
  {
3
3
  key: "SLACK_BOT_TOKEN",
4
4
  label: "Slack bot token",
5
+ required: false,
6
+ },
7
+ {
8
+ key: "SLACK_CLIENT_ID",
9
+ label: "Slack OAuth client ID",
10
+ required: true,
11
+ },
12
+ {
13
+ key: "SLACK_CLIENT_SECRET",
14
+ label: "Slack OAuth client secret",
5
15
  required: true,
6
16
  },
7
17
  {
@@ -14,6 +24,41 @@ export const envKeys = [
14
24
  label: "Telegram bot token",
15
25
  required: true,
16
26
  },
27
+ {
28
+ key: "TELEGRAM_WEBHOOK_SECRET",
29
+ label: "Telegram webhook secret",
30
+ required: true,
31
+ },
32
+ {
33
+ key: "MICROSOFT_TEAMS_APP_ID",
34
+ label: "Microsoft Bot app ID",
35
+ required: false,
36
+ },
37
+ {
38
+ key: "MICROSOFT_TEAMS_APP_PASSWORD",
39
+ label: "Microsoft Bot client secret",
40
+ required: false,
41
+ },
42
+ {
43
+ key: "MICROSOFT_TEAMS_APP_TENANT_ID",
44
+ label: "Microsoft Bot tenant ID",
45
+ required: false,
46
+ },
47
+ {
48
+ key: "MICROSOFT_TEAMS_ALLOWED_TENANT_IDS",
49
+ label: "Allowed Microsoft Teams tenant IDs",
50
+ required: false,
51
+ },
52
+ {
53
+ key: "DISCORD_APPLICATION_ID",
54
+ label: "Discord application ID",
55
+ required: false,
56
+ },
57
+ {
58
+ key: "DISCORD_PUBLIC_KEY",
59
+ label: "Discord public key",
60
+ required: false,
61
+ },
17
62
  {
18
63
  key: "EMAIL_AGENT_ADDRESS",
19
64
  label: "Agent email address",
@@ -39,6 +84,11 @@ export const envKeys = [
39
84
  label: "WhatsApp phone number ID",
40
85
  required: false,
41
86
  },
87
+ {
88
+ key: "WHATSAPP_APP_SECRET",
89
+ label: "WhatsApp app secret",
90
+ required: false,
91
+ },
42
92
  {
43
93
  key: "PYLON_API_KEY",
44
94
  label: "Pylon API key",
@@ -1 +1 @@
1
- {"version":3,"file":"env-config.js","sourceRoot":"","sources":["../../../src/server/lib/env-config.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,OAAO,GAAmB;IACrC;QACE,GAAG,EAAE,iBAAiB;QACtB,KAAK,EAAE,iBAAiB;QACxB,QAAQ,EAAE,IAAI;KACf;IACD;QACE,GAAG,EAAE,sBAAsB;QAC3B,KAAK,EAAE,sBAAsB;QAC7B,QAAQ,EAAE,IAAI;KACf;IACD;QACE,GAAG,EAAE,oBAAoB;QACzB,KAAK,EAAE,oBAAoB;QAC3B,QAAQ,EAAE,IAAI;KACf;IACD;QACE,GAAG,EAAE,qBAAqB;QAC1B,KAAK,EAAE,qBAAqB;QAC5B,QAAQ,EAAE,KAAK;KAChB;IACD;QACE,GAAG,EAAE,8BAA8B;QACnC,KAAK,EAAE,2BAA2B;QAClC,QAAQ,EAAE,KAAK;KAChB;IACD;QACE,GAAG,EAAE,uBAAuB;QAC5B,KAAK,EAAE,uBAAuB;QAC9B,QAAQ,EAAE,KAAK;KAChB;IACD;QACE,GAAG,EAAE,uBAAuB;QAC5B,KAAK,EAAE,uBAAuB;QAC9B,QAAQ,EAAE,KAAK;KAChB;IACD;QACE,GAAG,EAAE,0BAA0B;QAC/B,KAAK,EAAE,0BAA0B;QACjC,QAAQ,EAAE,KAAK;KAChB;IACD;QACE,GAAG,EAAE,eAAe;QACpB,KAAK,EAAE,eAAe;QACtB,QAAQ,EAAE,KAAK;KAChB;CACF,CAAC","sourcesContent":["import type { EnvKeyConfig } from \"@agent-native/core/server\";\n\nexport const envKeys: EnvKeyConfig[] = [\n {\n key: \"SLACK_BOT_TOKEN\",\n label: \"Slack bot token\",\n required: true,\n },\n {\n key: \"SLACK_SIGNING_SECRET\",\n label: \"Slack signing secret\",\n required: true,\n },\n {\n key: \"TELEGRAM_BOT_TOKEN\",\n label: \"Telegram bot token\",\n required: true,\n },\n {\n key: \"EMAIL_AGENT_ADDRESS\",\n label: \"Agent email address\",\n required: false,\n },\n {\n key: \"DISPATCH_DEFAULT_OWNER_EMAIL\",\n label: \"Default Slack owner email\",\n required: false,\n },\n {\n key: \"WHATSAPP_ACCESS_TOKEN\",\n label: \"WhatsApp access token\",\n required: false,\n },\n {\n key: \"WHATSAPP_VERIFY_TOKEN\",\n label: \"WhatsApp verify token\",\n required: false,\n },\n {\n key: \"WHATSAPP_PHONE_NUMBER_ID\",\n label: \"WhatsApp phone number ID\",\n required: false,\n },\n {\n key: \"PYLON_API_KEY\",\n label: \"Pylon API key\",\n required: false,\n },\n];\n"]}
1
+ {"version":3,"file":"env-config.js","sourceRoot":"","sources":["../../../src/server/lib/env-config.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,OAAO,GAAmB;IACrC;QACE,GAAG,EAAE,iBAAiB;QACtB,KAAK,EAAE,iBAAiB;QACxB,QAAQ,EAAE,KAAK;KAChB;IACD;QACE,GAAG,EAAE,iBAAiB;QACtB,KAAK,EAAE,uBAAuB;QAC9B,QAAQ,EAAE,IAAI;KACf;IACD;QACE,GAAG,EAAE,qBAAqB;QAC1B,KAAK,EAAE,2BAA2B;QAClC,QAAQ,EAAE,IAAI;KACf;IACD;QACE,GAAG,EAAE,sBAAsB;QAC3B,KAAK,EAAE,sBAAsB;QAC7B,QAAQ,EAAE,IAAI;KACf;IACD;QACE,GAAG,EAAE,oBAAoB;QACzB,KAAK,EAAE,oBAAoB;QAC3B,QAAQ,EAAE,IAAI;KACf;IACD;QACE,GAAG,EAAE,yBAAyB;QAC9B,KAAK,EAAE,yBAAyB;QAChC,QAAQ,EAAE,IAAI;KACf;IACD;QACE,GAAG,EAAE,wBAAwB;QAC7B,KAAK,EAAE,sBAAsB;QAC7B,QAAQ,EAAE,KAAK;KAChB;IACD;QACE,GAAG,EAAE,8BAA8B;QACnC,KAAK,EAAE,6BAA6B;QACpC,QAAQ,EAAE,KAAK;KAChB;IACD;QACE,GAAG,EAAE,+BAA+B;QACpC,KAAK,EAAE,yBAAyB;QAChC,QAAQ,EAAE,KAAK;KAChB;IACD;QACE,GAAG,EAAE,oCAAoC;QACzC,KAAK,EAAE,oCAAoC;QAC3C,QAAQ,EAAE,KAAK;KAChB;IACD;QACE,GAAG,EAAE,wBAAwB;QAC7B,KAAK,EAAE,wBAAwB;QAC/B,QAAQ,EAAE,KAAK;KAChB;IACD;QACE,GAAG,EAAE,oBAAoB;QACzB,KAAK,EAAE,oBAAoB;QAC3B,QAAQ,EAAE,KAAK;KAChB;IACD;QACE,GAAG,EAAE,qBAAqB;QAC1B,KAAK,EAAE,qBAAqB;QAC5B,QAAQ,EAAE,KAAK;KAChB;IACD;QACE,GAAG,EAAE,8BAA8B;QACnC,KAAK,EAAE,2BAA2B;QAClC,QAAQ,EAAE,KAAK;KAChB;IACD;QACE,GAAG,EAAE,uBAAuB;QAC5B,KAAK,EAAE,uBAAuB;QAC9B,QAAQ,EAAE,KAAK;KAChB;IACD;QACE,GAAG,EAAE,uBAAuB;QAC5B,KAAK,EAAE,uBAAuB;QAC9B,QAAQ,EAAE,KAAK;KAChB;IACD;QACE,GAAG,EAAE,0BAA0B;QAC/B,KAAK,EAAE,0BAA0B;QACjC,QAAQ,EAAE,KAAK;KAChB;IACD;QACE,GAAG,EAAE,qBAAqB;QAC1B,KAAK,EAAE,qBAAqB;QAC5B,QAAQ,EAAE,KAAK;KAChB;IACD;QACE,GAAG,EAAE,eAAe;QACpB,KAAK,EAAE,eAAe;QACtB,QAAQ,EAAE,KAAK;KAChB;CACF,CAAC","sourcesContent":["import type { EnvKeyConfig } from \"@agent-native/core/server\";\n\nexport const envKeys: EnvKeyConfig[] = [\n {\n key: \"SLACK_BOT_TOKEN\",\n label: \"Slack bot token\",\n required: false,\n },\n {\n key: \"SLACK_CLIENT_ID\",\n label: \"Slack OAuth client ID\",\n required: true,\n },\n {\n key: \"SLACK_CLIENT_SECRET\",\n label: \"Slack OAuth client secret\",\n required: true,\n },\n {\n key: \"SLACK_SIGNING_SECRET\",\n label: \"Slack signing secret\",\n required: true,\n },\n {\n key: \"TELEGRAM_BOT_TOKEN\",\n label: \"Telegram bot token\",\n required: true,\n },\n {\n key: \"TELEGRAM_WEBHOOK_SECRET\",\n label: \"Telegram webhook secret\",\n required: true,\n },\n {\n key: \"MICROSOFT_TEAMS_APP_ID\",\n label: \"Microsoft Bot app ID\",\n required: false,\n },\n {\n key: \"MICROSOFT_TEAMS_APP_PASSWORD\",\n label: \"Microsoft Bot client secret\",\n required: false,\n },\n {\n key: \"MICROSOFT_TEAMS_APP_TENANT_ID\",\n label: \"Microsoft Bot tenant ID\",\n required: false,\n },\n {\n key: \"MICROSOFT_TEAMS_ALLOWED_TENANT_IDS\",\n label: \"Allowed Microsoft Teams tenant IDs\",\n required: false,\n },\n {\n key: \"DISCORD_APPLICATION_ID\",\n label: \"Discord application ID\",\n required: false,\n },\n {\n key: \"DISCORD_PUBLIC_KEY\",\n label: \"Discord public key\",\n required: false,\n },\n {\n key: \"EMAIL_AGENT_ADDRESS\",\n label: \"Agent email address\",\n required: false,\n },\n {\n key: \"DISPATCH_DEFAULT_OWNER_EMAIL\",\n label: \"Default Slack owner email\",\n required: false,\n },\n {\n key: \"WHATSAPP_ACCESS_TOKEN\",\n label: \"WhatsApp access token\",\n required: false,\n },\n {\n key: \"WHATSAPP_VERIFY_TOKEN\",\n label: \"WhatsApp verify token\",\n required: false,\n },\n {\n key: \"WHATSAPP_PHONE_NUMBER_ID\",\n label: \"WhatsApp phone number ID\",\n required: false,\n },\n {\n key: \"WHATSAPP_APP_SECRET\",\n label: \"WhatsApp app secret\",\n required: false,\n },\n {\n key: \"PYLON_API_KEY\",\n label: \"Pylon API key\",\n required: false,\n },\n];\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"onboarding-steps.d.ts","sourceRoot":"","sources":["../../../src/server/lib/onboarding-steps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAQH,wBAAgB,+BAA+B,IAAI,IAAI,CAgCtD"}
1
+ {"version":3,"file":"onboarding-steps.d.ts","sourceRoot":"","sources":["../../../src/server/lib/onboarding-steps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAaH,wBAAgB,+BAA+B,IAAI,IAAI,CA2DtD"}
@@ -8,7 +8,9 @@
8
8
  * the user has even created a real app. This step nudges them at adding
9
9
  * their first workspace app first.
10
10
  */
11
+ import { listIntegrationInstallations } from "@agent-native/core/integrations";
11
12
  import { registerOnboardingStep } from "@agent-native/core/onboarding";
13
+ import { getRequestOrgId, getRequestUserEmail, } from "@agent-native/core/server/request-context";
12
14
  import { listWorkspaceApps } from "./app-creation-store.js";
13
15
  let registered = false;
14
16
  export function registerDispatchOnboardingSteps() {
@@ -43,5 +45,28 @@ export function registerDispatchOnboardingSteps() {
43
45
  }
44
46
  },
45
47
  });
48
+ registerOnboardingStep({
49
+ id: "dispatch:connect-slack-workspace",
50
+ title: "Connect a Slack workspace",
51
+ description: "Install Agent Native with OAuth, then configure channel identities and access policies.",
52
+ order: 60,
53
+ required: false,
54
+ methods: [
55
+ {
56
+ id: "open-messaging",
57
+ kind: "link",
58
+ primary: true,
59
+ label: "Open Messaging",
60
+ payload: { url: "/messaging", external: false },
61
+ },
62
+ ],
63
+ isComplete: async () => {
64
+ const userEmail = getRequestUserEmail();
65
+ if (!userEmail)
66
+ return false;
67
+ const rows = await listIntegrationInstallations({ userEmail, orgId: getRequestOrgId() ?? null }, "slack").catch(() => []);
68
+ return rows.some((row) => row.status === "connected");
69
+ },
70
+ });
46
71
  }
47
72
  //# sourceMappingURL=onboarding-steps.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"onboarding-steps.js","sourceRoot":"","sources":["../../../src/server/lib/onboarding-steps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAEvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAE5D,IAAI,UAAU,GAAG,KAAK,CAAC;AAEvB,MAAM,UAAU,+BAA+B;IAC7C,IAAI,UAAU;QAAE,OAAO;IACvB,UAAU,GAAG,IAAI,CAAC;IAElB,sBAAsB,CAAC;QACrB,EAAE,EAAE,2BAA2B;QAC/B,KAAK,EAAE,uBAAuB;QAC9B,WAAW,EACT,mGAAmG;QACrG,KAAK,EAAE,CAAC;QACR,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE;YACP;gBACE,EAAE,EAAE,WAAW;gBACf,IAAI,EAAE,MAAM;gBACZ,OAAO,EAAE,IAAI;gBACb,KAAK,EAAE,aAAa;gBACpB,OAAO,EAAE,EAAE,GAAG,EAAE,gBAAgB,EAAE,QAAQ,EAAE,KAAK,EAAE;aACpD;SACF;QACD,UAAU,EAAE,KAAK,IAAI,EAAE;YACrB,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,MAAM,iBAAiB,CAAC;oBACnC,iBAAiB,EAAE,KAAK;oBACxB,eAAe,EAAE,IAAI;iBACtB,CAAC,CAAC;gBACH,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YAC7C,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC;KACF,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * Dispatch-specific onboarding steps.\n *\n * Slack/Telegram/etc. are auto-registered at order 60 by the framework when\n * their env keys are declared `required: true` in `env-config.ts`. Without\n * any earlier dispatch-specific step, a brand-new workspace lands on\n * \"Connect Slack\" as the first visible to-do — which is intimidating before\n * the user has even created a real app. This step nudges them at adding\n * their first workspace app first.\n */\n\nimport { registerOnboardingStep } from \"@agent-native/core/onboarding\";\n\nimport { listWorkspaceApps } from \"./app-creation-store.js\";\n\nlet registered = false;\n\nexport function registerDispatchOnboardingSteps(): void {\n if (registered) return;\n registered = true;\n\n registerOnboardingStep({\n id: \"dispatch:create-first-app\",\n title: \"Create your first app\",\n description:\n \"Add a workspace app like Mail, Calendar, or Slides — or describe a custom app from the Apps page.\",\n order: 5,\n required: false,\n methods: [\n {\n id: \"open-apps\",\n kind: \"link\",\n primary: true,\n label: \"Browse apps\",\n payload: { url: \"/dispatch/apps\", external: false },\n },\n ],\n isComplete: async () => {\n try {\n const apps = await listWorkspaceApps({\n includeAgentCards: false,\n includeArchived: true,\n });\n return apps.some((app) => !app.isDispatch);\n } catch {\n return false;\n }\n },\n });\n}\n"]}
1
+ {"version":3,"file":"onboarding-steps.js","sourceRoot":"","sources":["../../../src/server/lib/onboarding-steps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,4BAA4B,EAAE,MAAM,iCAAiC,CAAC;AAC/E,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EACL,eAAe,EACf,mBAAmB,GACpB,MAAM,2CAA2C,CAAC;AAEnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAE5D,IAAI,UAAU,GAAG,KAAK,CAAC;AAEvB,MAAM,UAAU,+BAA+B;IAC7C,IAAI,UAAU;QAAE,OAAO;IACvB,UAAU,GAAG,IAAI,CAAC;IAElB,sBAAsB,CAAC;QACrB,EAAE,EAAE,2BAA2B;QAC/B,KAAK,EAAE,uBAAuB;QAC9B,WAAW,EACT,mGAAmG;QACrG,KAAK,EAAE,CAAC;QACR,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE;YACP;gBACE,EAAE,EAAE,WAAW;gBACf,IAAI,EAAE,MAAM;gBACZ,OAAO,EAAE,IAAI;gBACb,KAAK,EAAE,aAAa;gBACpB,OAAO,EAAE,EAAE,GAAG,EAAE,gBAAgB,EAAE,QAAQ,EAAE,KAAK,EAAE;aACpD;SACF;QACD,UAAU,EAAE,KAAK,IAAI,EAAE;YACrB,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,MAAM,iBAAiB,CAAC;oBACnC,iBAAiB,EAAE,KAAK;oBACxB,eAAe,EAAE,IAAI;iBACtB,CAAC,CAAC;gBACH,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YAC7C,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC;KACF,CAAC,CAAC;IAEH,sBAAsB,CAAC;QACrB,EAAE,EAAE,kCAAkC;QACtC,KAAK,EAAE,2BAA2B;QAClC,WAAW,EACT,yFAAyF;QAC3F,KAAK,EAAE,EAAE;QACT,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE;YACP;gBACE,EAAE,EAAE,gBAAgB;gBACpB,IAAI,EAAE,MAAM;gBACZ,OAAO,EAAE,IAAI;gBACb,KAAK,EAAE,gBAAgB;gBACvB,OAAO,EAAE,EAAE,GAAG,EAAE,YAAY,EAAE,QAAQ,EAAE,KAAK,EAAE;aAChD;SACF;QACD,UAAU,EAAE,KAAK,IAAI,EAAE;YACrB,MAAM,SAAS,GAAG,mBAAmB,EAAE,CAAC;YACxC,IAAI,CAAC,SAAS;gBAAE,OAAO,KAAK,CAAC;YAC7B,MAAM,IAAI,GAAG,MAAM,4BAA4B,CAC7C,EAAE,SAAS,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,IAAI,EAAE,EAC/C,OAAO,CACR,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YAClB,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC;QACxD,CAAC;KACF,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * Dispatch-specific onboarding steps.\n *\n * Slack/Telegram/etc. are auto-registered at order 60 by the framework when\n * their env keys are declared `required: true` in `env-config.ts`. Without\n * any earlier dispatch-specific step, a brand-new workspace lands on\n * \"Connect Slack\" as the first visible to-do — which is intimidating before\n * the user has even created a real app. This step nudges them at adding\n * their first workspace app first.\n */\n\nimport { listIntegrationInstallations } from \"@agent-native/core/integrations\";\nimport { registerOnboardingStep } from \"@agent-native/core/onboarding\";\nimport {\n getRequestOrgId,\n getRequestUserEmail,\n} from \"@agent-native/core/server/request-context\";\n\nimport { listWorkspaceApps } from \"./app-creation-store.js\";\n\nlet registered = false;\n\nexport function registerDispatchOnboardingSteps(): void {\n if (registered) return;\n registered = true;\n\n registerOnboardingStep({\n id: \"dispatch:create-first-app\",\n title: \"Create your first app\",\n description:\n \"Add a workspace app like Mail, Calendar, or Slides — or describe a custom app from the Apps page.\",\n order: 5,\n required: false,\n methods: [\n {\n id: \"open-apps\",\n kind: \"link\",\n primary: true,\n label: \"Browse apps\",\n payload: { url: \"/dispatch/apps\", external: false },\n },\n ],\n isComplete: async () => {\n try {\n const apps = await listWorkspaceApps({\n includeAgentCards: false,\n includeArchived: true,\n });\n return apps.some((app) => !app.isDispatch);\n } catch {\n return false;\n }\n },\n });\n\n registerOnboardingStep({\n id: \"dispatch:connect-slack-workspace\",\n title: \"Connect a Slack workspace\",\n description:\n \"Install Agent Native with OAuth, then configure channel identities and access policies.\",\n order: 60,\n required: false,\n methods: [\n {\n id: \"open-messaging\",\n kind: \"link\",\n primary: true,\n label: \"Open Messaging\",\n payload: { url: \"/messaging\", external: false },\n },\n ],\n isComplete: async () => {\n const userEmail = getRequestUserEmail();\n if (!userEmail) return false;\n const rows = await listIntegrationInstallations(\n { userEmail, orgId: getRequestOrgId() ?? null },\n \"slack\",\n ).catch(() => []);\n return rows.some((row) => row.status === \"connected\");\n },\n });\n}\n"]}
@@ -1,10 +1,10 @@
1
1
  import { type ProviderApiDocsOptions, type ProviderApiId, type ProviderApiMethod, type ProviderApiRequestArgs } from "@agent-native/core/provider-api";
2
2
  export declare const DISPATCH_APP_ID = "dispatch";
3
- export declare const DISPATCH_PROVIDER_API_IDS: readonly ["amplitude", "apollo", "bigquery", "commonroom", "dataforseo", "ga4", "gcloud", "github", "figma", "gmail", "gong", "google_calendar", "google_drive", "granola", "grafana", "hubspot", "jira", "mixpanel", "notion", "posthog", "prometheus", "pylon", "sentry", "slack", "stripe", "twitter"];
3
+ export declare const DISPATCH_PROVIDER_API_IDS: readonly ["amplitude", "apollo", "bigquery", "clay", "commonroom", "dataforseo", "ga4", "gcloud", "github", "figma", "gmail", "gong", "google_calendar", "google_drive", "granola", "grafana", "hubspot", "jira", "mixpanel", "notion", "posthog", "prometheus", "pylon", "sentry", "slack", "stripe", "twitter"];
4
4
  export type DispatchProviderApiId = ProviderApiId;
5
5
  export type { ProviderApiMethod, ProviderApiRequestArgs };
6
6
  export declare function listProviderApiCatalog(provider?: string): {
7
- id: "amplitude" | "apollo" | "bigquery" | "commonroom" | "dataforseo" | "figma" | "ga4" | "gcloud" | "github" | "gmail" | "gong" | "google_calendar" | "google_drive" | "grafana" | "granola" | "hubspot" | "jira" | "mixpanel" | "notion" | "posthog" | "prometheus" | "pylon" | "sentry" | "slack" | "stripe" | "twitter";
7
+ id: "amplitude" | "apollo" | "bigquery" | "clay" | "commonroom" | "dataforseo" | "figma" | "ga4" | "gcloud" | "github" | "gmail" | "gong" | "google_calendar" | "google_drive" | "grafana" | "granola" | "hubspot" | "jira" | "mixpanel" | "notion" | "posthog" | "prometheus" | "pylon" | "sentry" | "slack" | "stripe" | "twitter";
8
8
  label: string;
9
9
  defaultBaseUrl: string;
10
10
  baseUrlCredentialKey: string;
@@ -1 +1 @@
1
- {"version":3,"file":"provider-api.d.ts","sourceRoot":"","sources":["../../../src/server/lib/provider-api.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,sBAAsB,EAE3B,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC5B,MAAM,iCAAiC,CAAC;AAGzC,eAAO,MAAM,eAAe,aAAa,CAAC;AAC1C,eAAO,MAAM,yBAAyB,2SAAmB,CAAC;AAC1D,MAAM,MAAM,qBAAqB,GAAG,aAAa,CAAC;AAClD,YAAY,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,CAAC;AA0C1D,wBAAgB,sBAAsB,CAAC,QAAQ,CAAC,EAAE,MAAM;;;;;;;;;;;;;;;;yBAEvD;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,sBAAsB,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,oBAGvD;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,sBAAsB,oBAErE"}
1
+ {"version":3,"file":"provider-api.d.ts","sourceRoot":"","sources":["../../../src/server/lib/provider-api.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,sBAAsB,EAE3B,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC5B,MAAM,iCAAiC,CAAC;AAGzC,eAAO,MAAM,eAAe,aAAa,CAAC;AAC1C,eAAO,MAAM,yBAAyB,mTAAmB,CAAC;AAC1D,MAAM,MAAM,qBAAqB,GAAG,aAAa,CAAC;AAClD,YAAY,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,CAAC;AA0C1D,wBAAgB,sBAAsB,CAAC,QAAQ,CAAC,EAAE,MAAM;;;;;;;;;;;;;;;;yBAEvD;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,sBAAsB,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,oBAGvD;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,sBAAsB,oBAErE"}
@@ -1 +1 @@
1
- {"version":3,"file":"integrations.d.ts","sourceRoot":"","sources":["../../../src/server/plugins/integrations.ts"],"names":[],"mappings":"AA8CA;;;;GAIG;AACH,QAAA,MAAM,0BAA0B,aAAoB,GAAG,kBAuBtD,CAAC;eAEa,0BAA0B"}
1
+ {"version":3,"file":"integrations.d.ts","sourceRoot":"","sources":["../../../src/server/plugins/integrations.ts"],"names":[],"mappings":"AAiDA;;;;GAIG;AACH,QAAA,MAAM,0BAA0B,aAAoB,GAAG,kBAuBtD,CAAC;eAEa,0BAA0B"}