@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,82 +1,38 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { agentNativePath } from "@agent-native/core/client";
3
- import { IconBrandSlack, IconBrandTelegram, IconBrandWhatsapp, IconCheck, IconChevronRight, IconCopy, IconExternalLink, IconInfoCircle, IconLoader2, IconMail, } from "@tabler/icons-react";
2
+ import { disconnectManagedIntegrationInstallation, listManagedIntegrationBudgets, listManagedIntegrationInstallations, listManagedIntegrationScopes, listIntegrationEnvStatuses, listIntegrationStatuses, managedIntegrationOAuthUrl, managedSlackAgentManifestUrl, saveIntegrationEnvVars, saveManagedIntegrationBudget, saveManagedIntegrationScope, setIntegrationEnabled, setupIntegration, testManagedIntegrationInstallation, useFormatters, useT, } from "@agent-native/core/client";
3
+ import { listBuiltInChannelIntegrations, } from "@agent-native/core/integrations";
4
+ import { IconBrandDiscord, IconBrandSlack, IconBrandTelegram, IconBrandTeams, IconBrandWhatsapp, IconCheck, IconChevronRight, IconCopy, IconExternalLink, IconFileDescription, IconInfoCircle, IconLoader2, IconMail, IconPlug, } from "@tabler/icons-react";
4
5
  import { useEffect, useMemo, useState } from "react";
5
6
  import { toast } from "sonner";
6
7
  import { Button } from "./ui/button.js";
7
8
  import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "./ui/collapsible.js";
8
9
  import { Input } from "./ui/input.js";
10
+ import { Switch } from "./ui/switch.js";
9
11
  import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip.js";
10
- const PLATFORM_DEFINITIONS = [
11
- {
12
- id: "slack",
13
- label: "Slack",
14
- icon: IconBrandSlack,
15
- description: "Receive mentions and DMs in one workspace-aware dispatch.",
16
- docsUrl: "/docs/messaging#slack",
17
- externalUrl: "https://api.slack.com/apps",
18
- externalLabel: "Open Slack apps",
19
- envKeys: ["SLACK_BOT_TOKEN", "SLACK_SIGNING_SECRET"],
20
- setupSteps: [
21
- "Create or open a Slack app at api.slack.com/apps.",
22
- "Save the bot token and signing secret below — the webhook URL appears once they're saved.",
23
- "Back in Slack, enable Event Subscriptions and paste the webhook URL.",
24
- "Subscribe to app_mention and message.im events, then install the app.",
25
- "Optional but recommended: Basic Information → Display Information → upload an app icon and pick a background color so the bot has a clean avatar in every channel.",
26
- ],
27
- },
28
- {
29
- id: "telegram",
30
- label: "Telegram",
31
- icon: IconBrandTelegram,
32
- description: "Chat with dispatch through a Telegram bot.",
33
- docsUrl: "/docs/messaging#telegram",
34
- externalUrl: "https://t.me/BotFather",
35
- externalLabel: "Open BotFather",
36
- envKeys: ["TELEGRAM_BOT_TOKEN"],
37
- setupSteps: [
38
- "Open @BotFather in Telegram and send /newbot.",
39
- "Save the bot token here, then click Set up webhook below.",
40
- "DM the bot in Telegram to test.",
41
- ],
42
- },
43
- {
44
- id: "email",
45
- label: "Email",
46
- icon: IconMail,
47
- description: "Give your agent an email address. People can email it directly or CC it on threads.",
48
- docsUrl: "/docs/messaging#email",
49
- externalUrl: "https://resend.com/webhooks",
50
- externalLabel: "Open Resend webhooks",
51
- envKeys: ["EMAIL_AGENT_ADDRESS"],
52
- setupSteps: [
53
- "Save your Resend or SendGrid API key (Vault or onboarding).",
54
- "Pick an email address — the easiest is a free <slug>.resend.app address.",
55
- "If using your own domain, add MX records pointing to your provider.",
56
- "Save the address here, then register the webhook URL below in Resend (event: email.received).",
57
- ],
58
- },
59
- {
60
- id: "whatsapp",
61
- label: "WhatsApp",
62
- icon: IconBrandWhatsapp,
63
- description: "Receive WhatsApp messages and reply through a Meta-managed phone number.",
64
- docsUrl: "/docs/messaging#whatsapp",
65
- externalUrl: "https://developers.facebook.com/apps",
66
- externalLabel: "Open Meta developer console",
67
- envKeys: [
68
- "WHATSAPP_ACCESS_TOKEN",
69
- "WHATSAPP_VERIFY_TOKEN",
70
- "WHATSAPP_PHONE_NUMBER_ID",
71
- ],
72
- setupSteps: [
73
- "Create a Meta app and add the WhatsApp product.",
74
- "Save the access token, verify token, and phone number ID below.",
75
- "In Meta's WhatsApp configuration, paste the webhook URL and your verify token.",
76
- "Subscribe to the messages field, then enable here.",
77
- ],
78
- },
79
- ];
12
+ const CHANNELS = listBuiltInChannelIntegrations();
13
+ const PLATFORM_ICONS = {
14
+ slack: IconBrandSlack,
15
+ "microsoft-teams": IconBrandTeams,
16
+ discord: IconBrandDiscord,
17
+ telegram: IconBrandTelegram,
18
+ whatsapp: IconBrandWhatsapp,
19
+ email: IconMail,
20
+ };
21
+ function hasMissingRequiredCredentials(credentials, envStatusByKey) {
22
+ const alternatives = new Map();
23
+ for (const credential of credentials) {
24
+ if (!credential.required)
25
+ continue;
26
+ if (!credential.alternativeGroup) {
27
+ if (!envStatusByKey.get(credential.key)?.configured)
28
+ return true;
29
+ continue;
30
+ }
31
+ const group = alternatives.get(credential.alternativeGroup) ?? [];
32
+ alternatives.set(credential.alternativeGroup, [...group, credential]);
33
+ }
34
+ return [...alternatives.values()].some((group) => group.every((credential) => !envStatusByKey.get(credential.key)?.configured));
35
+ }
80
36
  function HelpTooltip({ content }) {
81
37
  return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", className: "text-muted-foreground/60 hover:text-foreground cursor-pointer", children: _jsx(IconInfoCircle, { className: "h-3.5 w-3.5" }) }) }), _jsx(TooltipContent, { side: "top", className: "max-w-64 text-xs leading-relaxed", children: content })] }));
82
38
  }
@@ -107,6 +63,8 @@ function ConnectionStatus({ configured, enabled, }) {
107
63
  return _jsx(StatusPill, { tone: "neutral", label: "Not configured" });
108
64
  }
109
65
  export function MessagingSetupPanel() {
66
+ const t = useT();
67
+ const { formatDate } = useFormatters();
110
68
  const [statuses, setStatuses] = useState([]);
111
69
  const [loading, setLoading] = useState(true);
112
70
  const [envStatuses, setEnvStatuses] = useState([]);
@@ -116,12 +74,16 @@ export function MessagingSetupPanel() {
116
74
  const [togglingPlatform, setTogglingPlatform] = useState(null);
117
75
  const [setupPlatform, setSetupPlatform] = useState(null);
118
76
  const [copiedWebhook, setCopiedWebhook] = useState(null);
77
+ const [installations, setInstallations] = useState([]);
78
+ const [installationAction, setInstallationAction] = useState(null);
79
+ const [scopes, setScopes] = useState([]);
80
+ const [budgets, setBudgets] = useState([]);
81
+ const [scopeBudget, setScopeBudget] = useState({});
82
+ const [savingScope, setSavingScope] = useState(null);
119
83
  const refreshStatuses = async () => {
120
84
  setLoading(true);
121
85
  try {
122
- const res = await fetch(agentNativePath("/_agent-native/integrations/status"));
123
- const rows = res.ok ? await res.json() : [];
124
- setStatuses(Array.isArray(rows) ? rows : []);
86
+ setStatuses(await listIntegrationStatuses());
125
87
  }
126
88
  finally {
127
89
  setLoading(false);
@@ -129,11 +91,10 @@ export function MessagingSetupPanel() {
129
91
  };
130
92
  useEffect(() => {
131
93
  let active = true;
132
- fetch(agentNativePath("/_agent-native/integrations/status"))
133
- .then((res) => (res.ok ? res.json() : []))
94
+ listIntegrationStatuses()
134
95
  .then((rows) => {
135
96
  if (active) {
136
- setStatuses(Array.isArray(rows) ? rows : []);
97
+ setStatuses(rows);
137
98
  setLoading(false);
138
99
  }
139
100
  })
@@ -147,11 +108,34 @@ export function MessagingSetupPanel() {
147
108
  }, []);
148
109
  useEffect(() => {
149
110
  let active = true;
150
- fetch(agentNativePath("/_agent-native/env-status"))
151
- .then((res) => (res.ok ? res.json() : []))
111
+ listManagedIntegrationInstallations("slack")
112
+ .then((rows) => {
113
+ if (active)
114
+ setInstallations(rows);
115
+ })
116
+ .catch(() => { });
117
+ listManagedIntegrationScopes("slack")
118
+ .then((rows) => {
119
+ if (active)
120
+ setScopes(rows);
121
+ })
122
+ .catch(() => { });
123
+ listManagedIntegrationBudgets()
124
+ .then((rows) => {
125
+ if (active)
126
+ setBudgets(rows);
127
+ })
128
+ .catch(() => { });
129
+ return () => {
130
+ active = false;
131
+ };
132
+ }, []);
133
+ useEffect(() => {
134
+ let active = true;
135
+ listIntegrationEnvStatuses()
152
136
  .then((rows) => {
153
137
  if (active) {
154
- setEnvStatuses(Array.isArray(rows) ? rows : []);
138
+ setEnvStatuses(rows);
155
139
  setEnvLoading(false);
156
140
  }
157
141
  })
@@ -168,9 +152,7 @@ export function MessagingSetupPanel() {
168
152
  const refreshEnvStatus = async () => {
169
153
  setEnvLoading(true);
170
154
  try {
171
- const res = await fetch(agentNativePath("/_agent-native/env-status"));
172
- const rows = res.ok ? await res.json() : [];
173
- setEnvStatuses(Array.isArray(rows) ? rows : []);
155
+ setEnvStatuses(await listIntegrationEnvStatuses());
174
156
  }
175
157
  finally {
176
158
  setEnvLoading(false);
@@ -186,16 +168,8 @@ export function MessagingSetupPanel() {
186
168
  }
187
169
  setSavingKeysFor(platform.id);
188
170
  try {
189
- const res = await fetch(agentNativePath("/_agent-native/env-vars"), {
190
- method: "POST",
191
- headers: { "Content-Type": "application/json" },
192
- body: JSON.stringify({ vars }),
193
- });
194
- if (!res.ok) {
195
- const payload = await res.json().catch(() => ({}));
196
- throw new Error(payload.error || "Failed to save credentials");
197
- }
198
- toast.success(`${platform.label} credentials saved`);
171
+ await saveIntegrationEnvVars(vars);
172
+ toast.success(`${platform.name} credentials saved`);
199
173
  setEnvValues((current) => {
200
174
  const next = { ...current };
201
175
  for (const key of keys)
@@ -215,17 +189,10 @@ export function MessagingSetupPanel() {
215
189
  const togglePlatform = async (platform, enabled) => {
216
190
  setTogglingPlatform(platform.id);
217
191
  try {
218
- const action = enabled ? "disable" : "enable";
219
- const res = await fetch(agentNativePath(`/_agent-native/integrations/${platform.id}/${action}`), {
220
- method: "POST",
221
- });
222
- if (!res.ok) {
223
- const payload = await res.json().catch(() => ({}));
224
- throw new Error(payload.error || `Failed to ${action} ${platform.label}`);
225
- }
192
+ await setIntegrationEnabled(platform.id, !enabled);
226
193
  toast.success(enabled
227
- ? `${platform.label} disconnected`
228
- : `${platform.label} connected`);
194
+ ? `${platform.name} disconnected`
195
+ : `${platform.name} connected`);
229
196
  await refreshStatuses();
230
197
  }
231
198
  catch (error) {
@@ -238,22 +205,16 @@ export function MessagingSetupPanel() {
238
205
  const runSetup = async (platform) => {
239
206
  setSetupPlatform(platform.id);
240
207
  try {
241
- const res = await fetch(agentNativePath(`/_agent-native/integrations/${platform.id}/setup`), {
242
- method: "POST",
243
- });
244
- if (!res.ok) {
245
- const payload = await res.json().catch(() => ({}));
246
- throw new Error(payload.error || `Failed to set up ${platform.label}`);
247
- }
208
+ await setupIntegration(platform.id);
248
209
  toast.success(platform.id === "telegram"
249
210
  ? "Telegram webhook registered"
250
- : `${platform.label} setup complete`);
211
+ : `${platform.name} setup complete`);
251
212
  await refreshStatuses();
252
213
  }
253
214
  catch (error) {
254
215
  toast.error(error instanceof Error
255
216
  ? error.message
256
- : `Failed to set up ${platform.label}`);
217
+ : `Failed to set up ${platform.name}`);
257
218
  }
258
219
  finally {
259
220
  setSetupPlatform(null);
@@ -265,21 +226,136 @@ export function MessagingSetupPanel() {
265
226
  toast.success("Webhook URL copied");
266
227
  setTimeout(() => setCopiedWebhook(null), 1500);
267
228
  };
268
- return (_jsxs("div", { className: "space-y-4", children: [_jsx("div", { className: "grid gap-4 xl:grid-cols-2", children: PLATFORM_DEFINITIONS.map((platform) => {
229
+ const runInstallationAction = async (installation, action) => {
230
+ setInstallationAction(`${action}:${installation.id}`);
231
+ try {
232
+ await (action === "test"
233
+ ? testManagedIntegrationInstallation(installation.id)
234
+ : disconnectManagedIntegrationInstallation(installation.id));
235
+ setInstallations(await listManagedIntegrationInstallations("slack"));
236
+ toast.success(action === "test"
237
+ ? t("messaging.managed.connectionChecked")
238
+ : t("messaging.managed.workspaceDisconnected"));
239
+ }
240
+ catch (error) {
241
+ toast.error(error instanceof Error
242
+ ? error.message
243
+ : t("messaging.managed.actionFailed"));
244
+ }
245
+ finally {
246
+ setInstallationAction(null);
247
+ }
248
+ };
249
+ const updateScopePolicy = async (scope, policy) => {
250
+ setSavingScope(scope.id);
251
+ try {
252
+ await saveManagedIntegrationScope({
253
+ platform: scope.platform,
254
+ tenantId: scope.tenantId,
255
+ conversationId: scope.conversationId,
256
+ conversationType: scope.conversationType,
257
+ trust: scope.trust,
258
+ orgId: scope.orgId,
259
+ serviceOwnerEmail: scope.serviceOwnerEmail,
260
+ defaultModel: scope.defaultModel,
261
+ policy: { ...scope.policy, ...policy },
262
+ });
263
+ setScopes(await listManagedIntegrationScopes("slack"));
264
+ toast.success(t("messaging.managed.channelPolicyUpdated"));
265
+ }
266
+ catch (error) {
267
+ toast.error(error instanceof Error
268
+ ? error.message
269
+ : t("messaging.managed.policyUpdateFailed"));
270
+ }
271
+ finally {
272
+ setSavingScope(null);
273
+ }
274
+ };
275
+ const saveScopeBudget = async (scope) => {
276
+ const dollars = Number(scopeBudget[scope.id]);
277
+ if (!Number.isFinite(dollars) || dollars <= 0) {
278
+ toast.error(t("messaging.managed.positiveMonthlyBudget"));
279
+ return;
280
+ }
281
+ setSavingScope(`budget:${scope.id}`);
282
+ try {
283
+ await saveManagedIntegrationBudget({
284
+ subject: {
285
+ type: "scope",
286
+ scope: {
287
+ platform: scope.platform,
288
+ tenantId: scope.tenantId,
289
+ conversationId: scope.conversationId,
290
+ },
291
+ },
292
+ period: "month",
293
+ limitMicros: Math.round(dollars * 1_000_000),
294
+ });
295
+ setBudgets(await listManagedIntegrationBudgets());
296
+ toast.success(t("messaging.managed.channelBudgetSaved"));
297
+ }
298
+ catch (error) {
299
+ toast.error(error instanceof Error
300
+ ? error.message
301
+ : t("messaging.managed.budgetSaveFailed"));
302
+ }
303
+ finally {
304
+ setSavingScope(null);
305
+ }
306
+ };
307
+ return (_jsxs("div", { className: "space-y-4", children: [_jsx("div", { className: "grid gap-4 xl:grid-cols-2", children: CHANNELS.map((platform) => {
269
308
  const status = statusByPlatform.get(platform.id);
270
- const configured = !!status?.configured;
309
+ const configured = !!status?.configured ||
310
+ (platform.id === "slack" &&
311
+ installations.some((installation) => installation.status === "connected"));
271
312
  const enabled = !!status?.enabled;
272
- // Prefer adapter-supplied env keys (includes optional fields like
273
- // webhook secrets); fall back to the static list.
274
- const adapterKeys = status?.requiredEnvKeys;
275
- const envKeys = adapterKeys && adapterKeys.length > 0
276
- ? adapterKeys
277
- : platform.envKeys.map((key) => ({
278
- key,
279
- label: key,
280
- required: true,
281
- }));
282
- return (_jsxs("section", { className: "rounded-2xl border bg-card p-5", children: [_jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-xl border bg-muted/30 text-foreground", children: _jsx(platform.icon, { size: 18 }) }), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("h3", { className: "text-base font-semibold text-foreground", children: platform.label }), _jsx(ConnectionStatus, { configured: configured, enabled: enabled })] }), _jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: platform.description })] })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [_jsx(Button, { asChild: true, variant: "ghost", size: "sm", className: "h-7 px-2 text-xs text-muted-foreground", children: _jsxs("a", { href: platform.docsUrl, target: "_blank", rel: "noreferrer", children: ["Docs", _jsx(IconExternalLink, { className: "ml-1 h-3 w-3" })] }) }), platform.externalUrl ? (_jsx(Button, { asChild: true, variant: "ghost", size: "sm", className: "h-7 px-2 text-xs text-muted-foreground", children: _jsxs("a", { href: platform.externalUrl, target: "_blank", rel: "noreferrer", children: [platform.externalLabel ?? "Open", _jsx(IconExternalLink, { className: "ml-1 h-3 w-3" })] }) })) : null] })] }), _jsxs(Collapsible, { className: "mt-5", children: [_jsxs(CollapsibleTrigger, { className: "group flex w-full cursor-pointer items-center gap-1.5 text-xs font-medium text-muted-foreground hover:text-foreground", children: [_jsx(IconChevronRight, { className: "h-3.5 w-3.5 transition-transform group-data-[state=open]:rotate-90" }), _jsx("span", { children: "Setup steps" })] }), _jsx(CollapsibleContent, { children: _jsx("div", { className: "mt-2 rounded-xl border bg-muted/20 p-4", children: _jsx("ol", { className: "space-y-2 text-sm text-muted-foreground", children: platform.setupSteps.map((step, index) => (_jsxs("li", { className: "flex gap-2", children: [_jsxs("span", { className: "text-muted-foreground/60", children: [index + 1, "."] }), _jsx("span", { children: step })] }, step))) }) }) })] }), _jsxs("div", { className: "mt-4 space-y-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "text-sm font-medium text-foreground", children: "Credentials" }), envLoading ? (_jsx("span", { className: "text-xs text-muted-foreground", children: "Checking..." })) : null] }), _jsx("div", { className: "space-y-3", children: envKeys.map((envKey) => {
313
+ const envKeys = platform.credentialRequirements;
314
+ const missingRequiredCredentials = hasMissingRequiredCredentials(envKeys, envStatusByKey);
315
+ const Icon = PLATFORM_ICONS[platform.iconKey] ?? IconPlug;
316
+ return (_jsxs("section", { className: "rounded-2xl border bg-card p-5", children: [_jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-xl border bg-muted/30 text-foreground", children: _jsx(Icon, { size: 18 }) }), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("h3", { className: "text-base font-semibold text-foreground", children: platform.name }), _jsx(ConnectionStatus, { configured: configured, enabled: enabled })] }), _jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: platform.description })] })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [_jsx(Button, { asChild: true, variant: "ghost", size: "sm", className: "h-7 px-2 text-xs text-muted-foreground", children: _jsxs("a", { href: platform.documentation.href, target: "_blank", rel: "noreferrer", children: ["Docs", _jsx(IconExternalLink, { className: "ml-1 h-3 w-3" })] }) }), platform.documentation.externalHref ? (_jsx(Button, { asChild: true, variant: "ghost", size: "sm", className: "h-7 px-2 text-xs text-muted-foreground", children: _jsxs("a", { href: platform.documentation.externalHref, target: "_blank", rel: "noreferrer", children: [platform.documentation.externalLabel ?? "Open", _jsx(IconExternalLink, { className: "ml-1 h-3 w-3" })] }) })) : null] })] }), platform.id === "slack" ? (_jsxs("div", { className: "mt-5 space-y-3 rounded-xl border bg-muted/20 p-4", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsxs("div", { children: [_jsx("div", { className: "text-sm font-medium text-foreground", children: t("messaging.managed.title") }), _jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: t("messaging.managed.description") }), _jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: t("messaging.managed.agentManifestDescription") })] }), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsxs("a", { href: managedSlackAgentManifestUrl(), children: [_jsx(IconFileDescription, { className: "mr-2 h-4 w-4" }), t("messaging.managed.agentManifest")] }) }), missingRequiredCredentials ? (_jsxs(Button, { size: "sm", disabled: true, children: [_jsx(IconBrandSlack, { className: "mr-2 h-4 w-4" }), t("messaging.managed.addToSlack")] })) : (_jsx(Button, { asChild: true, size: "sm", children: _jsxs("a", { href: managedIntegrationOAuthUrl("slack"), children: [_jsx(IconBrandSlack, { className: "mr-2 h-4 w-4" }), t("messaging.managed.addToSlack")] }) }))] }), missingRequiredCredentials ? (_jsx("p", { className: "max-w-72 text-xs text-amber-700 dark:text-amber-300", children: t("messaging.managed.requiredCredentials") })) : null] })] }), installations.length ? (_jsx("div", { className: "space-y-2", children: installations.map((installation) => (_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 rounded-lg border bg-background/60 px-3 py-2", children: [_jsxs("div", { children: [_jsx("div", { className: "text-sm font-medium text-foreground", children: installation.teamName ||
317
+ installation.enterpriseName ||
318
+ installation.teamId ||
319
+ t("messaging.managed.workspaceFallback") }), _jsx("div", { className: "mt-0.5 text-xs text-muted-foreground", children: t("messaging.managed.scopesUpdated", {
320
+ count: installation.scopes.length,
321
+ date: formatDate(installation.updatedAt, {
322
+ dateStyle: "medium",
323
+ }),
324
+ }) })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(StatusPill, { tone: installation.health === "healthy"
325
+ ? "success"
326
+ : installation.health === "degraded" ||
327
+ installation.health === "revoked"
328
+ ? "warning"
329
+ : "neutral", label: t(`messaging.managed.health.${installation.health}`) }), _jsx(Button, { variant: "outline", size: "sm", onClick: () => runInstallationAction(installation, "test"), disabled: installationAction === `test:${installation.id}`, children: t("messaging.managed.test") }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => runInstallationAction(installation, "disconnect"), disabled: installationAction ===
330
+ `disconnect:${installation.id}`, children: t("messaging.managed.disconnect") })] })] }, installation.id))) })) : (_jsx("p", { className: "text-xs text-muted-foreground", children: t("messaging.managed.empty") }))] })) : null, platform.id === "slack" && scopes.length ? (_jsxs("div", { className: "mt-4 space-y-3 rounded-xl border bg-muted/20 p-4", children: [_jsxs("div", { children: [_jsx("div", { className: "text-sm font-medium text-foreground", children: t("messaging.managed.channelAccessTitle") }), _jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: t("messaging.managed.channelAccessDescription") })] }), scopes.map((scope) => {
331
+ const subjectId = JSON.stringify([
332
+ scope.platform,
333
+ scope.tenantId,
334
+ scope.conversationId,
335
+ ]);
336
+ const budget = budgets.find((item) => item.subjectType === "scope" &&
337
+ item.subjectId === subjectId &&
338
+ item.period === "month");
339
+ return (_jsxs("div", { className: "space-y-3 rounded-lg border bg-background/60 p-3", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsxs("div", { children: [_jsx("div", { className: "font-mono text-xs text-foreground", children: scope.conversationId }), _jsx("div", { className: "mt-0.5 text-xs text-muted-foreground", children: t("messaging.managed.isolatedIdentity", {
340
+ trust: t(`messaging.managed.trust.${scope.trust}`),
341
+ }) })] }), _jsx(StatusPill, { tone: scope.trust === "trusted" ? "success" : "warning", label: t(`messaging.managed.trust.${scope.trust}`) })] }), _jsx("div", { className: "grid gap-2 sm:grid-cols-3", children: [
342
+ [
343
+ "requireMention",
344
+ t("messaging.managed.requireMention"),
345
+ ],
346
+ ["allowGuests", t("messaging.managed.allowGuests")],
347
+ [
348
+ "allowExternalShared",
349
+ t("messaging.managed.allowSlackConnect"),
350
+ ],
351
+ ].map(([key, label]) => (_jsxs("label", { className: "flex items-center justify-between gap-2 rounded-md border px-2.5 py-2 text-xs", children: [label, _jsx(Switch, { checked: scope.policy[key], disabled: savingScope === scope.id, onCheckedChange: (checked) => updateScopePolicy(scope, { [key]: checked }) })] }, key))) }), _jsxs("div", { className: "flex flex-wrap items-end gap-2", children: [_jsxs("div", { className: "min-w-40 flex-1 space-y-1", children: [_jsx("label", { className: "text-xs font-medium text-foreground", children: t("messaging.managed.monthlyBudgetUsd") }), _jsx(Input, { inputMode: "decimal", value: scopeBudget[scope.id] ??
352
+ (budget
353
+ ? String(budget.limitMicros / 1_000_000)
354
+ : ""), onChange: (event) => setScopeBudget((current) => ({
355
+ ...current,
356
+ [scope.id]: event.target.value,
357
+ })), placeholder: "25" })] }), _jsx(Button, { variant: "outline", onClick: () => saveScopeBudget(scope), disabled: savingScope === `budget:${scope.id}`, children: t("messaging.managed.saveBudget") })] })] }, scope.id));
358
+ })] })) : null, _jsxs(Collapsible, { className: "mt-5", children: [_jsxs(CollapsibleTrigger, { className: "group flex w-full cursor-pointer items-center gap-1.5 text-xs font-medium text-muted-foreground hover:text-foreground", children: [_jsx(IconChevronRight, { className: "h-3.5 w-3.5 transition-transform group-data-[state=open]:rotate-90" }), _jsx("span", { children: "Setup steps" })] }), _jsx(CollapsibleContent, { children: _jsx("div", { className: "mt-2 rounded-xl border bg-muted/20 p-4", children: _jsx("ol", { className: "space-y-2 text-sm text-muted-foreground", children: platform.setup.steps.map((step, index) => (_jsxs("li", { className: "flex gap-2", children: [_jsxs("span", { className: "text-muted-foreground/60", children: [index + 1, "."] }), _jsx("span", { children: step })] }, step))) }) }) })] }), _jsxs("div", { className: "mt-4 space-y-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "text-sm font-medium text-foreground", children: "Credentials" }), envLoading ? (_jsx("span", { className: "text-xs text-muted-foreground", children: "Checking..." })) : null] }), _jsx("div", { className: "space-y-3", children: envKeys.map((envKey) => {
283
359
  const envStatus = envStatusByKey.get(envKey.key);
284
360
  const isConfigured = !!envStatus?.configured;
285
361
  const helpText = envKey.helpText ?? envStatus?.helpText;
@@ -293,7 +369,7 @@ export function MessagingSetupPanel() {
293
369
  })), placeholder: isPublicValue
294
370
  ? "agent@yourcompany.com"
295
371
  : `Enter ${label}`, autoComplete: "off" })) : null] }, envKey.key));
296
- }) }), envKeys.some((k) => !envStatusByKey.get(k.key)?.configured) ? (_jsx(Button, { variant: "outline", onClick: () => saveEnvKeys(platform, envKeys.map((k) => k.key)), disabled: savingKeysFor === platform.id, children: savingKeysFor === platform.id ? (_jsxs(_Fragment, { children: [_jsx(IconLoader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Saving..."] })) : ("Save credentials") })) : null] }), status?.webhookUrl ? (_jsxs("div", { className: "mt-4 space-y-2", children: [_jsx("div", { className: "text-sm font-medium text-foreground", children: "Webhook URL" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("code", { className: "flex-1 truncate rounded-md border bg-muted/30 px-3 py-2 text-xs text-foreground", children: status.webhookUrl }), _jsx(Button, { variant: "outline", size: "icon", onClick: () => copyWebhook(status.webhookUrl), "aria-label": `Copy ${platform.label} webhook URL`, children: copiedWebhook === status.webhookUrl ? (_jsx(IconCheck, { className: "h-4 w-4" })) : (_jsx(IconCopy, { className: "h-4 w-4" })) })] })] })) : null, _jsxs("div", { className: "mt-5 flex flex-wrap items-center justify-end gap-2 border-t border-border pt-4", children: [platform.id === "telegram" && configured ? (_jsx(Button, { variant: "outline", onClick: () => runSetup(platform), disabled: setupPlatform === platform.id, children: setupPlatform === platform.id ? (_jsxs(_Fragment, { children: [_jsx(IconLoader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Setting up..."] })) : ("Set up webhook") })) : null, !configured && !enabled ? (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { tabIndex: 0, children: _jsx(Button, { disabled: true, children: "Enable" }) }) }), _jsx(TooltipContent, { children: "Save the required credentials first." })] })) : (_jsx(Button, { onClick: () => togglePlatform(platform, enabled), disabled: togglingPlatform === platform.id, children: togglingPlatform === platform.id ? (_jsxs(_Fragment, { children: [_jsx(IconLoader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Saving..."] })) : enabled ? ("Disable") : ("Enable") }))] })] }, platform.id));
372
+ }) }), missingRequiredCredentials ? (_jsx(Button, { variant: "outline", onClick: () => saveEnvKeys(platform, envKeys.map((k) => k.key)), disabled: savingKeysFor === platform.id, children: savingKeysFor === platform.id ? (_jsxs(_Fragment, { children: [_jsx(IconLoader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Saving..."] })) : ("Save credentials") })) : null] }), status?.webhookUrl ? (_jsxs("div", { className: "mt-4 space-y-2", children: [_jsx("div", { className: "text-sm font-medium text-foreground", children: "Webhook URL" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("code", { className: "flex-1 truncate rounded-md border bg-muted/30 px-3 py-2 text-xs text-foreground", children: status.webhookUrl }), _jsx(Button, { variant: "outline", size: "icon", onClick: () => copyWebhook(status.webhookUrl), "aria-label": `Copy ${platform.name} webhook URL`, children: copiedWebhook === status.webhookUrl ? (_jsx(IconCheck, { className: "h-4 w-4" })) : (_jsx(IconCopy, { className: "h-4 w-4" })) })] })] })) : null, _jsxs("div", { className: "mt-5 flex flex-wrap items-center justify-end gap-2 border-t border-border pt-4", children: [platform.id === "telegram" && configured ? (_jsx(Button, { variant: "outline", onClick: () => runSetup(platform), disabled: setupPlatform === platform.id, children: setupPlatform === platform.id ? (_jsxs(_Fragment, { children: [_jsx(IconLoader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Setting up..."] })) : ("Set up webhook") })) : null, !configured && !enabled ? (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { tabIndex: 0, children: _jsx(Button, { disabled: true, children: "Enable" }) }) }), _jsx(TooltipContent, { children: "Save the required credentials first." })] })) : (_jsx(Button, { onClick: () => togglePlatform(platform, enabled), disabled: togglingPlatform === platform.id, children: togglingPlatform === platform.id ? (_jsxs(_Fragment, { children: [_jsx(IconLoader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Saving..."] })) : enabled ? ("Disable") : ("Enable") }))] })] }, platform.id));
297
373
  }) }), loading ? (_jsx("div", { className: "rounded-2xl border border-dashed px-4 py-6 text-sm text-muted-foreground", children: "Loading messaging status..." })) : null] }));
298
374
  }
299
375
  //# sourceMappingURL=messaging-setup-panel.js.map