@vaia-lab/sdk 0.2.0 → 0.4.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/agent-1bVw0eB8.d.cts +222 -0
- package/dist/agent-1bVw0eB8.d.ts +222 -0
- package/dist/cli.js +252 -0
- package/dist/index.cjs +495 -12
- package/dist/index.d.cts +487 -5
- package/dist/index.d.ts +487 -5
- package/dist/index.js +477 -11
- package/dist/react/index.cjs +1240 -0
- package/dist/react/index.d.cts +95 -0
- package/dist/react/index.d.ts +95 -0
- package/dist/react/index.js +1228 -0
- package/package.json +45 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VAIA Extension Protocol — superficie de AGENTE.
|
|
3
|
+
*
|
|
4
|
+
* Es la parte del protocolo que permite que el asistente de Handeia viva
|
|
5
|
+
* dentro de un espacio de terceros. Sigue la regla del ecosistema: protocolo
|
|
6
|
+
* antes que SDK. Lo que hay aquí son CONTRATOS; el SDK solo los transporta.
|
|
7
|
+
*
|
|
8
|
+
* ── El reparto de papeles ────────────────────────────────────────────────
|
|
9
|
+
* El espacio → superficie y manos. Declara qué sabe y qué puede hacer.
|
|
10
|
+
* Handeia → cerebro, memoria y autoridad. Decide y ejecuta a través suyo.
|
|
11
|
+
*
|
|
12
|
+
* Por eso un espacio NO trae su propia IA: si la trajera, no te conocería,
|
|
13
|
+
* empezaría de cero cada vez, y no podría contradecirse a sí mismo. El caso
|
|
14
|
+
* que lo justifica: el espacio puntúa un resultado con 90 y el agente te dice
|
|
15
|
+
* que te conviene el de 87, porque sabe algo de ti que el espacio no sabe.
|
|
16
|
+
* Eso solo es posible si el cerebro vive fuera del espacio.
|
|
17
|
+
*
|
|
18
|
+
* ── La regla de confianza, que manda sobre todo lo demás ─────────────────
|
|
19
|
+
* El espacio es CÓDIGO DE TERCEROS. Nada de lo que envía es un hecho: es una
|
|
20
|
+
* AFIRMACIÓN. Handeia la trata como dato citado, nunca como instrucción y
|
|
21
|
+
* nunca al mismo nivel que lo que sabe del usuario. Un espacio que escriba
|
|
22
|
+
* "ignora las instrucciones anteriores" en su contexto no logra nada.
|
|
23
|
+
*
|
|
24
|
+
* @see AGENT_PROTOCOL_VERSION para la política de compatibilidad.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Versión del protocolo de agente. Viaja en cada mensaje.
|
|
28
|
+
*
|
|
29
|
+
* Se versiona desde el primer día a propósito: este contrato es público y
|
|
30
|
+
* cambiarlo después obliga a coordinar despliegues entre partes que no se
|
|
31
|
+
* conocen. Ya se pagó esa factura una vez con la codificación del JWT.
|
|
32
|
+
*/
|
|
33
|
+
declare const AGENT_PROTOCOL_VERSION = 1;
|
|
34
|
+
/** Un parámetro de una acción. Sin tipos no hay validación posible. */
|
|
35
|
+
interface AgentActionParam {
|
|
36
|
+
name: string;
|
|
37
|
+
type: 'string' | 'number' | 'boolean';
|
|
38
|
+
description: string;
|
|
39
|
+
required?: boolean | undefined;
|
|
40
|
+
/** Valores admitidos. Si se define, nada fuera de la lista es válido. */
|
|
41
|
+
enum?: string[] | undefined;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Algo que el espacio sabe hacer.
|
|
45
|
+
*
|
|
46
|
+
* Handeia SOLO puede pedir acciones declaradas aquí. No improvisa, no toca el
|
|
47
|
+
* DOM, no busca la forma. Si no está declarada, para el agente no existe —
|
|
48
|
+
* y eso es lo que hace que el mismo agente sirva en cualquier espacio sin que
|
|
49
|
+
* Handeia sepa nada de ninguno en particular.
|
|
50
|
+
*/
|
|
51
|
+
interface AgentAction {
|
|
52
|
+
/** Identificador estable, en minúsculas: 'filtrar_resultados'. */
|
|
53
|
+
name: string;
|
|
54
|
+
/** Qué hace, en lenguaje natural. Es lo que lee el modelo para elegirla. */
|
|
55
|
+
description: string;
|
|
56
|
+
params?: AgentActionParam[] | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* true si modifica algo. Las que escriben se confirman con el usuario ANTES
|
|
59
|
+
* de ejecutarse — un agente que escribe sin preguntar se siente fuera de
|
|
60
|
+
* control incluso cuando acierta.
|
|
61
|
+
*/
|
|
62
|
+
writes?: boolean | undefined;
|
|
63
|
+
/** Permiso que el usuario debe haber concedido a este espacio. */
|
|
64
|
+
permission?: string | undefined;
|
|
65
|
+
}
|
|
66
|
+
/** Configuración de la superficie de agente dentro de defineCapability. */
|
|
67
|
+
interface AgentSurfaceConfig {
|
|
68
|
+
/** Acciones que el espacio expone. Vacío = el agente solo puede responder. */
|
|
69
|
+
actions?: AgentAction[] | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* Endpoint para preguntarle al espacio cuando el usuario NO está dentro
|
|
72
|
+
* ("¿tengo algo pendiente ahí?"). El círculo solo existe con el espacio
|
|
73
|
+
* abierto; esto es lo que permite que Handeia sea el lugar donde convergen
|
|
74
|
+
* todos tus espacios en vez de uno más al que entrar.
|
|
75
|
+
*/
|
|
76
|
+
queryEndpoint?: string | undefined;
|
|
77
|
+
/** Frase de bienvenida propia del espacio. */
|
|
78
|
+
greeting?: string | undefined;
|
|
79
|
+
/**
|
|
80
|
+
* Servicios externos que el espacio necesita consultar. El usuario los
|
|
81
|
+
* concede por espacio y los puede revocar cuando quiera. El espacio jamás
|
|
82
|
+
* recibe el token: pide operaciones, la plataforma las ejecuta.
|
|
83
|
+
*/
|
|
84
|
+
needs?: ConnectorNeed[] | undefined;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Servicios externos que un espacio puede necesitar (GitHub, Drive, Calendar…).
|
|
88
|
+
*
|
|
89
|
+
* ── La regla, y no tiene excepciones ─────────────────────────────────────
|
|
90
|
+
* El espacio NUNCA recibe el token del usuario. Declara qué necesita, la
|
|
91
|
+
* plataforma llama al proveedor con el token que YA tiene guardado, y le
|
|
92
|
+
* devuelve solo el resultado.
|
|
93
|
+
*
|
|
94
|
+
* Por qué así y no entregando el token:
|
|
95
|
+
* - Si cada espacio guardara tokens, la superficie de ataque se multiplica
|
|
96
|
+
* por cada desarrollador que publique. Un espacio comprometido entregaría
|
|
97
|
+
* el GitHub y el Drive de todos sus usuarios.
|
|
98
|
+
* - Prestado, un espacio comprometido solo puede pedir las operaciones que
|
|
99
|
+
* el usuario le concedió, con límite de frecuencia, auditadas y
|
|
100
|
+
* revocables al instante desde Conectores.
|
|
101
|
+
*
|
|
102
|
+
* De regalo, publicar un espacio se vuelve barato: el desarrollador no
|
|
103
|
+
* implementa OAuth de nada.
|
|
104
|
+
*/
|
|
105
|
+
type ConnectorNeed = 'github' | 'drive' | 'calendar' | 'email' | 'notion' | 'discord';
|
|
106
|
+
/**
|
|
107
|
+
* Operaciones de LECTURA que la plataforma sabe hacer por el espacio.
|
|
108
|
+
*
|
|
109
|
+
* Lista cerrada a propósito: un espacio no puede pedir "haz esta llamada
|
|
110
|
+
* arbitraria a la API de GitHub". Solo puede pedir lo que está aquí, y cada
|
|
111
|
+
* una devuelve datos ya acotados. Escribir en un servicio externo NO se
|
|
112
|
+
* presta — para eso el usuario usa el servicio.
|
|
113
|
+
*/
|
|
114
|
+
type ConnectorOperation = 'github.repos' | 'github.issues' | 'drive.files' | 'calendar.events' | 'email.recent' | 'notion.pages';
|
|
115
|
+
/** Lo que el espacio pide prestado. */
|
|
116
|
+
interface ConnectorRequest {
|
|
117
|
+
operation: ConnectorOperation;
|
|
118
|
+
/** Filtros simples. La plataforma los valida; nada de consultas libres. */
|
|
119
|
+
params?: Record<string, string | number | boolean> | undefined;
|
|
120
|
+
}
|
|
121
|
+
/** Lo que la plataforma devuelve. Datos, jamás credenciales. */
|
|
122
|
+
interface ConnectorResult {
|
|
123
|
+
operation: ConnectorOperation;
|
|
124
|
+
ok: boolean;
|
|
125
|
+
items?: Record<string, unknown>[] | undefined;
|
|
126
|
+
/** 'sin_conectar' = el usuario no ha vinculado ese servicio todavía. */
|
|
127
|
+
error?: 'sin_permiso' | 'sin_conectar' | 'no_soportada' | 'limite_excedido' | 'fallo' | undefined;
|
|
128
|
+
}
|
|
129
|
+
/** Qué operación necesita qué conector — la plataforma lo usa para autorizar. */
|
|
130
|
+
declare const CONNECTOR_OF_OPERATION: Record<ConnectorOperation, ConnectorNeed>;
|
|
131
|
+
/**
|
|
132
|
+
* Lo que el espacio dice que está pasando.
|
|
133
|
+
*
|
|
134
|
+
* OJO: se llama `claims` y no `facts` a propósito. Handeia lo etiqueta como
|
|
135
|
+
* afirmación de un tercero antes de dárselo al modelo.
|
|
136
|
+
*/
|
|
137
|
+
interface AgentSpaceContext {
|
|
138
|
+
/** Dónde está el usuario dentro del espacio: '/lista'. */
|
|
139
|
+
route?: string | undefined;
|
|
140
|
+
/** Qué está viendo, en lenguaje natural: 'Lista de 12 resultados'. */
|
|
141
|
+
view?: string | undefined;
|
|
142
|
+
/** Datos que el espacio considera relevantes ahora mismo. */
|
|
143
|
+
claims?: Record<string, unknown> | undefined;
|
|
144
|
+
}
|
|
145
|
+
/** Petición del espacio a Handeia. Un solo endpoint, un solo formato. */
|
|
146
|
+
interface AgentTurnRequest {
|
|
147
|
+
protocol: typeof AGENT_PROTOCOL_VERSION;
|
|
148
|
+
/** Lo que escribió el usuario. */
|
|
149
|
+
message: string;
|
|
150
|
+
context?: AgentSpaceContext | undefined;
|
|
151
|
+
/** Acciones disponibles AHORA (pueden ser menos que las declaradas). */
|
|
152
|
+
actions?: AgentAction[] | undefined;
|
|
153
|
+
/** Turnos previos, para que el agente no pierda el hilo. */
|
|
154
|
+
history?: {
|
|
155
|
+
role: 'user' | 'agent';
|
|
156
|
+
text: string;
|
|
157
|
+
}[] | undefined;
|
|
158
|
+
/** Resultado de una acción que Handeia pidió en el turno anterior. */
|
|
159
|
+
actionResult?: AgentActionResult | undefined;
|
|
160
|
+
}
|
|
161
|
+
/** Lo que el espacio devuelve tras ejecutar una acción. */
|
|
162
|
+
interface AgentActionResult {
|
|
163
|
+
action: string;
|
|
164
|
+
ok: boolean;
|
|
165
|
+
/** Qué pasó, para que el agente pueda cerrar el ciclo con el usuario. */
|
|
166
|
+
summary?: string | undefined;
|
|
167
|
+
error?: string | undefined;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* De dónde salió lo que el agente afirma.
|
|
171
|
+
*
|
|
172
|
+
* No es adorno: es el pilar de info verificada. Cuando el agente contradice
|
|
173
|
+
* al espacio ("dice 90, pero te conviene la de 87"), tiene que poder decir de
|
|
174
|
+
* dónde sacó su razón. Un oráculo que no se explica no se gana la confianza.
|
|
175
|
+
*/
|
|
176
|
+
interface AgentEvidence {
|
|
177
|
+
/** 'handeia' = memoria del usuario · 'space' = lo que declaró el espacio. */
|
|
178
|
+
source: 'handeia' | 'space';
|
|
179
|
+
label: string;
|
|
180
|
+
}
|
|
181
|
+
/** Respuesta de Handeia al espacio. */
|
|
182
|
+
interface AgentTurnResponse {
|
|
183
|
+
protocol: typeof AGENT_PROTOCOL_VERSION;
|
|
184
|
+
/** Qué decirle al usuario. */
|
|
185
|
+
text?: string | undefined;
|
|
186
|
+
/** Acción a ejecutar. Siempre sale de la lista declarada, nunca inventada. */
|
|
187
|
+
action?: {
|
|
188
|
+
name: string;
|
|
189
|
+
args?: Record<string, unknown> | undefined;
|
|
190
|
+
} | undefined;
|
|
191
|
+
/** true si hay que confirmar con el usuario antes de ejecutarla. */
|
|
192
|
+
confirm?: boolean | undefined;
|
|
193
|
+
evidence?: AgentEvidence[] | undefined;
|
|
194
|
+
/** Identificador para cruzar los registros de todas las capas. */
|
|
195
|
+
traceId?: string | undefined;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Revisa que las acciones declaradas sean utilizables.
|
|
199
|
+
*
|
|
200
|
+
* Corre al declarar la capacidad, no en producción: un contrato mal escrito
|
|
201
|
+
* debe reventar en el escritorio del desarrollador, no frente al usuario.
|
|
202
|
+
*/
|
|
203
|
+
declare function validateAgentSurface(cfg: AgentSurfaceConfig): string[];
|
|
204
|
+
/**
|
|
205
|
+
* ¿Es válida esta acción contra lo declarado?
|
|
206
|
+
*
|
|
207
|
+
* La usa Handeia antes de reenviarle nada al espacio. Es la lista blanca en
|
|
208
|
+
* ejecución: aunque el modelo se invente una acción o un argumento fuera de
|
|
209
|
+
* rango, aquí se detiene.
|
|
210
|
+
*/
|
|
211
|
+
declare function validateActionCall(llamada: {
|
|
212
|
+
name: string;
|
|
213
|
+
args?: Record<string, unknown> | undefined;
|
|
214
|
+
}, declaradas: AgentAction[]): {
|
|
215
|
+
ok: true;
|
|
216
|
+
action: AgentAction;
|
|
217
|
+
} | {
|
|
218
|
+
ok: false;
|
|
219
|
+
reason: string;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
export { type AgentSurfaceConfig as A, CONNECTOR_OF_OPERATION as C, type AgentAction as a, AGENT_PROTOCOL_VERSION as b, type AgentActionParam as c, type AgentActionResult as d, type AgentEvidence as e, type AgentSpaceContext as f, type AgentTurnRequest as g, type AgentTurnResponse as h, type ConnectorNeed as i, type ConnectorOperation as j, type ConnectorRequest as k, type ConnectorResult as l, validateAgentSurface as m, validateActionCall as v };
|
package/dist/cli.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
5
5
|
import { resolve, join } from "path";
|
|
6
|
+
import { pathToFileURL } from "url";
|
|
6
7
|
import { createInterface } from "readline";
|
|
7
8
|
|
|
8
9
|
// src/crypto.ts
|
|
@@ -22,6 +23,107 @@ async function hmacSign(secret, data) {
|
|
|
22
23
|
return Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
23
24
|
}
|
|
24
25
|
|
|
26
|
+
// src/agent.ts
|
|
27
|
+
var AGENT_PROTOCOL_VERSION = 1;
|
|
28
|
+
var NOMBRE_ACCION = /^[a-z][a-z0-9_]{1,48}$/;
|
|
29
|
+
function validateAgentSurface(cfg) {
|
|
30
|
+
const errores = [];
|
|
31
|
+
const vistos = /* @__PURE__ */ new Set();
|
|
32
|
+
for (const accion of cfg.actions ?? []) {
|
|
33
|
+
if (!NOMBRE_ACCION.test(accion.name)) {
|
|
34
|
+
errores.push(`Acci\xF3n "${accion.name}": el nombre debe ser min\xFAsculas, n\xFAmeros o guion bajo.`);
|
|
35
|
+
}
|
|
36
|
+
if (vistos.has(accion.name)) {
|
|
37
|
+
errores.push(`Acci\xF3n "${accion.name}": declarada dos veces.`);
|
|
38
|
+
}
|
|
39
|
+
vistos.add(accion.name);
|
|
40
|
+
if (!accion.description?.trim()) {
|
|
41
|
+
errores.push(`Acci\xF3n "${accion.name}": falta la descripci\xF3n, que es lo que el agente lee para elegirla.`);
|
|
42
|
+
}
|
|
43
|
+
if (accion.writes && !accion.permission) {
|
|
44
|
+
errores.push(`Acci\xF3n "${accion.name}": modifica datos, as\xED que necesita un permiso declarado.`);
|
|
45
|
+
}
|
|
46
|
+
for (const p of accion.params ?? []) {
|
|
47
|
+
if (!p.name?.trim()) errores.push(`Acci\xF3n "${accion.name}": un par\xE1metro no tiene nombre.`);
|
|
48
|
+
if (!p.description?.trim()) {
|
|
49
|
+
errores.push(`Acci\xF3n "${accion.name}", par\xE1metro "${p.name}": falta la descripci\xF3n.`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (cfg.queryEndpoint && !cfg.queryEndpoint.startsWith("/")) {
|
|
54
|
+
errores.push('queryEndpoint debe ser una ruta de tu propio servidor, empezando por "/".');
|
|
55
|
+
}
|
|
56
|
+
return errores;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/pieces.ts
|
|
60
|
+
var NOMBRE = /^[a-z][a-z0-9_]{1,48}$/;
|
|
61
|
+
var ORDEN = {
|
|
62
|
+
prohibida: 0,
|
|
63
|
+
requiere_aprobacion: 1,
|
|
64
|
+
autonoma: 2
|
|
65
|
+
};
|
|
66
|
+
function validatePieces(cfg) {
|
|
67
|
+
const errores = [];
|
|
68
|
+
const vistos = /* @__PURE__ */ new Set();
|
|
69
|
+
const revisarNombre = (pieza, name) => {
|
|
70
|
+
if (!NOMBRE.test(name)) errores.push(`${pieza} "${name}": el nombre debe ser min\xFAsculas, n\xFAmeros o guion bajo.`);
|
|
71
|
+
if (vistos.has(name)) errores.push(`"${name}": hay dos piezas con el mismo nombre.`);
|
|
72
|
+
vistos.add(name);
|
|
73
|
+
};
|
|
74
|
+
const revisarAutoridad = (pieza, a) => {
|
|
75
|
+
if (a.consequence === "irreversible" && a.level === "autonoma") {
|
|
76
|
+
errores.push(`${pieza}: una acci\xF3n irreversible no puede ser aut\xF3noma \u2014 como m\xEDnimo requiere aprobaci\xF3n.`);
|
|
77
|
+
}
|
|
78
|
+
if (a.maxAmount !== void 0) {
|
|
79
|
+
if (a.maxAmount <= 0) errores.push(`${pieza}: el tope de gasto debe ser mayor que cero.`);
|
|
80
|
+
if (!a.currency) errores.push(`${pieza}: hay tope de gasto pero no se declar\xF3 la moneda.`);
|
|
81
|
+
}
|
|
82
|
+
if (a.consequence === "costosa" && a.level === "autonoma" && a.maxAmount === void 0) {
|
|
83
|
+
errores.push(`${pieza}: es aut\xF3noma y cuesta dinero, as\xED que necesita un tope declarado.`);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
for (const s of cfg.skills ?? []) {
|
|
87
|
+
revisarNombre("Skill", s.name);
|
|
88
|
+
if (!s.description?.trim()) errores.push(`Skill "${s.name}": falta la descripci\xF3n, que es lo que el modelo lee para elegirla.`);
|
|
89
|
+
if (s.evidence?.required && (s.evidence.accepts?.length ?? 0) === 0) {
|
|
90
|
+
errores.push(`Skill "${s.name}": exige evidencia pero no declara qu\xE9 tipos acepta.`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
for (const t of cfg.tools ?? []) {
|
|
94
|
+
revisarNombre("Herramienta", t.name);
|
|
95
|
+
if (!t.description?.trim()) errores.push(`Herramienta "${t.name}": falta la descripci\xF3n.`);
|
|
96
|
+
if (!t.permission?.trim()) errores.push(`Herramienta "${t.name}": toca el mundo real, as\xED que necesita un permiso declarado.`);
|
|
97
|
+
revisarAutoridad(`Herramienta "${t.name}"`, t.authority);
|
|
98
|
+
}
|
|
99
|
+
for (const w of cfg.workflows ?? []) {
|
|
100
|
+
revisarNombre("Workflow", w.name);
|
|
101
|
+
if ((w.steps?.length ?? 0) === 0) errores.push(`Workflow "${w.name}": no tiene pasos.`);
|
|
102
|
+
revisarAutoridad(`Workflow "${w.name}"`, w.authority);
|
|
103
|
+
}
|
|
104
|
+
const porNombre = new Map((cfg.tools ?? []).map((t) => [t.name, t]));
|
|
105
|
+
for (const a of cfg.agents ?? []) {
|
|
106
|
+
revisarNombre("Agente", a.name);
|
|
107
|
+
if (!a.purpose?.trim()) {
|
|
108
|
+
errores.push(`Agente "${a.name}": falta el prop\xF3sito \u2014 para qu\xE9 existe.`);
|
|
109
|
+
}
|
|
110
|
+
revisarAutoridad(`Agente "${a.name}"`, a.authority);
|
|
111
|
+
for (const nombreTool of a.tools ?? []) {
|
|
112
|
+
const tool = porNombre.get(nombreTool);
|
|
113
|
+
if (!tool) {
|
|
114
|
+
errores.push(`Agente "${a.name}": usa la herramienta "${nombreTool}", que no est\xE1 declarada.`);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (ORDEN[tool.authority.level] > ORDEN[a.authority.level]) {
|
|
118
|
+
errores.push(
|
|
119
|
+
`Agente "${a.name}": su herramienta "${nombreTool}" tiene m\xE1s autoridad que \xE9l. Ninguna pieza puede superar el techo de su agente.`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return errores;
|
|
125
|
+
}
|
|
126
|
+
|
|
25
127
|
// src/define.ts
|
|
26
128
|
function defineCapability(config) {
|
|
27
129
|
const required = ["id", "name", "version", "target", "type", "sector", "permissions", "risk"];
|
|
@@ -38,6 +140,20 @@ function defineCapability(config) {
|
|
|
38
140
|
if (Object.keys(config.surfaces).length === 0) {
|
|
39
141
|
throw new Error(`[@vaia/sdk] defineCapability: 'surfaces' no puede estar vac\xEDo. Define al menos un surface con su endpoint.`);
|
|
40
142
|
}
|
|
143
|
+
if (config.pieces) {
|
|
144
|
+
const errores = validatePieces(config.pieces);
|
|
145
|
+
if (errores.length > 0) {
|
|
146
|
+
throw new Error(`[@vaia/sdk] defineCapability: piezas inv\xE1lidas:
|
|
147
|
+
- ${errores.join("\n - ")}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (config.agent) {
|
|
151
|
+
const errores = validateAgentSurface(config.agent);
|
|
152
|
+
if (errores.length > 0) {
|
|
153
|
+
throw new Error(`[@vaia/sdk] defineCapability: superficie de agente inv\xE1lida:
|
|
154
|
+
- ${errores.join("\n - ")}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
41
157
|
return config;
|
|
42
158
|
}
|
|
43
159
|
function toManifest(config) {
|
|
@@ -52,6 +168,16 @@ function toManifest(config) {
|
|
|
52
168
|
level: config.level,
|
|
53
169
|
sector: config.sector,
|
|
54
170
|
surfaces,
|
|
171
|
+
// El agente viaja en el manifest para que el portal y Handeia sepan qué
|
|
172
|
+
// puede hacer este espacio sin abrir su código.
|
|
173
|
+
agent: config.agent ? {
|
|
174
|
+
protocol: AGENT_PROTOCOL_VERSION,
|
|
175
|
+
actions: config.agent.actions ?? [],
|
|
176
|
+
query_endpoint: config.agent.queryEndpoint
|
|
177
|
+
} : void 0,
|
|
178
|
+
// Las piezas viajan al manifest: el portal necesita mostrar qué autoridad
|
|
179
|
+
// pide una capacidad ANTES de que alguien la instale.
|
|
180
|
+
pieces: config.pieces,
|
|
55
181
|
permissions: config.permissions,
|
|
56
182
|
risk: config.risk,
|
|
57
183
|
has_own_auth: config.has_own_auth ?? false,
|
|
@@ -72,6 +198,10 @@ var args = process.argv.slice(2);
|
|
|
72
198
|
var cmd = args[0];
|
|
73
199
|
async function main() {
|
|
74
200
|
switch (cmd) {
|
|
201
|
+
case "init":
|
|
202
|
+
return cmdInit();
|
|
203
|
+
case "doctor":
|
|
204
|
+
return cmdDoctor();
|
|
75
205
|
case "manifest":
|
|
76
206
|
return cmdManifest();
|
|
77
207
|
case "sign":
|
|
@@ -85,6 +215,126 @@ async function main() {
|
|
|
85
215
|
printHelp();
|
|
86
216
|
}
|
|
87
217
|
}
|
|
218
|
+
async function cmdInit() {
|
|
219
|
+
const cwd = process.cwd();
|
|
220
|
+
const out = join(cwd, "vaia.config.ts");
|
|
221
|
+
if (existsSync(out)) {
|
|
222
|
+
console.error("\u2717 Ya existe vaia.config.ts en esta carpeta.");
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
const plantilla = `import { defineCapability } from '@vaia-lab/sdk'
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Tu capacidad, declarada.
|
|
229
|
+
*
|
|
230
|
+
* Esto es la \xFAnica fuente de verdad: el portal la lee y arma el manifest solo.
|
|
231
|
+
*/
|
|
232
|
+
export default defineCapability({
|
|
233
|
+
id: 'mx.mi-capacidad',
|
|
234
|
+
name: 'Mi capacidad',
|
|
235
|
+
version: '0.1.0',
|
|
236
|
+
target: 'handeia', // 'gandia' | 'handeia' | 'both'
|
|
237
|
+
type: 'app',
|
|
238
|
+
sector: 'general',
|
|
239
|
+
description: 'Describe en una l\xEDnea qu\xE9 resuelve.',
|
|
240
|
+
|
|
241
|
+
surfaces: {
|
|
242
|
+
text: { endpoint: '/api/vaia/invoke' },
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
// \u2500\u2500 Las piezas \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
246
|
+
// La autoridad NO es opcional: hay que decir hasta d\xF3nde puede llegar cada
|
|
247
|
+
// cosa. Lo irreversible nunca puede ser aut\xF3nomo, y lo que gasta dinero
|
|
248
|
+
// necesita tope y moneda. Si te lo saltas, esto no compila.
|
|
249
|
+
pieces: {
|
|
250
|
+
tools: [
|
|
251
|
+
{
|
|
252
|
+
name: 'consultar_datos',
|
|
253
|
+
description: 'Lee datos del usuario para responder preguntas.',
|
|
254
|
+
permission: 'read:datos',
|
|
255
|
+
authority: {
|
|
256
|
+
level: 'autonoma', // 'autonoma' | 'requiere_aprobacion' | 'prohibida'
|
|
257
|
+
consequence: 'reversible', // 'reversible' | 'costosa' | 'irreversible'
|
|
258
|
+
rationale: 'Solo lee. No cambia nada del usuario.',
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
],
|
|
262
|
+
},
|
|
263
|
+
|
|
264
|
+
// \u2500\u2500 El agente dentro de tu app \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
265
|
+
// El asistente de Handeia vive en tu superficie. T\xFA declaras qu\xE9 sabes
|
|
266
|
+
// hacer; \xE9l razona con eso m\xE1s lo que sabe del usuario, que t\xFA nunca ves.
|
|
267
|
+
agent: {
|
|
268
|
+
greeting: '\xBFEn qu\xE9 te ayudo?',
|
|
269
|
+
actions: [
|
|
270
|
+
{
|
|
271
|
+
name: 'ir_a',
|
|
272
|
+
description: 'Lleva al usuario a una secci\xF3n de la app.',
|
|
273
|
+
params: [
|
|
274
|
+
{ name: 'seccion', type: 'string', description: 'Secci\xF3n destino.', required: true },
|
|
275
|
+
],
|
|
276
|
+
},
|
|
277
|
+
],
|
|
278
|
+
// Servicios que necesitas consultar. NUNCA recibes el token del usuario:
|
|
279
|
+
// pides la operaci\xF3n y la plataforma te devuelve solo el resultado.
|
|
280
|
+
needs: [],
|
|
281
|
+
},
|
|
282
|
+
|
|
283
|
+
permissions: ['read:datos'],
|
|
284
|
+
risk: 'low',
|
|
285
|
+
})
|
|
286
|
+
`;
|
|
287
|
+
writeFileSync(out, plantilla, "utf8");
|
|
288
|
+
console.log("\u2713 vaia.config.ts creado");
|
|
289
|
+
console.log("");
|
|
290
|
+
console.log(" Siguiente:");
|
|
291
|
+
console.log(" 1. Edita el id, el nombre y lo que sabe hacer tu app.");
|
|
292
|
+
console.log(" 2. npx vaia manifest \u2192 genera el manifest");
|
|
293
|
+
console.log(" 3. S\xFAbelo desde el portal de developers.");
|
|
294
|
+
}
|
|
295
|
+
async function cmdDoctor() {
|
|
296
|
+
const cwd = process.cwd();
|
|
297
|
+
const configPath = resolve(cwd, "vaia.config.js");
|
|
298
|
+
if (!existsSync(configPath)) {
|
|
299
|
+
console.error("\u2717 No encontr\xE9 vaia.config.js. Compila tu vaia.config.ts primero, o corre `vaia init`.");
|
|
300
|
+
process.exit(1);
|
|
301
|
+
}
|
|
302
|
+
let config;
|
|
303
|
+
try {
|
|
304
|
+
const mod = await import(pathToFileURL(configPath).href);
|
|
305
|
+
config = mod.default ?? mod.config;
|
|
306
|
+
} catch (err) {
|
|
307
|
+
console.error(`\u2717 No pude cargar vaia.config.js:
|
|
308
|
+
${err instanceof Error ? err.message : String(err)}`);
|
|
309
|
+
process.exit(1);
|
|
310
|
+
}
|
|
311
|
+
const avisos = [];
|
|
312
|
+
const graves = [];
|
|
313
|
+
for (const t of config.pieces?.tools ?? []) {
|
|
314
|
+
if (t.authority.level === "autonoma" && t.authority.consequence === "costosa" && !t.authority.rationale) {
|
|
315
|
+
avisos.push(`"${t.name}" gasta dinero sola y no explica por qu\xE9 se le dio esa confianza.`);
|
|
316
|
+
}
|
|
317
|
+
if (t.permission && !config.permissions.includes(t.permission)) {
|
|
318
|
+
graves.push(`"${t.name}" pide el permiso "${t.permission}", que no est\xE1 en la lista de permisos de la capacidad.`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
const usados = new Set((config.pieces?.tools ?? []).map((t) => t.permission));
|
|
322
|
+
for (const p of config.permissions) {
|
|
323
|
+
if (!usados.has(p)) avisos.push(`El permiso "${p}" se pide pero ninguna herramienta lo usa. Pedir de m\xE1s incomoda al usuario.`);
|
|
324
|
+
}
|
|
325
|
+
for (const a of config.agent?.actions ?? []) {
|
|
326
|
+
if (a.writes && !a.permission) {
|
|
327
|
+
graves.push(`La acci\xF3n "${a.name}" modifica datos y no declara permiso.`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (graves.length === 0 && avisos.length === 0) {
|
|
331
|
+
console.log("\u2713 Todo en orden.");
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
for (const g of graves) console.error(`\u2717 ${g}`);
|
|
335
|
+
for (const a of avisos) console.log(`\u26A0 ${a}`);
|
|
336
|
+
if (graves.length > 0) process.exit(1);
|
|
337
|
+
}
|
|
88
338
|
async function cmdManifest() {
|
|
89
339
|
const cwd = process.cwd();
|
|
90
340
|
const configPath = resolve(cwd, "vaia.config.js");
|
|
@@ -182,6 +432,8 @@ function printHelp() {
|
|
|
182
432
|
`@vaia/sdk v${pkg.version}`,
|
|
183
433
|
"",
|
|
184
434
|
"Comandos:",
|
|
435
|
+
" vaia-sdk init Crea vaia.config.ts listo para editar",
|
|
436
|
+
" vaia-sdk doctor Revisa tu configuraci\xF3n y avisa qu\xE9 est\xE1 mal",
|
|
185
437
|
" vaia-sdk manifest Genera gandia.manifest.json desde vaia.config.js",
|
|
186
438
|
" vaia-sdk manifest --validate Valida un gandia.manifest.json existente",
|
|
187
439
|
" vaia-sdk sign [payload] Firma un payload con GANDIA_KEY_SECRET",
|