@agentprojectcontext/apx 1.61.0 → 1.62.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/core/agent/super-agent.js +7 -1
- package/src/core/agent/tools/handlers/call-runtime.js +185 -76
- package/src/core/channels/telegram/dispatch.js +24 -1
- package/src/core/channels/telegram/reply.js +79 -2
- package/src/core/integrations/plugins/asana.js +10 -30
- package/src/core/integrations/plugins/github.js +4 -16
- package/src/core/stores/runtime-callbacks.js +107 -0
- package/src/host/daemon/callback-reconciler.js +87 -0
- package/src/host/daemon/index.js +7 -0
- package/src/interfaces/web/dist/assets/{index-DRFIAiiq.js → index-CFcs16SV.js} +169 -152
- package/src/interfaces/web/dist/assets/{index-DRFIAiiq.js.map → index-CFcs16SV.js.map} +1 -1
- package/src/interfaces/web/dist/index.html +1 -1
- package/src/interfaces/web/package-lock.json +3 -3
- package/src/interfaces/web/src/components/integrations/ComingSoonPlugin.tsx +3 -6
- package/src/interfaces/web/src/components/integrations/PluginConnect.tsx +39 -26
- package/src/interfaces/web/src/components/integrations/PluginToolsSection.tsx +7 -8
- package/src/interfaces/web/src/i18n/en.ts +57 -0
- package/src/interfaces/web/src/i18n/es.ts +57 -0
- package/src/interfaces/web/src/lib/api/integrations.ts +6 -10
- package/src/interfaces/web/src/screens/project/IntegrationsTab.tsx +20 -27
- package/src/interfaces/web/src/screens/project/McpsTab.tsx +1 -20
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
<link rel="apple-touch-icon" href="/favicon/dark/apple-touch-icon.png" media="(prefers-color-scheme: dark)" />
|
|
19
19
|
<link rel="manifest" href="/favicon/white/site.webmanifest" media="(prefers-color-scheme: light)" />
|
|
20
20
|
<link rel="manifest" href="/favicon/dark/site.webmanifest" media="(prefers-color-scheme: dark)" />
|
|
21
|
-
<script type="module" crossorigin src="/assets/index-
|
|
21
|
+
<script type="module" crossorigin src="/assets/index-CFcs16SV.js"></script>
|
|
22
22
|
<link rel="stylesheet" crossorigin href="/assets/index-BuII-tAi.css">
|
|
23
23
|
</head>
|
|
24
24
|
<body class="bg-background text-foreground antialiased">
|
|
@@ -2218,9 +2218,9 @@
|
|
|
2218
2218
|
}
|
|
2219
2219
|
},
|
|
2220
2220
|
"node_modules/caniuse-lite": {
|
|
2221
|
-
"version": "1.0.
|
|
2222
|
-
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.
|
|
2223
|
-
"integrity": "sha512-
|
|
2221
|
+
"version": "1.0.30001802",
|
|
2222
|
+
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001802.tgz",
|
|
2223
|
+
"integrity": "sha512-vmv8ub2xwTNmljSKf82mtCk5JH7hC+YgzLj3P5zotvA0tPQ9016tdNNOG8WRca1IxOnhSsivB+J0z5FeE5LOUw==",
|
|
2224
2224
|
"dev": true,
|
|
2225
2225
|
"funding": [
|
|
2226
2226
|
{
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
2
|
import { Github, MessageCircle, Mic, Puzzle } from "lucide-react";
|
|
3
3
|
import type { CatalogEntry } from "../../lib/api";
|
|
4
|
+
import { t } from "../../i18n";
|
|
4
5
|
import { PluginCard } from "./PluginCard";
|
|
5
6
|
|
|
6
7
|
const ICONS: Record<string, { icon: typeof Puzzle; className: string; wrap: string }> = {
|
|
@@ -28,17 +29,13 @@ export function ComingSoonPlugin({ entry }: { entry: CatalogEntry }) {
|
|
|
28
29
|
description={entry.description}
|
|
29
30
|
badges={
|
|
30
31
|
<span className="rounded-full border border-border bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground">
|
|
31
|
-
|
|
32
|
+
{t("integrations.coming_soon")}
|
|
32
33
|
</span>
|
|
33
34
|
}
|
|
34
35
|
expanded={expanded}
|
|
35
36
|
onToggle={() => setExpanded((v) => !v)}
|
|
36
37
|
>
|
|
37
|
-
<div className="p-4 text-xs text-muted-foreground">
|
|
38
|
-
Este plugin está declarado en el catálogo pero todavía no está conectable en APX. La
|
|
39
|
-
infraestructura de <span className="font-mono">{entry.slug}</span> se va a portar de forma
|
|
40
|
-
nativa en una próxima iteración.
|
|
41
|
-
</div>
|
|
38
|
+
<div className="p-4 text-xs text-muted-foreground">{t("integrations.coming_soon_body")}</div>
|
|
42
39
|
</PluginCard>
|
|
43
40
|
);
|
|
44
41
|
}
|
|
@@ -3,12 +3,17 @@ import useSWR from "swr";
|
|
|
3
3
|
import { AlertCircle, CheckCircle2, ChevronDown, ExternalLink, Eye, EyeOff, Github, Loader2, WifiOff, X } from "lucide-react";
|
|
4
4
|
import { cn } from "../../lib/cn";
|
|
5
5
|
import { Integrations, type CatalogEntry, type IntegrationScope, type IntegrationStatus } from "../../lib/api";
|
|
6
|
+
import { t } from "../../i18n";
|
|
7
|
+
import type { TKey } from "../../i18n";
|
|
6
8
|
import { PluginCard } from "./PluginCard";
|
|
7
9
|
import { PluginToolsSection } from "./PluginToolsSection";
|
|
8
10
|
|
|
9
11
|
// One generic component that renders any token-based plugin's config form from
|
|
10
|
-
// its `ui` descriptor (
|
|
11
|
-
// GitHub share it; each keeps its own independent config + credentials.
|
|
12
|
+
// its `ui` descriptor (structure) + i18n (all display text, keyed by slug). Asana
|
|
13
|
+
// and GitHub share it; each keeps its own independent config + credentials.
|
|
14
|
+
|
|
15
|
+
// Dynamic i18n lookup for computed per-slug keys (t is typed to known keys).
|
|
16
|
+
const tk = (key: string, vars?: Record<string, string | number>) => t(key as TKey, vars);
|
|
12
17
|
|
|
13
18
|
function AsanaLogo({ className }: { className?: string }) {
|
|
14
19
|
return (
|
|
@@ -75,7 +80,7 @@ export function PluginConnect({ pid, scope, entry }: { pid: string; scope: Integ
|
|
|
75
80
|
setStep("done");
|
|
76
81
|
setValues({});
|
|
77
82
|
} catch (err) {
|
|
78
|
-
setError(err instanceof Error ? err.message : "
|
|
83
|
+
setError(err instanceof Error ? err.message : t("integrations.err_connect"));
|
|
79
84
|
setStep("idle");
|
|
80
85
|
}
|
|
81
86
|
}
|
|
@@ -91,7 +96,7 @@ export function PluginConnect({ pid, scope, entry }: { pid: string; scope: Integ
|
|
|
91
96
|
setSelectOptions([]);
|
|
92
97
|
setStep("done");
|
|
93
98
|
} catch (err) {
|
|
94
|
-
setError(err instanceof Error ? err.message : "
|
|
99
|
+
setError(err instanceof Error ? err.message : t("integrations.err_generic"));
|
|
95
100
|
setStep("idle");
|
|
96
101
|
}
|
|
97
102
|
}
|
|
@@ -102,10 +107,18 @@ export function PluginConnect({ pid, scope, entry }: { pid: string; scope: Integ
|
|
|
102
107
|
await Integrations.deactivate(pid, entry.slug, scope);
|
|
103
108
|
await mutate();
|
|
104
109
|
} catch (err) {
|
|
105
|
-
setError(err instanceof Error ? err.message : "
|
|
110
|
+
setError(err instanceof Error ? err.message : t("integrations.err_generic"));
|
|
106
111
|
}
|
|
107
112
|
}
|
|
108
113
|
|
|
114
|
+
const badgeLabel = isLoading
|
|
115
|
+
? "…"
|
|
116
|
+
: isActive
|
|
117
|
+
? t("integrations.status_active")
|
|
118
|
+
: status?.status === "error"
|
|
119
|
+
? t("integrations.status_error")
|
|
120
|
+
: t("integrations.status_unconfigured");
|
|
121
|
+
|
|
109
122
|
return (
|
|
110
123
|
<PluginCard
|
|
111
124
|
icon={
|
|
@@ -124,7 +137,7 @@ export function PluginConnect({ pid, scope, entry }: { pid: string; scope: Integ
|
|
|
124
137
|
)}
|
|
125
138
|
>
|
|
126
139
|
<span className={cn("h-1.5 w-1.5 rounded-full", isActive ? "bg-emerald-400" : "bg-muted-foreground")} />
|
|
127
|
-
{
|
|
140
|
+
{badgeLabel}
|
|
128
141
|
</span>
|
|
129
142
|
}
|
|
130
143
|
expanded={expanded}
|
|
@@ -144,14 +157,14 @@ export function PluginConnect({ pid, scope, entry }: { pid: string; scope: Integ
|
|
|
144
157
|
<div className="space-y-1 rounded-xl border border-emerald-700/30 bg-emerald-900/10 p-3">
|
|
145
158
|
<div className="flex items-center gap-2">
|
|
146
159
|
<CheckCircle2 className="h-3.5 w-3.5 text-emerald-400" />
|
|
147
|
-
<span className="text-xs font-medium text-emerald-300">
|
|
160
|
+
<span className="text-xs font-medium text-emerald-300">{t("integrations.connected")}</span>
|
|
148
161
|
</div>
|
|
149
|
-
{(ui.connectedFields || []).map((
|
|
150
|
-
const v = status?.[
|
|
162
|
+
{(ui.connectedFields || []).map((key) => {
|
|
163
|
+
const v = status?.[key];
|
|
151
164
|
if (!v) return null;
|
|
152
165
|
return (
|
|
153
|
-
<p key={
|
|
154
|
-
{
|
|
166
|
+
<p key={key} className="pl-5 text-[10px] text-muted-foreground">
|
|
167
|
+
{tk(`integrations.${entry.slug}.connected.${key}`)}: {String(v)}
|
|
155
168
|
</p>
|
|
156
169
|
);
|
|
157
170
|
})}
|
|
@@ -161,14 +174,14 @@ export function PluginConnect({ pid, scope, entry }: { pid: string; scope: Integ
|
|
|
161
174
|
{/* Post-validate selection (e.g. Asana workspace) */}
|
|
162
175
|
{selectOptions.length > 1 && ui.select && (
|
|
163
176
|
<div className="space-y-2">
|
|
164
|
-
<p className="text-[11px] text-muted-foreground">{
|
|
177
|
+
<p className="text-[11px] text-muted-foreground">{tk(`integrations.${entry.slug}.select_label`)}:</p>
|
|
165
178
|
<div className="flex gap-2">
|
|
166
179
|
<select
|
|
167
180
|
value={selectValue}
|
|
168
181
|
onChange={(e) => setSelectValue(e.target.value)}
|
|
169
182
|
className={cn("flex-1 rounded-lg border border-border bg-background px-2 py-1.5 text-xs outline-none", accent.ring)}
|
|
170
183
|
>
|
|
171
|
-
<option value="">
|
|
184
|
+
<option value="">{t("integrations.select_placeholder")}</option>
|
|
172
185
|
{selectOptions.map((o) => (
|
|
173
186
|
<option key={o.value} value={o.value}>{o.label}</option>
|
|
174
187
|
))}
|
|
@@ -178,7 +191,7 @@ export function PluginConnect({ pid, scope, entry }: { pid: string; scope: Integ
|
|
|
178
191
|
disabled={!selectValue || busy}
|
|
179
192
|
className={cn("rounded-lg border px-3 py-1.5 text-xs transition-all disabled:cursor-not-allowed disabled:opacity-50", accent.border, accent.text, accent.hover)}
|
|
180
193
|
>
|
|
181
|
-
{busy ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : "
|
|
194
|
+
{busy ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : t("integrations.confirm")}
|
|
182
195
|
</button>
|
|
183
196
|
</div>
|
|
184
197
|
</div>
|
|
@@ -187,10 +200,10 @@ export function PluginConnect({ pid, scope, entry }: { pid: string; scope: Integ
|
|
|
187
200
|
{/* Config form */}
|
|
188
201
|
{showForm && (
|
|
189
202
|
<div className="space-y-3">
|
|
190
|
-
<p className="text-xs font-semibold text-foreground">
|
|
203
|
+
<p className="text-xs font-semibold text-foreground">{t("integrations.credentials", { name: entry.name })}</p>
|
|
191
204
|
{ui.configFields.map((field) => (
|
|
192
205
|
<div key={field.key} className="space-y-2">
|
|
193
|
-
{field.
|
|
206
|
+
{field.help_url && (
|
|
194
207
|
<div className="overflow-hidden rounded-lg border border-border">
|
|
195
208
|
<button
|
|
196
209
|
type="button"
|
|
@@ -198,22 +211,22 @@ export function PluginConnect({ pid, scope, entry }: { pid: string; scope: Integ
|
|
|
198
211
|
className="flex w-full items-center justify-between px-3 py-2 text-left transition-colors hover:bg-muted/40"
|
|
199
212
|
>
|
|
200
213
|
<span className="text-[11px] text-muted-foreground">
|
|
201
|
-
{field.
|
|
214
|
+
{tk(`integrations.${entry.slug}.fields.${field.key}.help_label`)} ·{" "}
|
|
202
215
|
<a
|
|
203
|
-
href={field.
|
|
216
|
+
href={field.help_url}
|
|
204
217
|
target="_blank"
|
|
205
218
|
rel="noreferrer"
|
|
206
219
|
onClick={(e) => e.stopPropagation()}
|
|
207
220
|
className={cn("inline-flex items-center gap-0.5 hover:underline", accent.text)}
|
|
208
221
|
>
|
|
209
|
-
{field.
|
|
222
|
+
{field.help_url_label} <ExternalLink className="h-2.5 w-2.5" />
|
|
210
223
|
</a>
|
|
211
224
|
</span>
|
|
212
225
|
<ChevronDown className={cn("h-3.5 w-3.5 flex-shrink-0 text-muted-foreground transition-transform", showHelp === field.key && "rotate-180")} />
|
|
213
226
|
</button>
|
|
214
227
|
{showHelp === field.key && (
|
|
215
228
|
<div className="space-y-1.5 border-t border-border px-3 pb-3 pt-2.5">
|
|
216
|
-
{field.
|
|
229
|
+
{tk(`integrations.${entry.slug}.fields.${field.key}.help_steps`).split("\n").map((s, i) => (
|
|
217
230
|
<div key={i} className="flex items-start gap-2">
|
|
218
231
|
<span className={cn("mt-0.5 flex-shrink-0 font-mono text-[10px]", accent.text)}>{i + 1}.</span>
|
|
219
232
|
<p className="text-[11px] text-muted-foreground">{s}</p>
|
|
@@ -224,7 +237,7 @@ export function PluginConnect({ pid, scope, entry }: { pid: string; scope: Integ
|
|
|
224
237
|
</div>
|
|
225
238
|
)}
|
|
226
239
|
<div>
|
|
227
|
-
<label className="mb-1 block text-[10px] text-muted-foreground">{field.label}</label>
|
|
240
|
+
<label className="mb-1 block text-[10px] text-muted-foreground">{tk(`integrations.${entry.slug}.fields.${field.key}.label`)}</label>
|
|
228
241
|
<div className="relative">
|
|
229
242
|
<input
|
|
230
243
|
type={field.type === "password" && !reveal[field.key] ? "password" : "text"}
|
|
@@ -241,7 +254,7 @@ export function PluginConnect({ pid, scope, entry }: { pid: string; scope: Integ
|
|
|
241
254
|
className="absolute right-2.5 top-1/2 flex -translate-y-1/2 items-center gap-0.5 text-[10px] text-muted-foreground transition-colors hover:text-foreground"
|
|
242
255
|
>
|
|
243
256
|
{reveal[field.key] ? <EyeOff className="h-3 w-3" /> : <Eye className="h-3 w-3" />}
|
|
244
|
-
{reveal[field.key] ? "
|
|
257
|
+
{reveal[field.key] ? t("integrations.hide") : t("integrations.reveal")}
|
|
245
258
|
</button>
|
|
246
259
|
)}
|
|
247
260
|
</div>
|
|
@@ -251,7 +264,7 @@ export function PluginConnect({ pid, scope, entry }: { pid: string; scope: Integ
|
|
|
251
264
|
|
|
252
265
|
{step === "validating" && (
|
|
253
266
|
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
|
254
|
-
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
|
267
|
+
<Loader2 className="h-3.5 w-3.5 animate-spin" /> {t("integrations.verifying")}
|
|
255
268
|
</div>
|
|
256
269
|
)}
|
|
257
270
|
|
|
@@ -261,8 +274,8 @@ export function PluginConnect({ pid, scope, entry }: { pid: string; scope: Integ
|
|
|
261
274
|
className={cn("flex w-full items-center justify-center gap-1.5 rounded-lg border px-3 py-2 text-xs transition-all disabled:cursor-not-allowed disabled:opacity-50", accent.border, accent.text, accent.hover)}
|
|
262
275
|
>
|
|
263
276
|
{busy ? (
|
|
264
|
-
<><Loader2 className="h-3.5 w-3.5 animate-spin" />{step === "saving" ? "
|
|
265
|
-
) : "
|
|
277
|
+
<><Loader2 className="h-3.5 w-3.5 animate-spin" />{step === "saving" ? t("integrations.saving") : t("integrations.validating")}</>
|
|
278
|
+
) : t("integrations.connect")}
|
|
266
279
|
</button>
|
|
267
280
|
</div>
|
|
268
281
|
)}
|
|
@@ -277,7 +290,7 @@ export function PluginConnect({ pid, scope, entry }: { pid: string; scope: Integ
|
|
|
277
290
|
onClick={handleDeactivate}
|
|
278
291
|
className="flex items-center gap-1.5 rounded-lg border border-red-700/50 px-3 py-1.5 text-xs text-red-400 transition-all hover:bg-red-900/20"
|
|
279
292
|
>
|
|
280
|
-
<WifiOff className="h-3.5 w-3.5" />
|
|
293
|
+
<WifiOff className="h-3.5 w-3.5" /> {t("integrations.deactivate")}
|
|
281
294
|
</button>
|
|
282
295
|
</div>
|
|
283
296
|
)}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Wrench } from "lucide-react";
|
|
2
|
+
import { t } from "../../i18n";
|
|
2
3
|
|
|
3
4
|
export interface PluginTool {
|
|
4
5
|
slug: string;
|
|
@@ -15,25 +16,23 @@ export function PluginToolsSection({ tools, isActive }: { pid: string; tools: Pl
|
|
|
15
16
|
<div className="space-y-2.5 rounded-xl border border-border bg-muted/30 p-3">
|
|
16
17
|
<div className="flex items-center justify-between">
|
|
17
18
|
<p className="text-[10px] font-semibold uppercase tracking-wide text-muted-foreground">
|
|
18
|
-
|
|
19
|
+
{t("integrations.tools_for_agents")}
|
|
19
20
|
</p>
|
|
20
21
|
</div>
|
|
21
22
|
<div className="flex flex-wrap gap-1.5">
|
|
22
|
-
{tools.map((
|
|
23
|
+
{tools.map((tool) => (
|
|
23
24
|
<div
|
|
24
|
-
key={
|
|
25
|
+
key={tool.slug}
|
|
25
26
|
className="flex items-center gap-1.5 rounded-lg border border-border bg-background px-2 py-1"
|
|
26
27
|
>
|
|
27
28
|
<Wrench className="h-2.5 w-2.5 flex-shrink-0 text-muted-foreground" />
|
|
28
|
-
<span className="font-mono text-[10px] text-foreground">{
|
|
29
|
+
<span className="font-mono text-[10px] text-foreground">{tool.slug}</span>
|
|
29
30
|
<span className="text-[10px] text-muted-foreground/60">·</span>
|
|
30
|
-
<span className="text-[10px] text-muted-foreground">{
|
|
31
|
+
<span className="text-[10px] text-muted-foreground">{tool.desc}</span>
|
|
31
32
|
</div>
|
|
32
33
|
))}
|
|
33
34
|
</div>
|
|
34
|
-
<p className="text-[10px] text-muted-foreground/70">
|
|
35
|
-
Disponibles para los agentes que las tengan permitidas, o vía <span className="font-mono">discover_tools</span>.
|
|
36
|
-
</p>
|
|
35
|
+
<p className="text-[10px] text-muted-foreground/70">{t("integrations.tools_available_note")}</p>
|
|
37
36
|
</div>
|
|
38
37
|
);
|
|
39
38
|
}
|
|
@@ -602,6 +602,7 @@ export const en = {
|
|
|
602
602
|
logs_btn: "Logs",
|
|
603
603
|
testing: "Testing…",
|
|
604
604
|
test_ok: "OK · {n} tools available",
|
|
605
|
+
tools_count: "{n} tools",
|
|
605
606
|
logs_title: "Logs · {name}",
|
|
606
607
|
logs_empty: "No logs yet. Start the MCP by calling a tool or running Test.",
|
|
607
608
|
logs_events: "Recent events",
|
|
@@ -1789,4 +1790,60 @@ export const en = {
|
|
|
1789
1790
|
err_load_conversation: "Could not load conversation.",
|
|
1790
1791
|
err_stream: "Stream error.",
|
|
1791
1792
|
},
|
|
1793
|
+
integrations: {
|
|
1794
|
+
title: "Integrations",
|
|
1795
|
+
description: "Plugins and tools available for this project",
|
|
1796
|
+
tab_plugins: "Plugins",
|
|
1797
|
+
tab_tools: "Tools",
|
|
1798
|
+
scope_label: "Scope:",
|
|
1799
|
+
scope_project: "This project",
|
|
1800
|
+
scope_global: "Global (default)",
|
|
1801
|
+
plugins_hint: "Channel & service plugins installable per project. Saved in the scope selected above.",
|
|
1802
|
+
more_soon: "More plugins coming soon…",
|
|
1803
|
+
tools_hint: "Tools that connected plugins expose to this project's agents.",
|
|
1804
|
+
tools_empty: "No integration tools yet. Connect a plugin to enable them.",
|
|
1805
|
+
tool_active: "active",
|
|
1806
|
+
tool_inactive: "inactive",
|
|
1807
|
+
status_active: "Active",
|
|
1808
|
+
status_error: "Error",
|
|
1809
|
+
status_unconfigured: "Not configured",
|
|
1810
|
+
connected: "Connected",
|
|
1811
|
+
connect: "Connect",
|
|
1812
|
+
deactivate: "Deactivate",
|
|
1813
|
+
saving: "Saving...",
|
|
1814
|
+
validating: "Validating...",
|
|
1815
|
+
verifying: "Verifying token...",
|
|
1816
|
+
confirm: "Confirm",
|
|
1817
|
+
select_placeholder: "Select...",
|
|
1818
|
+
reveal: "Show",
|
|
1819
|
+
hide: "Hide",
|
|
1820
|
+
credentials: "{name} credentials",
|
|
1821
|
+
coming_soon: "Coming soon",
|
|
1822
|
+
coming_soon_body: "This plugin is declared in the catalog but isn't connectable in APX yet. It will be ported natively in a future iteration.",
|
|
1823
|
+
tools_for_agents: "Agent tools",
|
|
1824
|
+
tools_available_note: "Available to agents that allow them, or via discover_tools.",
|
|
1825
|
+
err_connect: "Failed to connect",
|
|
1826
|
+
err_generic: "Something went wrong",
|
|
1827
|
+
asana: {
|
|
1828
|
+
select_label: "Select the workspace to use",
|
|
1829
|
+
connected: { user_name: "Connected as", user_email: "Email", workspace_name: "Workspace" },
|
|
1830
|
+
fields: {
|
|
1831
|
+
personal_access_token: {
|
|
1832
|
+
label: "Personal Access Token",
|
|
1833
|
+
help_label: "How to get the token?",
|
|
1834
|
+
help_steps: "Open app.asana.com/0/my-apps in your browser.\nScroll to the \"Personal access tokens\" section (not your OAuth apps).\nClick \"+ New access token\".\nGive it a name and confirm.\nCopy the full token — it starts with \"1/...\" and has a \":\" in the middle.\nPaste it in the field below.",
|
|
1835
|
+
},
|
|
1836
|
+
},
|
|
1837
|
+
},
|
|
1838
|
+
github: {
|
|
1839
|
+
connected: { user_login: "Connected as", user_name: "Name" },
|
|
1840
|
+
fields: {
|
|
1841
|
+
token: {
|
|
1842
|
+
label: "Personal Access Token",
|
|
1843
|
+
help_label: "How to get the token?",
|
|
1844
|
+
help_steps: "Open github.com/settings/tokens.\nGenerate a token (classic or fine-grained) with the \"repo\" scope.\nCopy the token — it starts with ghp_ or github_pat_.\nPaste it in the field below.",
|
|
1845
|
+
},
|
|
1846
|
+
},
|
|
1847
|
+
},
|
|
1848
|
+
},
|
|
1792
1849
|
} as const;
|
|
@@ -620,6 +620,7 @@ export const es = {
|
|
|
620
620
|
logs_btn: "Logs",
|
|
621
621
|
testing: "Probando…",
|
|
622
622
|
test_ok: "OK · {n} tools disponibles",
|
|
623
|
+
tools_count: "{n} tools",
|
|
623
624
|
logs_title: "Logs · {name}",
|
|
624
625
|
logs_empty: "Sin logs todavía. Arrancá el MCP llamando un tool o probando.",
|
|
625
626
|
logs_events: "Eventos recientes",
|
|
@@ -1787,6 +1788,62 @@ export const es = {
|
|
|
1787
1788
|
err_load_conversation: "No se pudo cargar la conversación.",
|
|
1788
1789
|
err_stream: "Error de stream.",
|
|
1789
1790
|
},
|
|
1791
|
+
integrations: {
|
|
1792
|
+
title: "Integrations",
|
|
1793
|
+
description: "Plugins y tools disponibles para este proyecto",
|
|
1794
|
+
tab_plugins: "Plugins",
|
|
1795
|
+
tab_tools: "Tools",
|
|
1796
|
+
scope_label: "Ámbito:",
|
|
1797
|
+
scope_project: "Este proyecto",
|
|
1798
|
+
scope_global: "Global (default)",
|
|
1799
|
+
plugins_hint: "Plugins de canal y servicio instalables por proyecto. Se guardan en el ámbito seleccionado arriba.",
|
|
1800
|
+
more_soon: "Más plugins próximamente…",
|
|
1801
|
+
tools_hint: "Tools que los plugins conectados exponen a los agentes de este proyecto.",
|
|
1802
|
+
tools_empty: "No hay tools de integraciones. Conectá un plugin para habilitarlas.",
|
|
1803
|
+
tool_active: "activo",
|
|
1804
|
+
tool_inactive: "inactivo",
|
|
1805
|
+
status_active: "Activo",
|
|
1806
|
+
status_error: "Error",
|
|
1807
|
+
status_unconfigured: "No configurado",
|
|
1808
|
+
connected: "Conectado",
|
|
1809
|
+
connect: "Conectar",
|
|
1810
|
+
deactivate: "Desactivar",
|
|
1811
|
+
saving: "Guardando...",
|
|
1812
|
+
validating: "Validando...",
|
|
1813
|
+
verifying: "Verificando token...",
|
|
1814
|
+
confirm: "Confirmar",
|
|
1815
|
+
select_placeholder: "Seleccionar...",
|
|
1816
|
+
reveal: "Ver",
|
|
1817
|
+
hide: "Ocultar",
|
|
1818
|
+
credentials: "Credenciales {name}",
|
|
1819
|
+
coming_soon: "Próximamente",
|
|
1820
|
+
coming_soon_body: "Este plugin está declarado en el catálogo pero todavía no está conectable en APX. Se va a portar de forma nativa en una próxima iteración.",
|
|
1821
|
+
tools_for_agents: "Tools para agentes",
|
|
1822
|
+
tools_available_note: "Disponibles para los agentes que las tengan permitidas, o vía discover_tools.",
|
|
1823
|
+
err_connect: "Error al conectar",
|
|
1824
|
+
err_generic: "Ocurrió un error",
|
|
1825
|
+
asana: {
|
|
1826
|
+
select_label: "Seleccioná el workspace a usar",
|
|
1827
|
+
connected: { user_name: "Conectado como", user_email: "Email", workspace_name: "Workspace" },
|
|
1828
|
+
fields: {
|
|
1829
|
+
personal_access_token: {
|
|
1830
|
+
label: "Personal Access Token",
|
|
1831
|
+
help_label: "¿Cómo obtener el token?",
|
|
1832
|
+
help_steps: "Abrí app.asana.com/0/my-apps en el navegador.\nBajá hasta la sección \"Personal access tokens\" (no tus apps OAuth).\nHacé clic en \"+ New access token\".\nDale un nombre y confirmá.\nCopiá el token completo — empieza con \"1/...\" y tiene un \":\" en el medio.\nPegalo en el campo de abajo.",
|
|
1833
|
+
},
|
|
1834
|
+
},
|
|
1835
|
+
},
|
|
1836
|
+
github: {
|
|
1837
|
+
connected: { user_login: "Conectado como", user_name: "Nombre" },
|
|
1838
|
+
fields: {
|
|
1839
|
+
token: {
|
|
1840
|
+
label: "Personal Access Token",
|
|
1841
|
+
help_label: "¿Cómo obtener el token?",
|
|
1842
|
+
help_steps: "Abrí github.com/settings/tokens.\nGenerá un token (classic o fine-grained) con scope \"repo\".\nCopiá el token — empieza con ghp_ o github_pat_.\nPegalo en el campo de abajo.",
|
|
1843
|
+
},
|
|
1844
|
+
},
|
|
1845
|
+
},
|
|
1846
|
+
},
|
|
1790
1847
|
} as const;
|
|
1791
1848
|
|
|
1792
1849
|
export type EsStrings = typeof es;
|
|
@@ -20,32 +20,28 @@ export interface PluginTool {
|
|
|
20
20
|
desc: string;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
// Declarative UI descriptor (mirrors the plugin's `ui` in core).
|
|
24
|
-
//
|
|
23
|
+
// Declarative UI descriptor (mirrors the plugin's `ui` in core). STRUCTURE ONLY
|
|
24
|
+
// — display text is resolved from i18n keys (integrations.<slug>.*) by the
|
|
25
|
+
// generic PluginConnect component. help_url/help_url_label are non-translatable.
|
|
25
26
|
export interface PluginConfigField {
|
|
26
27
|
key: string;
|
|
27
|
-
label: string;
|
|
28
28
|
type: "password" | "text";
|
|
29
29
|
placeholder?: string;
|
|
30
|
-
|
|
30
|
+
help_url?: string;
|
|
31
|
+
help_url_label?: string;
|
|
31
32
|
}
|
|
32
33
|
export interface PluginSelect {
|
|
33
34
|
key: string;
|
|
34
|
-
label: string;
|
|
35
35
|
action: string;
|
|
36
36
|
listKey: string;
|
|
37
37
|
valueKey: string;
|
|
38
38
|
labelKey: string;
|
|
39
39
|
}
|
|
40
|
-
export interface PluginConnectedField {
|
|
41
|
-
key: string;
|
|
42
|
-
label: string;
|
|
43
|
-
}
|
|
44
40
|
export interface PluginUi {
|
|
45
41
|
accent?: string;
|
|
46
42
|
configFields: PluginConfigField[];
|
|
47
43
|
select?: PluginSelect;
|
|
48
|
-
connectedFields?:
|
|
44
|
+
connectedFields?: string[];
|
|
49
45
|
}
|
|
50
46
|
|
|
51
47
|
// One entry of the plugin catalog with its resolved status for this project.
|
|
@@ -7,12 +7,13 @@ import { Section } from "../../components/Section";
|
|
|
7
7
|
import { Empty, Loading } from "../../components/ui";
|
|
8
8
|
import { PluginConnect } from "../../components/integrations/PluginConnect";
|
|
9
9
|
import { ComingSoonPlugin } from "../../components/integrations/ComingSoonPlugin";
|
|
10
|
+
import { t } from "../../i18n";
|
|
10
11
|
|
|
11
12
|
type SubTab = "plugins" | "tools";
|
|
12
13
|
|
|
13
|
-
const SUBTABS: { value: SubTab;
|
|
14
|
-
{ value: "plugins",
|
|
15
|
-
{ value: "tools",
|
|
14
|
+
const SUBTABS: { value: SubTab; labelKey: "integrations.tab_plugins" | "integrations.tab_tools"; icon: typeof Puzzle }[] = [
|
|
15
|
+
{ value: "plugins", labelKey: "integrations.tab_plugins", icon: Puzzle },
|
|
16
|
+
{ value: "tools", labelKey: "integrations.tab_tools", icon: Wrench },
|
|
16
17
|
];
|
|
17
18
|
|
|
18
19
|
// Renders a connectable plugin (via the generic PluginConnect, driven by its
|
|
@@ -28,16 +29,13 @@ function PluginsSection({ pid, scope }: { pid: string; scope: IntegrationScope }
|
|
|
28
29
|
|
|
29
30
|
return (
|
|
30
31
|
<div className="space-y-3">
|
|
31
|
-
<p className="text-xs text-muted-foreground">
|
|
32
|
-
Plugins de canal y servicio instalables por proyecto. Se guardan en el ámbito
|
|
33
|
-
seleccionado arriba.
|
|
34
|
-
</p>
|
|
32
|
+
<p className="text-xs text-muted-foreground">{t("integrations.plugins_hint")}</p>
|
|
35
33
|
{isLoading && <Loading />}
|
|
36
34
|
{(catalog || []).map((entry) => (
|
|
37
35
|
<PluginRow key={entry.slug} pid={pid} scope={scope} entry={entry} />
|
|
38
36
|
))}
|
|
39
37
|
<div className="rounded-xl border border-dashed border-border p-6 text-center">
|
|
40
|
-
<p className="text-sm text-muted-foreground">
|
|
38
|
+
<p className="text-sm text-muted-foreground">{t("integrations.more_soon")}</p>
|
|
41
39
|
</div>
|
|
42
40
|
</div>
|
|
43
41
|
);
|
|
@@ -47,35 +45,33 @@ function ToolsSection({ pid }: { pid: string }) {
|
|
|
47
45
|
const { data: catalog } = useSWR(`integrations-catalog-${pid}`, () => Integrations.catalog(pid));
|
|
48
46
|
const rows = (catalog || [])
|
|
49
47
|
.filter((c) => !c.coming_soon && (c.tools?.length ?? 0) > 0)
|
|
50
|
-
.flatMap((c) => (c.tools || []).map((
|
|
48
|
+
.flatMap((c) => (c.tools || []).map((tool) => ({ ...tool, plugin: c.name, active: c.status.is_enabled })));
|
|
51
49
|
|
|
52
50
|
return (
|
|
53
51
|
<div className="space-y-3">
|
|
54
|
-
<p className="text-xs text-muted-foreground">
|
|
55
|
-
Tools que los plugins conectados exponen a los agentes de este proyecto.
|
|
56
|
-
</p>
|
|
52
|
+
<p className="text-xs text-muted-foreground">{t("integrations.tools_hint")}</p>
|
|
57
53
|
{rows.length === 0 ? (
|
|
58
|
-
<Empty>
|
|
54
|
+
<Empty>{t("integrations.tools_empty")}</Empty>
|
|
59
55
|
) : (
|
|
60
56
|
<ul className="space-y-2">
|
|
61
|
-
{rows.map((
|
|
62
|
-
<li key={
|
|
57
|
+
{rows.map((tool) => (
|
|
58
|
+
<li key={tool.slug} className={cn("rounded-md border border-border bg-muted/30 px-3 py-2", !tool.active && "opacity-55")}>
|
|
63
59
|
<div className="flex items-center gap-2">
|
|
64
60
|
<Wrench className="h-3.5 w-3.5 text-muted-foreground" />
|
|
65
|
-
<span className="font-mono text-xs text-foreground">{
|
|
66
|
-
<span className="ml-auto text-[10px] text-muted-foreground">{
|
|
61
|
+
<span className="font-mono text-xs text-foreground">{tool.slug}</span>
|
|
62
|
+
<span className="ml-auto text-[10px] text-muted-foreground">{tool.plugin}</span>
|
|
67
63
|
<span
|
|
68
64
|
className={cn(
|
|
69
65
|
"rounded border px-1.5 py-0.5 text-[10px]",
|
|
70
|
-
|
|
66
|
+
tool.active
|
|
71
67
|
? "border-emerald-700/40 bg-emerald-900/20 text-emerald-400"
|
|
72
68
|
: "border-border bg-muted text-muted-foreground",
|
|
73
69
|
)}
|
|
74
70
|
>
|
|
75
|
-
{
|
|
71
|
+
{tool.active ? t("integrations.tool_active") : t("integrations.tool_inactive")}
|
|
76
72
|
</span>
|
|
77
73
|
</div>
|
|
78
|
-
<p className="mt-0.5 pl-5 text-[10px] text-muted-foreground">{
|
|
74
|
+
<p className="mt-0.5 pl-5 text-[10px] text-muted-foreground">{tool.desc}</p>
|
|
79
75
|
</li>
|
|
80
76
|
))}
|
|
81
77
|
</ul>
|
|
@@ -91,15 +87,12 @@ export function IntegrationsTab({ pid }: { pid: string }) {
|
|
|
91
87
|
const [scope, setScope] = useState<IntegrationScope>(isBase ? "global" : "project");
|
|
92
88
|
|
|
93
89
|
return (
|
|
94
|
-
<Section
|
|
95
|
-
title="Integrations"
|
|
96
|
-
description="Plugins y tools disponibles para este proyecto"
|
|
97
|
-
>
|
|
90
|
+
<Section title={t("integrations.title")} description={t("integrations.description")}>
|
|
98
91
|
{/* Scope selector — a real project can use its own integrations or the
|
|
99
92
|
global (default-project) ones. */}
|
|
100
93
|
{!isBase && (
|
|
101
94
|
<div className="mb-4 flex items-center gap-2">
|
|
102
|
-
<span className="text-xs text-muted-foreground"
|
|
95
|
+
<span className="text-xs text-muted-foreground">{t("integrations.scope_label")}</span>
|
|
103
96
|
{(["project", "global"] as const).map((s) => (
|
|
104
97
|
<button
|
|
105
98
|
key={s}
|
|
@@ -111,7 +104,7 @@ export function IntegrationsTab({ pid }: { pid: string }) {
|
|
|
111
104
|
: "border-border bg-muted/30 text-muted-foreground hover:bg-muted/50",
|
|
112
105
|
)}
|
|
113
106
|
>
|
|
114
|
-
{s === "project" ? "
|
|
107
|
+
{s === "project" ? t("integrations.scope_project") : t("integrations.scope_global")}
|
|
115
108
|
</button>
|
|
116
109
|
))}
|
|
117
110
|
</div>
|
|
@@ -130,7 +123,7 @@ export function IntegrationsTab({ pid }: { pid: string }) {
|
|
|
130
123
|
tab === s.value ? "bg-card text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground",
|
|
131
124
|
)}
|
|
132
125
|
>
|
|
133
|
-
<Icon className="h-3.5 w-3.5" /> {s.
|
|
126
|
+
<Icon className="h-3.5 w-3.5" /> {t(s.labelKey)}
|
|
134
127
|
</button>
|
|
135
128
|
);
|
|
136
129
|
})}
|
|
@@ -113,10 +113,6 @@ export function McpsTab({ pid }: { pid: string }) {
|
|
|
113
113
|
}
|
|
114
114
|
};
|
|
115
115
|
|
|
116
|
-
const testedValues = Object.values(results);
|
|
117
|
-
const okCount = testedValues.filter((r) => r.ok).length;
|
|
118
|
-
const errCount = testedValues.filter((r) => r.ok === false).length;
|
|
119
|
-
|
|
120
116
|
return (
|
|
121
117
|
<div className="grid grid-cols-1 gap-4 lg:grid-cols-4">
|
|
122
118
|
<div className="lg:col-span-3">
|
|
@@ -150,21 +146,6 @@ export function McpsTab({ pid }: { pid: string }) {
|
|
|
150
146
|
{list.isLoading && <Loading />}
|
|
151
147
|
{!list.isLoading && (list.data?.length ?? 0) === 0 && <Empty>{t("project.mcps.empty")}</Empty>}
|
|
152
148
|
|
|
153
|
-
{(okCount > 0 || errCount > 0) && (
|
|
154
|
-
<div className="mb-2 flex items-center gap-4 rounded-md border border-border bg-muted/30 px-3 py-2 text-xs">
|
|
155
|
-
{okCount > 0 && (
|
|
156
|
-
<span className="flex items-center gap-1.5 text-emerald-400">
|
|
157
|
-
<span className="h-2 w-2 rounded-full bg-emerald-400" /> {okCount} conectados
|
|
158
|
-
</span>
|
|
159
|
-
)}
|
|
160
|
-
{errCount > 0 && (
|
|
161
|
-
<span className="flex items-center gap-1.5 text-red-400">
|
|
162
|
-
<span className="h-2 w-2 rounded-full bg-red-400" /> {errCount} con error
|
|
163
|
-
</span>
|
|
164
|
-
)}
|
|
165
|
-
</div>
|
|
166
|
-
)}
|
|
167
|
-
|
|
168
149
|
<ul className="space-y-2 text-sm">
|
|
169
150
|
{(list.data || []).map((m) => {
|
|
170
151
|
const writable = m.source === "apc" || m.source === "runtime" || m.source === "global";
|
|
@@ -240,7 +221,7 @@ export function McpsTab({ pid }: { pid: string }) {
|
|
|
240
221
|
onClick={() => setExpandedTools((prev) => ({ ...prev, [m.name]: !prev[m.name] }))}
|
|
241
222
|
className="flex items-center gap-1 text-xs text-muted-fg transition-colors hover:text-fg"
|
|
242
223
|
>
|
|
243
|
-
<Wrench size={12} /> {tools.length}
|
|
224
|
+
<Wrench size={12} /> {t("project.mcps.tools_count", { n: tools.length })}
|
|
244
225
|
<ChevronDown size={12} className={cn("transition-transform", open && "rotate-180")} />
|
|
245
226
|
</button>
|
|
246
227
|
{open && (
|