@vaia-lab/sdk 0.1.3 → 0.3.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/LICENSE +21 -0
- package/README.md +107 -151
- package/dist/cli.js +252 -0
- package/dist/index.cjs +747 -28
- package/dist/index.d.cts +754 -5
- package/dist/index.d.ts +754 -5
- package/dist/index.js +728 -27
- package/package.json +13 -2
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -21,11 +21,29 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
// src/index.ts
|
|
22
22
|
var src_exports = {};
|
|
23
23
|
__export(src_exports, {
|
|
24
|
+
AGENT_PROTOCOL_VERSION: () => AGENT_PROTOCOL_VERSION,
|
|
25
|
+
CONNECTOR_OF_OPERATION: () => CONNECTOR_OF_OPERATION,
|
|
24
26
|
VAIAError: () => VAIAError,
|
|
27
|
+
agentLoop: () => agentLoop,
|
|
28
|
+
capabilities: () => capabilities,
|
|
29
|
+
checkAuthority: () => checkAuthority,
|
|
25
30
|
defineCapability: () => defineCapability,
|
|
31
|
+
defineEcosystem: () => defineEcosystem,
|
|
32
|
+
fromMCPTool: () => fromMCPTool,
|
|
26
33
|
gandia: () => gandia,
|
|
27
34
|
handeia: () => handeia,
|
|
28
|
-
|
|
35
|
+
http: () => http,
|
|
36
|
+
httpTransport: () => httpTransport,
|
|
37
|
+
local: () => local,
|
|
38
|
+
mcp: () => mcp,
|
|
39
|
+
mountAgent: () => mountAgent,
|
|
40
|
+
requiresApproval: () => requiresApproval,
|
|
41
|
+
toMCPTool: () => toMCPTool,
|
|
42
|
+
toMCPTools: () => toMCPTools,
|
|
43
|
+
toManifest: () => toManifest,
|
|
44
|
+
validateActionCall: () => validateActionCall,
|
|
45
|
+
validateAgentSurface: () => validateAgentSurface,
|
|
46
|
+
validatePieces: () => validatePieces
|
|
29
47
|
});
|
|
30
48
|
module.exports = __toCommonJS(src_exports);
|
|
31
49
|
|
|
@@ -47,10 +65,12 @@ __export(gandia_exports, {
|
|
|
47
65
|
// src/crypto.ts
|
|
48
66
|
var enc = new TextEncoder();
|
|
49
67
|
function hexToBytes(hex) {
|
|
50
|
-
if (hex.length % 2 !== 0
|
|
51
|
-
|
|
68
|
+
if (hex.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(hex)) {
|
|
69
|
+
throw new Error("Invalid hex string");
|
|
70
|
+
}
|
|
71
|
+
const bytes = new Uint8Array(new ArrayBuffer(hex.length / 2));
|
|
52
72
|
for (let i = 0; i < hex.length; i += 2) {
|
|
53
|
-
bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
|
|
73
|
+
bytes[i / 2] = Number.parseInt(hex.slice(i, i + 2), 16);
|
|
54
74
|
}
|
|
55
75
|
return bytes;
|
|
56
76
|
}
|
|
@@ -94,9 +114,7 @@ async function verify(request, secret) {
|
|
|
94
114
|
const sigHeader = headers.get("x-gandia-signature") ?? "";
|
|
95
115
|
const tsHeader = headers.get("x-gandia-timestamp") ?? "";
|
|
96
116
|
const callId = headers.get("x-gandia-call-id") ?? "";
|
|
97
|
-
|
|
98
|
-
return { ctx: buildProbeCtx(callId), raw: rawBody };
|
|
99
|
-
}
|
|
117
|
+
const esProbe = headers.get(PROBE_HEADER) === "1";
|
|
100
118
|
if (!sigHeader || !tsHeader) {
|
|
101
119
|
throw new VAIAError(
|
|
102
120
|
"Faltan headers de autenticaci\xF3n (X-Gandia-Signature, X-Gandia-Timestamp)",
|
|
@@ -114,6 +132,9 @@ async function verify(request, secret) {
|
|
|
114
132
|
if (!valid) {
|
|
115
133
|
throw new VAIAError("Firma HMAC inv\xE1lida", "HMAC_INVALID", 401);
|
|
116
134
|
}
|
|
135
|
+
if (esProbe) {
|
|
136
|
+
return { ctx: buildProbeCtx(callId), raw: rawBody };
|
|
137
|
+
}
|
|
117
138
|
let body;
|
|
118
139
|
try {
|
|
119
140
|
body = JSON.parse(rawBody);
|
|
@@ -198,14 +219,28 @@ __export(jwt_exports, {
|
|
|
198
219
|
|
|
199
220
|
// src/jwt-utils.ts
|
|
200
221
|
var enc2 = new TextEncoder();
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
222
|
+
var utf8Strict = new TextDecoder("utf-8", { fatal: true });
|
|
223
|
+
var utf8Loose = new TextDecoder("utf-8");
|
|
224
|
+
var latin1 = new TextDecoder("latin1");
|
|
225
|
+
var JWT_PAYLOAD_VERSION = 2;
|
|
226
|
+
function bytesToB64url(bytes) {
|
|
227
|
+
let bin = "";
|
|
228
|
+
const CHUNK = 32768;
|
|
229
|
+
for (let i = 0; i < bytes.length; i += CHUNK) {
|
|
230
|
+
bin += String.fromCharCode(...bytes.subarray(i, i + CHUNK));
|
|
231
|
+
}
|
|
232
|
+
return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
204
233
|
}
|
|
205
|
-
function
|
|
234
|
+
function b64urlToBytes(str2) {
|
|
206
235
|
const padded = str2.replace(/-/g, "+").replace(/_/g, "/");
|
|
207
236
|
const pad = padded.length % 4;
|
|
208
|
-
|
|
237
|
+
const bin = atob(pad ? padded + "=".repeat(4 - pad) : padded);
|
|
238
|
+
const out = new Uint8Array(new ArrayBuffer(bin.length));
|
|
239
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
240
|
+
return out;
|
|
241
|
+
}
|
|
242
|
+
function textToB64url(text) {
|
|
243
|
+
return bytesToB64url(enc2.encode(text));
|
|
209
244
|
}
|
|
210
245
|
async function importHMACKey2(secret) {
|
|
211
246
|
return crypto.subtle.importKey(
|
|
@@ -216,13 +251,33 @@ async function importHMACKey2(secret) {
|
|
|
216
251
|
["sign", "verify"]
|
|
217
252
|
);
|
|
218
253
|
}
|
|
219
|
-
var HEADER =
|
|
254
|
+
var HEADER = textToB64url(JSON.stringify({ alg: "HS256", typ: "JWT" }));
|
|
220
255
|
async function jwtSign(payload, secret) {
|
|
221
|
-
const body =
|
|
256
|
+
const body = textToB64url(JSON.stringify({ ...payload, v: JWT_PAYLOAD_VERSION }));
|
|
222
257
|
const unsigned = `${HEADER}.${body}`;
|
|
223
258
|
const key = await importHMACKey2(secret);
|
|
224
259
|
const sig = await crypto.subtle.sign("HMAC", key, enc2.encode(unsigned));
|
|
225
|
-
return `${unsigned}.${
|
|
260
|
+
return `${unsigned}.${bytesToB64url(new Uint8Array(sig))}`;
|
|
261
|
+
}
|
|
262
|
+
function decodePayload(body) {
|
|
263
|
+
const bytes = b64urlToBytes(body);
|
|
264
|
+
let sonda;
|
|
265
|
+
try {
|
|
266
|
+
sonda = JSON.parse(latin1.decode(bytes));
|
|
267
|
+
} catch {
|
|
268
|
+
throw new VAIAError("JWT payload inv\xE1lido", "JWT_PAYLOAD_INVALID", 401);
|
|
269
|
+
}
|
|
270
|
+
const version = typeof sonda["v"] === "number" ? sonda["v"] : 1;
|
|
271
|
+
if (version < 2) return sonda;
|
|
272
|
+
try {
|
|
273
|
+
return JSON.parse(utf8Strict.decode(bytes));
|
|
274
|
+
} catch {
|
|
275
|
+
try {
|
|
276
|
+
return JSON.parse(utf8Loose.decode(bytes));
|
|
277
|
+
} catch {
|
|
278
|
+
throw new VAIAError("JWT payload inv\xE1lido", "JWT_PAYLOAD_INVALID", 401);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
226
281
|
}
|
|
227
282
|
async function jwtVerify(token, secret) {
|
|
228
283
|
const parts = token.split(".");
|
|
@@ -232,17 +287,16 @@ async function jwtVerify(token, secret) {
|
|
|
232
287
|
const [header, body, sig] = parts;
|
|
233
288
|
const unsigned = `${header}.${body}`;
|
|
234
289
|
const key = await importHMACKey2(secret);
|
|
235
|
-
|
|
236
|
-
const valid = await crypto.subtle.verify("HMAC", key, sigBytes, enc2.encode(unsigned));
|
|
237
|
-
if (!valid) {
|
|
238
|
-
throw new VAIAError("Firma JWT inv\xE1lida", "JWT_SIGNATURE_INVALID", 401);
|
|
239
|
-
}
|
|
240
|
-
let payload;
|
|
290
|
+
let valid;
|
|
241
291
|
try {
|
|
242
|
-
|
|
292
|
+
valid = await crypto.subtle.verify("HMAC", key, b64urlToBytes(sig), enc2.encode(unsigned));
|
|
243
293
|
} catch {
|
|
244
|
-
throw new VAIAError("JWT
|
|
294
|
+
throw new VAIAError("JWT malformado", "JWT_MALFORMED", 401);
|
|
245
295
|
}
|
|
296
|
+
if (!valid) {
|
|
297
|
+
throw new VAIAError("Firma JWT inv\xE1lida", "JWT_SIGNATURE_INVALID", 401);
|
|
298
|
+
}
|
|
299
|
+
const payload = decodePayload(body);
|
|
246
300
|
if (typeof payload.exp === "number" && Date.now() / 1e3 > payload.exp) {
|
|
247
301
|
throw new VAIAError("JWT expirado", "JWT_EXPIRED", 401);
|
|
248
302
|
}
|
|
@@ -440,9 +494,7 @@ async function verify3(request, secret) {
|
|
|
440
494
|
const sigHeader = headers.get("x-handeia-signature") ?? "";
|
|
441
495
|
const tsHeader = headers.get("x-handeia-timestamp") ?? "";
|
|
442
496
|
const callId = headers.get("x-handeia-call-id") ?? "";
|
|
443
|
-
|
|
444
|
-
return { ctx: buildProbeCtx2(callId), raw: rawBody };
|
|
445
|
-
}
|
|
497
|
+
const esProbe = headers.get(PROBE_HEADER2) === "1";
|
|
446
498
|
if (!sigHeader || !tsHeader) {
|
|
447
499
|
throw new VAIAError(
|
|
448
500
|
"Faltan headers de autenticaci\xF3n (X-Handeia-Signature, X-Handeia-Timestamp)",
|
|
@@ -460,6 +512,9 @@ async function verify3(request, secret) {
|
|
|
460
512
|
if (!valid) {
|
|
461
513
|
throw new VAIAError("Firma HMAC inv\xE1lida", "HMAC_INVALID", 401);
|
|
462
514
|
}
|
|
515
|
+
if (esProbe) {
|
|
516
|
+
return { ctx: buildProbeCtx2(callId), raw: rawBody };
|
|
517
|
+
}
|
|
463
518
|
let body;
|
|
464
519
|
try {
|
|
465
520
|
body = JSON.parse(rawBody);
|
|
@@ -543,6 +598,154 @@ async function fromUrl2(url, secret) {
|
|
|
543
598
|
return verify4(token, secret);
|
|
544
599
|
}
|
|
545
600
|
|
|
601
|
+
// src/agent.ts
|
|
602
|
+
var AGENT_PROTOCOL_VERSION = 1;
|
|
603
|
+
var CONNECTOR_OF_OPERATION = {
|
|
604
|
+
"github.repos": "github",
|
|
605
|
+
"github.issues": "github",
|
|
606
|
+
"drive.files": "drive",
|
|
607
|
+
"calendar.events": "calendar",
|
|
608
|
+
"email.recent": "email",
|
|
609
|
+
"notion.pages": "notion"
|
|
610
|
+
};
|
|
611
|
+
var NOMBRE_ACCION = /^[a-z][a-z0-9_]{1,48}$/;
|
|
612
|
+
function validateAgentSurface(cfg) {
|
|
613
|
+
const errores = [];
|
|
614
|
+
const vistos = /* @__PURE__ */ new Set();
|
|
615
|
+
for (const accion of cfg.actions ?? []) {
|
|
616
|
+
if (!NOMBRE_ACCION.test(accion.name)) {
|
|
617
|
+
errores.push(`Acci\xF3n "${accion.name}": el nombre debe ser min\xFAsculas, n\xFAmeros o guion bajo.`);
|
|
618
|
+
}
|
|
619
|
+
if (vistos.has(accion.name)) {
|
|
620
|
+
errores.push(`Acci\xF3n "${accion.name}": declarada dos veces.`);
|
|
621
|
+
}
|
|
622
|
+
vistos.add(accion.name);
|
|
623
|
+
if (!accion.description?.trim()) {
|
|
624
|
+
errores.push(`Acci\xF3n "${accion.name}": falta la descripci\xF3n, que es lo que el agente lee para elegirla.`);
|
|
625
|
+
}
|
|
626
|
+
if (accion.writes && !accion.permission) {
|
|
627
|
+
errores.push(`Acci\xF3n "${accion.name}": modifica datos, as\xED que necesita un permiso declarado.`);
|
|
628
|
+
}
|
|
629
|
+
for (const p of accion.params ?? []) {
|
|
630
|
+
if (!p.name?.trim()) errores.push(`Acci\xF3n "${accion.name}": un par\xE1metro no tiene nombre.`);
|
|
631
|
+
if (!p.description?.trim()) {
|
|
632
|
+
errores.push(`Acci\xF3n "${accion.name}", par\xE1metro "${p.name}": falta la descripci\xF3n.`);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
if (cfg.queryEndpoint && !cfg.queryEndpoint.startsWith("/")) {
|
|
637
|
+
errores.push('queryEndpoint debe ser una ruta de tu propio servidor, empezando por "/".');
|
|
638
|
+
}
|
|
639
|
+
return errores;
|
|
640
|
+
}
|
|
641
|
+
function validateActionCall(llamada, declaradas) {
|
|
642
|
+
const action = declaradas.find((a) => a.name === llamada.name);
|
|
643
|
+
if (!action) return { ok: false, reason: `La acci\xF3n "${llamada.name}" no est\xE1 declarada por el espacio.` };
|
|
644
|
+
const args = llamada.args ?? {};
|
|
645
|
+
for (const p of action.params ?? []) {
|
|
646
|
+
const v = args[p.name];
|
|
647
|
+
if (v === void 0 || v === null) {
|
|
648
|
+
if (p.required) return { ok: false, reason: `Falta el par\xE1metro obligatorio "${p.name}".` };
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
if (typeof v !== p.type) {
|
|
652
|
+
return { ok: false, reason: `El par\xE1metro "${p.name}" debe ser ${p.type}.` };
|
|
653
|
+
}
|
|
654
|
+
if (p.enum && !p.enum.includes(String(v))) {
|
|
655
|
+
return { ok: false, reason: `El par\xE1metro "${p.name}" no admite el valor "${String(v)}".` };
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
const permitidos = new Set((action.params ?? []).map((p) => p.name));
|
|
659
|
+
for (const k of Object.keys(args)) {
|
|
660
|
+
if (!permitidos.has(k)) return { ok: false, reason: `El par\xE1metro "${k}" no est\xE1 declarado.` };
|
|
661
|
+
}
|
|
662
|
+
return { ok: true, action };
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// src/pieces.ts
|
|
666
|
+
var NOMBRE = /^[a-z][a-z0-9_]{1,48}$/;
|
|
667
|
+
var ORDEN = {
|
|
668
|
+
prohibida: 0,
|
|
669
|
+
requiere_aprobacion: 1,
|
|
670
|
+
autonoma: 2
|
|
671
|
+
};
|
|
672
|
+
function validatePieces(cfg) {
|
|
673
|
+
const errores = [];
|
|
674
|
+
const vistos = /* @__PURE__ */ new Set();
|
|
675
|
+
const revisarNombre = (pieza, name) => {
|
|
676
|
+
if (!NOMBRE.test(name)) errores.push(`${pieza} "${name}": el nombre debe ser min\xFAsculas, n\xFAmeros o guion bajo.`);
|
|
677
|
+
if (vistos.has(name)) errores.push(`"${name}": hay dos piezas con el mismo nombre.`);
|
|
678
|
+
vistos.add(name);
|
|
679
|
+
};
|
|
680
|
+
const revisarAutoridad = (pieza, a) => {
|
|
681
|
+
if (a.consequence === "irreversible" && a.level === "autonoma") {
|
|
682
|
+
errores.push(`${pieza}: una acci\xF3n irreversible no puede ser aut\xF3noma \u2014 como m\xEDnimo requiere aprobaci\xF3n.`);
|
|
683
|
+
}
|
|
684
|
+
if (a.maxAmount !== void 0) {
|
|
685
|
+
if (a.maxAmount <= 0) errores.push(`${pieza}: el tope de gasto debe ser mayor que cero.`);
|
|
686
|
+
if (!a.currency) errores.push(`${pieza}: hay tope de gasto pero no se declar\xF3 la moneda.`);
|
|
687
|
+
}
|
|
688
|
+
if (a.consequence === "costosa" && a.level === "autonoma" && a.maxAmount === void 0) {
|
|
689
|
+
errores.push(`${pieza}: es aut\xF3noma y cuesta dinero, as\xED que necesita un tope declarado.`);
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
for (const s2 of cfg.skills ?? []) {
|
|
693
|
+
revisarNombre("Skill", s2.name);
|
|
694
|
+
if (!s2.description?.trim()) errores.push(`Skill "${s2.name}": falta la descripci\xF3n, que es lo que el modelo lee para elegirla.`);
|
|
695
|
+
if (s2.evidence?.required && (s2.evidence.accepts?.length ?? 0) === 0) {
|
|
696
|
+
errores.push(`Skill "${s2.name}": exige evidencia pero no declara qu\xE9 tipos acepta.`);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
for (const t of cfg.tools ?? []) {
|
|
700
|
+
revisarNombre("Herramienta", t.name);
|
|
701
|
+
if (!t.description?.trim()) errores.push(`Herramienta "${t.name}": falta la descripci\xF3n.`);
|
|
702
|
+
if (!t.permission?.trim()) errores.push(`Herramienta "${t.name}": toca el mundo real, as\xED que necesita un permiso declarado.`);
|
|
703
|
+
revisarAutoridad(`Herramienta "${t.name}"`, t.authority);
|
|
704
|
+
}
|
|
705
|
+
for (const w of cfg.workflows ?? []) {
|
|
706
|
+
revisarNombre("Workflow", w.name);
|
|
707
|
+
if ((w.steps?.length ?? 0) === 0) errores.push(`Workflow "${w.name}": no tiene pasos.`);
|
|
708
|
+
revisarAutoridad(`Workflow "${w.name}"`, w.authority);
|
|
709
|
+
}
|
|
710
|
+
const porNombre = new Map((cfg.tools ?? []).map((t) => [t.name, t]));
|
|
711
|
+
for (const a of cfg.agents ?? []) {
|
|
712
|
+
revisarNombre("Agente", a.name);
|
|
713
|
+
if (!a.purpose?.trim()) {
|
|
714
|
+
errores.push(`Agente "${a.name}": falta el prop\xF3sito \u2014 para qu\xE9 existe.`);
|
|
715
|
+
}
|
|
716
|
+
revisarAutoridad(`Agente "${a.name}"`, a.authority);
|
|
717
|
+
for (const nombreTool of a.tools ?? []) {
|
|
718
|
+
const tool = porNombre.get(nombreTool);
|
|
719
|
+
if (!tool) {
|
|
720
|
+
errores.push(`Agente "${a.name}": usa la herramienta "${nombreTool}", que no est\xE1 declarada.`);
|
|
721
|
+
continue;
|
|
722
|
+
}
|
|
723
|
+
if (ORDEN[tool.authority.level] > ORDEN[a.authority.level]) {
|
|
724
|
+
errores.push(
|
|
725
|
+
`Agente "${a.name}": su herramienta "${nombreTool}" tiene m\xE1s autoridad que \xE9l. Ninguna pieza puede superar el techo de su agente.`
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return errores;
|
|
731
|
+
}
|
|
732
|
+
function requiresApproval(a) {
|
|
733
|
+
return a.level !== "autonoma";
|
|
734
|
+
}
|
|
735
|
+
function checkAuthority(a, intento = {}) {
|
|
736
|
+
if (a.level === "prohibida") return { ok: false, reason: "Esta acci\xF3n est\xE1 prohibida para esta pieza." };
|
|
737
|
+
if (intento.amount !== void 0) {
|
|
738
|
+
if (a.maxAmount === void 0) return { ok: false, reason: "Mueve dinero pero no hay tope declarado." };
|
|
739
|
+
if (intento.currency && a.currency && intento.currency !== a.currency) {
|
|
740
|
+
return { ok: false, reason: `Moneda distinta a la declarada (${a.currency}).` };
|
|
741
|
+
}
|
|
742
|
+
if (intento.amount > a.maxAmount) {
|
|
743
|
+
return { ok: false, reason: `Excede el tope declarado (${a.maxAmount} ${a.currency ?? ""}).`.trim() };
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
return { ok: true };
|
|
747
|
+
}
|
|
748
|
+
|
|
546
749
|
// src/define.ts
|
|
547
750
|
function defineCapability(config) {
|
|
548
751
|
const required = ["id", "name", "version", "target", "type", "sector", "permissions", "risk"];
|
|
@@ -559,6 +762,20 @@ function defineCapability(config) {
|
|
|
559
762
|
if (Object.keys(config.surfaces).length === 0) {
|
|
560
763
|
throw new Error(`[@vaia/sdk] defineCapability: 'surfaces' no puede estar vac\xEDo. Define al menos un surface con su endpoint.`);
|
|
561
764
|
}
|
|
765
|
+
if (config.pieces) {
|
|
766
|
+
const errores = validatePieces(config.pieces);
|
|
767
|
+
if (errores.length > 0) {
|
|
768
|
+
throw new Error(`[@vaia/sdk] defineCapability: piezas inv\xE1lidas:
|
|
769
|
+
- ${errores.join("\n - ")}`);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
if (config.agent) {
|
|
773
|
+
const errores = validateAgentSurface(config.agent);
|
|
774
|
+
if (errores.length > 0) {
|
|
775
|
+
throw new Error(`[@vaia/sdk] defineCapability: superficie de agente inv\xE1lida:
|
|
776
|
+
- ${errores.join("\n - ")}`);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
562
779
|
return config;
|
|
563
780
|
}
|
|
564
781
|
function toManifest(config) {
|
|
@@ -573,6 +790,16 @@ function toManifest(config) {
|
|
|
573
790
|
level: config.level,
|
|
574
791
|
sector: config.sector,
|
|
575
792
|
surfaces,
|
|
793
|
+
// El agente viaja en el manifest para que el portal y Handeia sepan qué
|
|
794
|
+
// puede hacer este espacio sin abrir su código.
|
|
795
|
+
agent: config.agent ? {
|
|
796
|
+
protocol: AGENT_PROTOCOL_VERSION,
|
|
797
|
+
actions: config.agent.actions ?? [],
|
|
798
|
+
query_endpoint: config.agent.queryEndpoint
|
|
799
|
+
} : void 0,
|
|
800
|
+
// Las piezas viajan al manifest: el portal necesita mostrar qué autoridad
|
|
801
|
+
// pide una capacidad ANTES de que alguien la instale.
|
|
802
|
+
pieces: config.pieces,
|
|
576
803
|
permissions: config.permissions,
|
|
577
804
|
risk: config.risk,
|
|
578
805
|
has_own_auth: config.has_own_auth ?? false,
|
|
@@ -587,15 +814,507 @@ function toManifest(config) {
|
|
|
587
814
|
};
|
|
588
815
|
}
|
|
589
816
|
|
|
817
|
+
// src/agent-mount.ts
|
|
818
|
+
var HANDEIA_POR_DEFECTO = "https://handeia.com";
|
|
819
|
+
var RUTA_TURNO = "/api/agent/space";
|
|
820
|
+
var CSS = `
|
|
821
|
+
.hdi-agent-root{position:fixed;z-index:2147483000;bottom:20px;right:20px;font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif}
|
|
822
|
+
.hdi-agent-orb{width:44px;height:44px;border-radius:9999px;border:0;cursor:pointer;background:#000;color:#fff;display:flex;align-items:center;justify-content:center;box-shadow:0 10px 30px -6px rgba(0,0,0,.4);transition:transform .15s}
|
|
823
|
+
.hdi-agent-orb:hover{transform:scale(1.05)}
|
|
824
|
+
.hdi-agent-orb:active{transform:scale(.95)}
|
|
825
|
+
.hdi-agent-panel{position:absolute;bottom:56px;right:0;width:min(360px,calc(100vw - 40px));background:#fff;color:#111;border:1px solid rgba(0,0,0,.09);border-radius:18px;box-shadow:0 24px 60px -18px rgba(0,0,0,.32);overflow:hidden;display:flex;flex-direction:column;max-height:min(60vh,460px)}
|
|
826
|
+
.hdi-agent-head{display:flex;align-items:center;gap:8px;padding:12px 14px;border-bottom:1px solid rgba(0,0,0,.06);font-size:12.5px;font-weight:600}
|
|
827
|
+
.hdi-agent-dot{width:7px;height:7px;border-radius:9999px;background:#8b5cf6}
|
|
828
|
+
.hdi-agent-log{flex:1;overflow-y:auto;padding:12px 14px;display:flex;flex-direction:column;gap:10px;font-size:13px;line-height:1.55}
|
|
829
|
+
.hdi-agent-turn{white-space:pre-wrap}
|
|
830
|
+
.hdi-agent-q{font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;color:rgba(0,0,0,.34)}
|
|
831
|
+
.hdi-agent-form{display:flex;gap:8px;padding:10px 12px;border-top:1px solid rgba(0,0,0,.06)}
|
|
832
|
+
.hdi-agent-input{flex:1;border:1px solid rgba(0,0,0,.12);border-radius:10px;padding:8px 10px;font:inherit;font-size:13px;outline:none}
|
|
833
|
+
.hdi-agent-input:focus{border-color:rgba(0,0,0,.34)}
|
|
834
|
+
.hdi-agent-send{border:0;border-radius:10px;padding:0 12px;background:#000;color:#fff;cursor:pointer;font-size:12.5px}
|
|
835
|
+
.hdi-agent-confirm{display:flex;gap:8px;margin-top:4px}
|
|
836
|
+
.hdi-agent-btn{border:1px solid rgba(0,0,0,.14);background:#fff;border-radius:9999px;padding:5px 12px;font-size:12px;cursor:pointer}
|
|
837
|
+
.hdi-agent-btn-primary{background:#000;color:#fff;border-color:#000}
|
|
838
|
+
@media (prefers-color-scheme:dark){
|
|
839
|
+
.hdi-agent-orb{background:#fff;color:#000}
|
|
840
|
+
.hdi-agent-panel{background:#171717;color:#f5f5f5;border-color:rgba(255,255,255,.13)}
|
|
841
|
+
.hdi-agent-head,.hdi-agent-form{border-color:rgba(255,255,255,.1)}
|
|
842
|
+
.hdi-agent-q{color:rgba(255,255,255,.5)}
|
|
843
|
+
.hdi-agent-input{background:transparent;color:inherit;border-color:rgba(255,255,255,.16)}
|
|
844
|
+
.hdi-agent-send{background:#fff;color:#000}
|
|
845
|
+
.hdi-agent-btn{background:transparent;color:inherit;border-color:rgba(255,255,255,.18)}
|
|
846
|
+
.hdi-agent-btn-primary{background:#fff;color:#000}
|
|
847
|
+
}`;
|
|
848
|
+
function el(tag, cls, text) {
|
|
849
|
+
const n = document.createElement(tag);
|
|
850
|
+
if (cls) n.className = cls;
|
|
851
|
+
if (text !== void 0) n.textContent = text;
|
|
852
|
+
return n;
|
|
853
|
+
}
|
|
854
|
+
function mountAgent(opts) {
|
|
855
|
+
if (typeof document === "undefined") {
|
|
856
|
+
throw new Error("[@vaia-lab/sdk] mountAgent solo corre en el navegador.");
|
|
857
|
+
}
|
|
858
|
+
const base = (opts.handeiaUrl ?? HANDEIA_POR_DEFECTO).replace(/\/$/, "");
|
|
859
|
+
const contenedor = opts.container ?? document.body;
|
|
860
|
+
const previo = contenedor.querySelector(".hdi-agent-root");
|
|
861
|
+
if (previo) previo.remove();
|
|
862
|
+
if (!document.getElementById("hdi-agent-css")) {
|
|
863
|
+
const estilo = el("style");
|
|
864
|
+
estilo.id = "hdi-agent-css";
|
|
865
|
+
estilo.textContent = CSS;
|
|
866
|
+
document.head.appendChild(estilo);
|
|
867
|
+
}
|
|
868
|
+
const raiz = el("div", "hdi-agent-root");
|
|
869
|
+
const orbe = el("button", "hdi-agent-orb");
|
|
870
|
+
orbe.type = "button";
|
|
871
|
+
orbe.setAttribute("aria-label", "Abrir asistente de Handeia");
|
|
872
|
+
orbe.textContent = "\u2726";
|
|
873
|
+
const panel = el("div", "hdi-agent-panel");
|
|
874
|
+
panel.hidden = true;
|
|
875
|
+
panel.setAttribute("role", "dialog");
|
|
876
|
+
panel.setAttribute("aria-label", "Asistente de Handeia");
|
|
877
|
+
const cabeza = el("div", "hdi-agent-head");
|
|
878
|
+
cabeza.appendChild(el("span", "hdi-agent-dot"));
|
|
879
|
+
cabeza.appendChild(el("span", void 0, "Handeia"));
|
|
880
|
+
const registro = el("div", "hdi-agent-log");
|
|
881
|
+
if (opts.greeting) registro.appendChild(el("div", "hdi-agent-turn", opts.greeting));
|
|
882
|
+
const forma = el("form", "hdi-agent-form");
|
|
883
|
+
const entrada = el("input", "hdi-agent-input");
|
|
884
|
+
entrada.type = "text";
|
|
885
|
+
entrada.placeholder = "Preg\xFAntale a Handeia\u2026";
|
|
886
|
+
entrada.autocomplete = "off";
|
|
887
|
+
const enviar = el("button", "hdi-agent-send", "Enviar");
|
|
888
|
+
enviar.type = "submit";
|
|
889
|
+
forma.append(entrada, enviar);
|
|
890
|
+
panel.append(cabeza, registro, forma);
|
|
891
|
+
raiz.append(panel, orbe);
|
|
892
|
+
contenedor.appendChild(raiz);
|
|
893
|
+
const historial = [];
|
|
894
|
+
const decir = (texto, clase = "hdi-agent-turn") => {
|
|
895
|
+
const n = el("div", clase, texto);
|
|
896
|
+
registro.appendChild(n);
|
|
897
|
+
registro.scrollTop = registro.scrollHeight;
|
|
898
|
+
return n;
|
|
899
|
+
};
|
|
900
|
+
async function turno(mensaje, actionResult) {
|
|
901
|
+
let context;
|
|
902
|
+
try {
|
|
903
|
+
context = await opts.getContext?.();
|
|
904
|
+
} catch {
|
|
905
|
+
context = void 0;
|
|
906
|
+
}
|
|
907
|
+
let res;
|
|
908
|
+
try {
|
|
909
|
+
res = await fetch(`${base}${RUTA_TURNO}`, {
|
|
910
|
+
method: "POST",
|
|
911
|
+
// La identidad va en la cookie de sesión de Handeia. El espacio nunca
|
|
912
|
+
// toca ni ve el token del usuario.
|
|
913
|
+
credentials: "include",
|
|
914
|
+
headers: { "Content-Type": "application/json; charset=utf-8" },
|
|
915
|
+
body: JSON.stringify({
|
|
916
|
+
protocol: AGENT_PROTOCOL_VERSION,
|
|
917
|
+
capId: opts.capabilityId,
|
|
918
|
+
message: mensaje,
|
|
919
|
+
context,
|
|
920
|
+
actions: opts.actions ?? [],
|
|
921
|
+
history: historial.slice(-12),
|
|
922
|
+
actionResult
|
|
923
|
+
})
|
|
924
|
+
});
|
|
925
|
+
} catch {
|
|
926
|
+
decir("No pude comunicarme con Handeia. Revisa tu conexi\xF3n.");
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
let data;
|
|
930
|
+
try {
|
|
931
|
+
data = await res.json();
|
|
932
|
+
} catch {
|
|
933
|
+
decir("Handeia respondi\xF3 algo que no pude leer.");
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
if (!res.ok || data.ok === false) {
|
|
937
|
+
decir(
|
|
938
|
+
data.error === "no_autenticado" ? "Inicia sesi\xF3n en Handeia para usar el asistente." : data.error === "espacio_no_instalado" ? "Este espacio no est\xE1 instalado en tu Handeia." : "Handeia no pudo responder ahora mismo."
|
|
939
|
+
);
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
if (data.text) {
|
|
943
|
+
decir(data.text);
|
|
944
|
+
historial.push({ role: "agent", text: data.text });
|
|
945
|
+
}
|
|
946
|
+
if (!data.action) return;
|
|
947
|
+
if (!opts.onAction) {
|
|
948
|
+
decir("Esto requiere una acci\xF3n que este espacio todav\xEDa no sabe ejecutar.");
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
const ejecutar = async () => {
|
|
952
|
+
let resultado;
|
|
953
|
+
try {
|
|
954
|
+
resultado = await opts.onAction(data.action.name, data.action.args ?? {});
|
|
955
|
+
} catch (e) {
|
|
956
|
+
resultado = { action: data.action.name, ok: false, error: e instanceof Error ? e.message : "fall\xF3" };
|
|
957
|
+
}
|
|
958
|
+
await turno(mensaje, resultado);
|
|
959
|
+
};
|
|
960
|
+
if (data.confirm) {
|
|
961
|
+
const fila = el("div", "hdi-agent-confirm");
|
|
962
|
+
const si = el("button", "hdi-agent-btn hdi-agent-btn-primary", "Hacerlo");
|
|
963
|
+
const no = el("button", "hdi-agent-btn", "Cancelar");
|
|
964
|
+
si.type = "button";
|
|
965
|
+
no.type = "button";
|
|
966
|
+
si.onclick = () => {
|
|
967
|
+
fila.remove();
|
|
968
|
+
void ejecutar();
|
|
969
|
+
};
|
|
970
|
+
no.onclick = () => {
|
|
971
|
+
fila.remove();
|
|
972
|
+
decir("Cancelado.");
|
|
973
|
+
};
|
|
974
|
+
fila.append(si, no);
|
|
975
|
+
registro.appendChild(fila);
|
|
976
|
+
registro.scrollTop = registro.scrollHeight;
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
await ejecutar();
|
|
980
|
+
}
|
|
981
|
+
forma.onsubmit = async (e) => {
|
|
982
|
+
e.preventDefault();
|
|
983
|
+
const texto = entrada.value.trim();
|
|
984
|
+
if (!texto) return;
|
|
985
|
+
entrada.value = "";
|
|
986
|
+
decir(texto, "hdi-agent-q");
|
|
987
|
+
historial.push({ role: "user", text: texto });
|
|
988
|
+
enviar.disabled = true;
|
|
989
|
+
try {
|
|
990
|
+
await turno(texto);
|
|
991
|
+
} finally {
|
|
992
|
+
enviar.disabled = false;
|
|
993
|
+
entrada.focus();
|
|
994
|
+
}
|
|
995
|
+
};
|
|
996
|
+
const abrir = () => {
|
|
997
|
+
panel.hidden = false;
|
|
998
|
+
entrada.focus();
|
|
999
|
+
};
|
|
1000
|
+
const cerrar = () => {
|
|
1001
|
+
panel.hidden = true;
|
|
1002
|
+
};
|
|
1003
|
+
orbe.onclick = () => panel.hidden ? abrir() : cerrar();
|
|
1004
|
+
const alTeclear = (e) => {
|
|
1005
|
+
if (e.key === "Escape") cerrar();
|
|
1006
|
+
};
|
|
1007
|
+
document.addEventListener("keydown", alTeclear);
|
|
1008
|
+
return {
|
|
1009
|
+
open: abrir,
|
|
1010
|
+
close: cerrar,
|
|
1011
|
+
destroy: () => {
|
|
1012
|
+
document.removeEventListener("keydown", alTeclear);
|
|
1013
|
+
raiz.remove();
|
|
1014
|
+
}
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
// src/capabilities.ts
|
|
1019
|
+
async function conAutoridad(tools, call, ejecutar) {
|
|
1020
|
+
const tool = tools.find((t) => t.name === call.name);
|
|
1021
|
+
if (!tool) {
|
|
1022
|
+
return { ok: false, error: `La operaci\xF3n "${call.name}" no est\xE1 declarada en esta capacidad.` };
|
|
1023
|
+
}
|
|
1024
|
+
const permitido = checkAuthority(tool.authority, {
|
|
1025
|
+
amount: call.amount,
|
|
1026
|
+
currency: call.currency
|
|
1027
|
+
});
|
|
1028
|
+
if (!permitido.ok) return { ok: false, error: permitido.reason };
|
|
1029
|
+
if (tool.authority.level === "prohibida") {
|
|
1030
|
+
return { ok: false, error: `"${call.name}" est\xE1 prohibida.` };
|
|
1031
|
+
}
|
|
1032
|
+
if (tool.authority.level !== "autonoma" && !call.approved) {
|
|
1033
|
+
return { ok: false, needsApproval: true, error: "Requiere aprobaci\xF3n humana antes de ejecutarse." };
|
|
1034
|
+
}
|
|
1035
|
+
try {
|
|
1036
|
+
return await ejecutar();
|
|
1037
|
+
} catch (e) {
|
|
1038
|
+
return { ok: false, error: e instanceof Error ? e.message : "La capacidad fall\xF3." };
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
function local(opts) {
|
|
1042
|
+
return {
|
|
1043
|
+
id: opts.id,
|
|
1044
|
+
tools: opts.tools,
|
|
1045
|
+
run: (call) => conAutoridad(opts.tools, call, async () => ({
|
|
1046
|
+
ok: true,
|
|
1047
|
+
data: await opts.handler(call.name, call.args ?? {}),
|
|
1048
|
+
evidence: { source: "local", label: opts.id }
|
|
1049
|
+
}))
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
function http(opts) {
|
|
1053
|
+
const base = opts.baseUrl.replace(/\/$/, "");
|
|
1054
|
+
return {
|
|
1055
|
+
id: opts.id,
|
|
1056
|
+
tools: opts.tools,
|
|
1057
|
+
run: (call) => conAutoridad(opts.tools, call, async () => {
|
|
1058
|
+
const res = await fetch(`${base}/${call.name}`, {
|
|
1059
|
+
method: "POST",
|
|
1060
|
+
headers: { "Content-Type": "application/json; charset=utf-8", ...opts.headers },
|
|
1061
|
+
body: JSON.stringify(call.args ?? {}),
|
|
1062
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? 15e3)
|
|
1063
|
+
});
|
|
1064
|
+
const texto = await res.text();
|
|
1065
|
+
if (!res.ok) return { ok: false, error: `${opts.id} respondi\xF3 ${res.status}` };
|
|
1066
|
+
let data;
|
|
1067
|
+
try {
|
|
1068
|
+
data = JSON.parse(texto);
|
|
1069
|
+
} catch {
|
|
1070
|
+
data = texto;
|
|
1071
|
+
}
|
|
1072
|
+
return { ok: true, data, evidence: { source: "http", label: `${opts.id}/${call.name}` } };
|
|
1073
|
+
})
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
function httpTransport(url, headers) {
|
|
1077
|
+
return {
|
|
1078
|
+
async send(mensaje) {
|
|
1079
|
+
const res = await fetch(url, {
|
|
1080
|
+
method: "POST",
|
|
1081
|
+
headers: { "Content-Type": "application/json", Accept: "application/json", ...headers },
|
|
1082
|
+
body: JSON.stringify(mensaje),
|
|
1083
|
+
signal: AbortSignal.timeout(2e4)
|
|
1084
|
+
});
|
|
1085
|
+
if (!res.ok) throw new VAIAError(`Servidor MCP respondi\xF3 ${res.status}`, "MCP_HTTP_ERROR", 502);
|
|
1086
|
+
return res.json();
|
|
1087
|
+
}
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
var PROHIBIDA = { level: "prohibida", consequence: "irreversible" };
|
|
1091
|
+
async function mcp(opts) {
|
|
1092
|
+
let siguienteId = 1;
|
|
1093
|
+
const rpc = async (method, params) => {
|
|
1094
|
+
const r = await opts.transport.send({
|
|
1095
|
+
jsonrpc: "2.0",
|
|
1096
|
+
id: siguienteId++,
|
|
1097
|
+
method,
|
|
1098
|
+
...params ? { params } : {}
|
|
1099
|
+
});
|
|
1100
|
+
if (r?.["error"]) {
|
|
1101
|
+
const e = r["error"];
|
|
1102
|
+
throw new VAIAError(e?.message ?? "Error del servidor MCP", "MCP_ERROR", 502);
|
|
1103
|
+
}
|
|
1104
|
+
return r?.["result"] ?? {};
|
|
1105
|
+
};
|
|
1106
|
+
await rpc("initialize", {
|
|
1107
|
+
protocolVersion: "2024-11-05",
|
|
1108
|
+
capabilities: {},
|
|
1109
|
+
clientInfo: { name: "@vaia-lab/sdk", version: "0.3.0" }
|
|
1110
|
+
});
|
|
1111
|
+
const listadas = (await rpc("tools/list"))["tools"] ?? [];
|
|
1112
|
+
const tools = listadas.map((t) => ({
|
|
1113
|
+
name: normalizar(t.name),
|
|
1114
|
+
description: t.description?.trim() || `Herramienta MCP "${t.name}".`,
|
|
1115
|
+
authority: opts.authority[t.name] ?? opts.defaultAuthority ?? PROHIBIDA,
|
|
1116
|
+
permission: opts.permission ?? `mcp:${opts.id}`
|
|
1117
|
+
}));
|
|
1118
|
+
const original = new Map(listadas.map((t) => [normalizar(t.name), t.name]));
|
|
1119
|
+
return {
|
|
1120
|
+
id: opts.id,
|
|
1121
|
+
tools,
|
|
1122
|
+
run: (call) => conAutoridad(tools, call, async () => {
|
|
1123
|
+
const nombreReal = original.get(call.name) ?? call.name;
|
|
1124
|
+
const r = await rpc("tools/call", { name: nombreReal, arguments: call.args ?? {} });
|
|
1125
|
+
return {
|
|
1126
|
+
ok: !r["isError"],
|
|
1127
|
+
data: r["content"] ?? r,
|
|
1128
|
+
error: r["isError"] ? "La herramienta MCP devolvi\xF3 un error." : void 0,
|
|
1129
|
+
evidence: { source: "mcp", label: `${opts.id}/${nombreReal}` }
|
|
1130
|
+
};
|
|
1131
|
+
}),
|
|
1132
|
+
dispose: () => opts.transport.close?.()
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
function normalizar(n) {
|
|
1136
|
+
return n.toLowerCase().replace(/[^a-z0-9_]/g, "_").replace(/_+/g, "_").slice(0, 48);
|
|
1137
|
+
}
|
|
1138
|
+
var capabilities = { local, http, mcp, httpTransport };
|
|
1139
|
+
|
|
1140
|
+
// src/ecosystem.ts
|
|
1141
|
+
function coincide(patron, nombre) {
|
|
1142
|
+
if (patron === "*") return true;
|
|
1143
|
+
if (patron.endsWith("*")) return nombre.startsWith(patron.slice(0, -1));
|
|
1144
|
+
return patron === nombre;
|
|
1145
|
+
}
|
|
1146
|
+
var ORDEN2 = { prohibida: 0, requiere_aprobacion: 1, autonoma: 2 };
|
|
1147
|
+
function aplicarReglas(tool, reglas) {
|
|
1148
|
+
if (!reglas) return tool;
|
|
1149
|
+
let resultado = tool.authority;
|
|
1150
|
+
for (const [patron, regla] of Object.entries(reglas)) {
|
|
1151
|
+
if (!coincide(patron, tool.name)) continue;
|
|
1152
|
+
if (ORDEN2[regla.level] < ORDEN2[resultado.level]) resultado = regla;
|
|
1153
|
+
else if (regla.maxAmount !== void 0 && (resultado.maxAmount === void 0 || regla.maxAmount < resultado.maxAmount)) {
|
|
1154
|
+
resultado = { ...resultado, maxAmount: regla.maxAmount, currency: regla.currency ?? resultado.currency };
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
return { ...tool, authority: resultado };
|
|
1158
|
+
}
|
|
1159
|
+
function defineEcosystem(cfg) {
|
|
1160
|
+
const caps = new Map(cfg.capabilities.map((c) => [c.id, c]));
|
|
1161
|
+
const recopilar = () => [...caps.values()].flatMap(
|
|
1162
|
+
(c) => c.tools.map((t) => ({ ...aplicarReglas(t, cfg.authority), capability: c.id }))
|
|
1163
|
+
);
|
|
1164
|
+
let tools = recopilar();
|
|
1165
|
+
return {
|
|
1166
|
+
name: cfg.name,
|
|
1167
|
+
get tools() {
|
|
1168
|
+
return tools;
|
|
1169
|
+
},
|
|
1170
|
+
async run(call) {
|
|
1171
|
+
const due\u00F1o = [...caps.values()].find((c) => c.tools.some((t) => t.name === call.name));
|
|
1172
|
+
if (!due\u00F1o) {
|
|
1173
|
+
return { ok: false, error: `Ninguna capacidad de "${cfg.name}" declara "${call.name}".` };
|
|
1174
|
+
}
|
|
1175
|
+
const conReglas = tools.find((t) => t.name === call.name);
|
|
1176
|
+
if (conReglas && conReglas.authority.level === "prohibida") {
|
|
1177
|
+
return { ok: false, error: `"${call.name}" est\xE1 prohibida por las reglas de ${cfg.name}.` };
|
|
1178
|
+
}
|
|
1179
|
+
if (conReglas && conReglas.authority.level !== "autonoma" && !call.approved) {
|
|
1180
|
+
return { ok: false, needsApproval: true, error: "Requiere aprobaci\xF3n humana antes de ejecutarse." };
|
|
1181
|
+
}
|
|
1182
|
+
return due\u00F1o.run(call);
|
|
1183
|
+
},
|
|
1184
|
+
add(capability) {
|
|
1185
|
+
caps.set(capability.id, capability);
|
|
1186
|
+
tools = recopilar();
|
|
1187
|
+
},
|
|
1188
|
+
async remove(capabilityId) {
|
|
1189
|
+
const c = caps.get(capabilityId);
|
|
1190
|
+
if (!c) return;
|
|
1191
|
+
await c.dispose?.();
|
|
1192
|
+
caps.delete(capabilityId);
|
|
1193
|
+
tools = recopilar();
|
|
1194
|
+
},
|
|
1195
|
+
async dispose() {
|
|
1196
|
+
for (const c of caps.values()) await c.dispose?.();
|
|
1197
|
+
caps.clear();
|
|
1198
|
+
tools = [];
|
|
1199
|
+
}
|
|
1200
|
+
};
|
|
1201
|
+
}
|
|
1202
|
+
async function agentLoop(opts) {
|
|
1203
|
+
const maxSteps = opts.maxSteps ?? 6;
|
|
1204
|
+
return async function turno(message, history = []) {
|
|
1205
|
+
const steps = [];
|
|
1206
|
+
let lastResult;
|
|
1207
|
+
for (let i = 0; i < maxSteps; i++) {
|
|
1208
|
+
const salida = await opts.model({
|
|
1209
|
+
message,
|
|
1210
|
+
tools: opts.ecosystem.tools.map((t) => ({ name: t.name, description: t.description })),
|
|
1211
|
+
history,
|
|
1212
|
+
lastResult
|
|
1213
|
+
});
|
|
1214
|
+
if (!salida.action) return { text: salida.text, steps };
|
|
1215
|
+
let resultado = await opts.ecosystem.run(salida.action);
|
|
1216
|
+
if (resultado.needsApproval && opts.onApproval) {
|
|
1217
|
+
const aprobado = await opts.onApproval(salida.action.name, salida.action.args ?? {});
|
|
1218
|
+
if (aprobado) {
|
|
1219
|
+
resultado = await ejecutarAprobado(opts.ecosystem, salida.action);
|
|
1220
|
+
} else {
|
|
1221
|
+
resultado = { ok: false, error: "El usuario no lo autoriz\xF3." };
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
steps.push({ action: salida.action.name, ok: resultado.ok, error: resultado.error });
|
|
1225
|
+
lastResult = resultado;
|
|
1226
|
+
}
|
|
1227
|
+
return { text: "No pude completarlo en los pasos disponibles.", steps };
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
async function ejecutarAprobado(eco, action) {
|
|
1231
|
+
const t = eco.tools.find((x) => x.name === action.name);
|
|
1232
|
+
if (!t) return { ok: false, error: "La herramienta ya no existe." };
|
|
1233
|
+
if (t.authority.level === "prohibida") {
|
|
1234
|
+
return { ok: false, error: "Prohibida: ni con aprobaci\xF3n se ejecuta." };
|
|
1235
|
+
}
|
|
1236
|
+
return eco.run({ ...action, approved: true });
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
// src/mcp.ts
|
|
1240
|
+
function fromMCPTool(tool, authority, permission) {
|
|
1241
|
+
const warnings = [];
|
|
1242
|
+
if (tool.annotations?.destructiveHint && authority.consequence === "reversible") {
|
|
1243
|
+
warnings.push(
|
|
1244
|
+
`"${tool.name}": el servidor MCP la marca como destructiva, pero se declar\xF3 como reversible. Rev\xEDsalo.`
|
|
1245
|
+
);
|
|
1246
|
+
}
|
|
1247
|
+
if (tool.annotations?.readOnlyHint === false && authority.level === "autonoma") {
|
|
1248
|
+
warnings.push(
|
|
1249
|
+
`"${tool.name}": escribe y se le dio autoridad aut\xF3noma. Aseg\xFArate de que sea lo que quieres.`
|
|
1250
|
+
);
|
|
1251
|
+
}
|
|
1252
|
+
if (!tool.description?.trim()) {
|
|
1253
|
+
warnings.push(`"${tool.name}": el servidor MCP no la describe, as\xED que el agente no sabr\xE1 cu\xE1ndo usarla.`);
|
|
1254
|
+
}
|
|
1255
|
+
return {
|
|
1256
|
+
tool: {
|
|
1257
|
+
// Prefijo para que se vea de dónde viene y no choque con lo propio.
|
|
1258
|
+
name: normalizarNombre(`mcp_${tool.name}`),
|
|
1259
|
+
description: tool.description?.trim() || `Herramienta MCP "${tool.name}" (sin descripci\xF3n del servidor).`,
|
|
1260
|
+
authority,
|
|
1261
|
+
permission
|
|
1262
|
+
},
|
|
1263
|
+
warnings
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
function toMCPTool(tool) {
|
|
1267
|
+
if (tool.authority.level !== "autonoma") return null;
|
|
1268
|
+
return {
|
|
1269
|
+
name: tool.name,
|
|
1270
|
+
description: tool.description,
|
|
1271
|
+
annotations: {
|
|
1272
|
+
readOnlyHint: tool.authority.consequence === "reversible",
|
|
1273
|
+
destructiveHint: tool.authority.consequence === "irreversible",
|
|
1274
|
+
idempotentHint: false
|
|
1275
|
+
}
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
function toMCPTools(tools) {
|
|
1279
|
+
const published = [];
|
|
1280
|
+
const withheld = [];
|
|
1281
|
+
for (const t of tools) {
|
|
1282
|
+
const m = toMCPTool(t);
|
|
1283
|
+
if (m) published.push(m);
|
|
1284
|
+
else withheld.push(t.name);
|
|
1285
|
+
}
|
|
1286
|
+
return { published, withheld };
|
|
1287
|
+
}
|
|
1288
|
+
function normalizarNombre(n) {
|
|
1289
|
+
return n.toLowerCase().replace(/[^a-z0-9_]/g, "_").replace(/_+/g, "_").slice(0, 48);
|
|
1290
|
+
}
|
|
1291
|
+
|
|
590
1292
|
// src/index.ts
|
|
591
1293
|
var gandia = gandia_exports;
|
|
592
1294
|
var handeia = handeia_exports;
|
|
593
1295
|
// Annotate the CommonJS export names for ESM import in node:
|
|
594
1296
|
0 && (module.exports = {
|
|
1297
|
+
AGENT_PROTOCOL_VERSION,
|
|
1298
|
+
CONNECTOR_OF_OPERATION,
|
|
595
1299
|
VAIAError,
|
|
1300
|
+
agentLoop,
|
|
1301
|
+
capabilities,
|
|
1302
|
+
checkAuthority,
|
|
596
1303
|
defineCapability,
|
|
1304
|
+
defineEcosystem,
|
|
1305
|
+
fromMCPTool,
|
|
597
1306
|
gandia,
|
|
598
1307
|
handeia,
|
|
599
|
-
|
|
1308
|
+
http,
|
|
1309
|
+
httpTransport,
|
|
1310
|
+
local,
|
|
1311
|
+
mcp,
|
|
1312
|
+
mountAgent,
|
|
1313
|
+
requiresApproval,
|
|
1314
|
+
toMCPTool,
|
|
1315
|
+
toMCPTools,
|
|
1316
|
+
toManifest,
|
|
1317
|
+
validateActionCall,
|
|
1318
|
+
validateAgentSurface,
|
|
1319
|
+
validatePieces
|
|
600
1320
|
});
|
|
601
|
-
//# sourceMappingURL=index.cjs.map
|