@7ots/cli 0.1.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/.env.example +140 -0
- package/LICENSE +21 -0
- package/README.md +655 -0
- package/brand/apple-touch-icon.png +0 -0
- package/brand/favicon.svg +4 -0
- package/brand/icon-512.png +0 -0
- package/brand/mark-mono.svg +3 -0
- package/brand/mark.svg +4 -0
- package/brand/og-es.png +0 -0
- package/brand/og-pt.png +0 -0
- package/brand/og.png +0 -0
- package/brand/ots.json +11 -0
- package/brand/tokens.css +59 -0
- package/brand/wordmark.svg +5 -0
- package/cli/7ots.mjs +400 -0
- package/cli/lib/brain.mjs +143 -0
- package/cli/lib/config.mjs +70 -0
- package/cli/lib/hooks.mjs +151 -0
- package/cli/lib/i18n.mjs +480 -0
- package/cli/lib/identity.mjs +51 -0
- package/cli/lib/install.mjs +79 -0
- package/cli/lib/lines.mjs +73 -0
- package/cli/lib/meet.mjs +166 -0
- package/cli/lib/open.mjs +19 -0
- package/cli/lib/orquesta.mjs +84 -0
- package/cli/lib/paths.mjs +49 -0
- package/cli/lib/pet-core.mjs +184 -0
- package/cli/lib/pet-server.mjs +296 -0
- package/cli/lib/prompts.mjs +75 -0
- package/cli/lib/terminal.mjs +145 -0
- package/cli/lib/ui.mjs +234 -0
- package/cli/lib/wizard.mjs +279 -0
- package/cli/pet/electron/main.cjs +86 -0
- package/cli/pet/pet.html +404 -0
- package/dist/7ots.esm.js +627 -0
- package/dist/7ots.esm.js.map +7 -0
- package/dist/7ots.iife.js +627 -0
- package/dist/7ots.iife.js.map +7 -0
- package/llms.txt +57 -0
- package/package.json +73 -0
- package/server/admin.mjs +213 -0
- package/server/apuchat-relay.mjs +170 -0
- package/server/channels/agent.mjs +136 -0
- package/server/channels/apuchat.mjs +364 -0
- package/server/channels/apumail.mjs +211 -0
- package/server/channels/index.mjs +69 -0
- package/server/contact.mjs +151 -0
- package/server/demo.mjs +154 -0
- package/server/i18n.mjs +54 -0
- package/server/identity.mjs +108 -0
- package/server/llm.mjs +413 -0
- package/server/platform/auth.mjs +290 -0
- package/server/platform/crypto.mjs +80 -0
- package/server/platform/db.mjs +130 -0
- package/server/platform/routes.mjs +388 -0
- package/server/platform/runtime.mjs +180 -0
- package/server/platform/store.mjs +331 -0
- package/server/sdk.mjs +125 -0
- package/server/server.mjs +456 -0
- package/server/settings.mjs +177 -0
- package/server/tts.mjs +186 -0
- package/skills/7ots/SKILL.md +59 -0
- package/src/actions/ActionRegistry.js +254 -0
- package/src/actions/PageTools.js +270 -0
- package/src/actions/builtins.js +565 -0
- package/src/auth/AuthManager.js +128 -0
- package/src/avatar/AvatarStage.js +473 -0
- package/src/character/AvatarEditor.js +829 -0
- package/src/character/Character.js +1191 -0
- package/src/character/motion.js +794 -0
- package/src/character/parts.js +559 -0
- package/src/context/ContextManager.js +472 -0
- package/src/core/AgentBrain.js +341 -0
- package/src/core/AgentWidget.js +1059 -0
- package/src/core/EventBus.js +61 -0
- package/src/core/Proactivity.js +363 -0
- package/src/core/storage.js +35 -0
- package/src/i18n/index.js +193 -0
- package/src/i18n/messages/character.js +543 -0
- package/src/i18n/messages/identity.js +141 -0
- package/src/i18n/messages/platform.js +114 -0
- package/src/i18n/messages/server.js +630 -0
- package/src/i18n/messages/widget.js +362 -0
- package/src/identity/ContactCard.js +351 -0
- package/src/identity/Face.js +148 -0
- package/src/identity/random.js +112 -0
- package/src/identity/schema.js +262 -0
- package/src/index.js +117 -0
- package/src/integrations/apuchat.js +212 -0
- package/src/integrations/apumail.js +88 -0
- package/src/llm/ProxyLLM.js +87 -0
- package/src/mcp/McpClient.js +153 -0
- package/src/ui/Companion.js +498 -0
- package/src/ui/UIManager.js +425 -0
- package/src/ui/VirtualPointer.js +746 -0
- package/src/ui/markdown.js +94 -0
- package/src/voice/VoiceEngine.js +227 -0
package/README.md
ADDED
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
<p align="center"><img src="brand/wordmark.svg" alt="7ots" width="220"></p>
|
|
2
|
+
|
|
3
|
+
<p align="center"><b>Tu web, con alguien dentro.</b><br>
|
|
4
|
+
Un agente web open source que ve la página, habla y actúa por tu visitante. Se instala con una etiqueta <code><script></code>.<br>
|
|
5
|
+
<a href="https://7ots.com">7ots.com</a> · <a href="docs/ARCHITECTURE.md">Arquitectura</a> · <a href="docs/BRAND.md">Marca</a></p>
|
|
6
|
+
|
|
7
|
+
7ots mete en tu web un agente con avatar 3D que *entiende la página*, *actúa sobre ella*
|
|
8
|
+
(navega, pulsa, rellena formularios, resalta, abre modales), usa las APIs y el **MCP
|
|
9
|
+
autenticado** de tu sitio con la sesión del usuario, y cuando hace falta deriva a una persona
|
|
10
|
+
por **apuchat.com** o abre un ticket por **apumail.com**.
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
┌──────────── tu sitio ────────────┐ ┌──── server/ (tu proxy) ────┐
|
|
14
|
+
│ <ots-agent> (Shadow DOM) │ /chat │ Claude u OpenAI │
|
|
15
|
+
│ avatar 3D · chat · voz │ ───────► │ TTS (OpenAI / ElevenLabs) │
|
|
16
|
+
│ ContextManager ← DOM, rutas │ │ apumail · apuchat │
|
|
17
|
+
│ ActionRegistry → click, forms… │ │ claves en .env │
|
|
18
|
+
│ McpClient ──► /mcp (JWT) │ └────────────────────────────┘
|
|
19
|
+
└───────────────────────────────────┘
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
- **Cero dependencias en el navegador.** Web Component nativo con Shadow DOM: no rompe tu CSS ni el tuyo lo rompe.
|
|
23
|
+
- **Las claves nunca llegan al navegador.** Todo pasa por un proxy Node sin estado (`server/`).
|
|
24
|
+
- **Proactivo, sin ser pesado.** Saluda, detecta errores de formulario, dudas largas en una sección… con límites de frecuencia y un botón de silencio.
|
|
25
|
+
- **Seguro por diseño.** Solo ejecuta herramientas registradas, valida argumentos contra JSON Schema, pide confirmación real antes de acciones con efectos, no lee campos de contraseña ni bloques `data-ots-private`, y trata el contenido de la página como datos, no como órdenes.
|
|
26
|
+
|
|
27
|
+
> Estado: `0.1.0` — prototipo funcional. APIs sujetas a cambios.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Inicio rápido
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install
|
|
35
|
+
cp .env.example .env # LLM_PROVIDER=mock funciona sin ninguna clave
|
|
36
|
+
npm run dev # → http://localhost:8787/examples/
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
La demo (**Nimbus**, un SaaS ficticio) incluye login con JWT, un MCP autenticado, acciones
|
|
40
|
+
propias, plantillas y reglas proactivas. En modo `mock` prueba a escribir:
|
|
41
|
+
|
|
42
|
+
| Escribe | Qué pasa |
|
|
43
|
+
|---|---|
|
|
44
|
+
| `precios` | resalta la sección de precios |
|
|
45
|
+
| `saldo` (tras iniciar sesión) | acción propia autenticada con el JWT |
|
|
46
|
+
| `/tool nimbus__listar_pedidos {}` | herramienta del MCP autenticado |
|
|
47
|
+
| `/tool navigate {"url":"./docs.html"}` | navega y retoma la conversación en la otra página |
|
|
48
|
+
| `/tool iniciar_prueba {"plan":"Pro"}` | diálogo de confirmación antes de ejecutar |
|
|
49
|
+
|
|
50
|
+
Para respuestas reales, en `.env`:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
LLM_PROVIDER=anthropic # usa ANTHROPIC_API_KEY · modelo por defecto claude-opus-5-5
|
|
54
|
+
# o
|
|
55
|
+
LLM_PROVIDER=openai # usa OPENAI_API_KEY · LLM_BASE_URL para compatibles (Groq, Ollama…)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Otros scripts: `npm run build` (genera `dist/7ots.esm.js` y `dist/7ots.iife.js`), `npm run check` (sintaxis).
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Integración en tu sitio
|
|
63
|
+
|
|
64
|
+
```html
|
|
65
|
+
<!-- Solo si quieres avatar 3D: TalkingHead importa "three" por nombre -->
|
|
66
|
+
<script type="importmap">
|
|
67
|
+
{ "imports": {
|
|
68
|
+
"three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js",
|
|
69
|
+
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"
|
|
70
|
+
} }
|
|
71
|
+
</script>
|
|
72
|
+
|
|
73
|
+
<script src="/7ots/7ots.iife.js"></script>
|
|
74
|
+
<script>
|
|
75
|
+
const agent = SevenOts.init({
|
|
76
|
+
endpoint: 'https://api.tusitio.com/api/agent', // tu proxy (server/)
|
|
77
|
+
siteKey: 'pk_tusitio', // identificador público
|
|
78
|
+
agent: { name: 'Ana', role: 'asesora', siteName: 'Acme', instructions: 'Recomienda el plan anual.' },
|
|
79
|
+
avatar: { url: '/avatars/ana.glb', body: 'F' },
|
|
80
|
+
auth: { getToken: () => localStorage.getItem('jwt') },
|
|
81
|
+
mcp: [{ url: '/mcp', name: 'acme', requiresAuth: true }],
|
|
82
|
+
});
|
|
83
|
+
</script>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
O como módulo: `import { init } from '@7ots/cli'`. O declarativo:
|
|
87
|
+
`<ots-agent endpoint="/api/agent" site-key="pk_x" name="Ana"></ots-agent>`.
|
|
88
|
+
|
|
89
|
+
### Configuración
|
|
90
|
+
|
|
91
|
+
| Clave | Descripción |
|
|
92
|
+
|---|---|
|
|
93
|
+
| `endpoint` | URL del proxy. Obligatoria. |
|
|
94
|
+
| `siteKey` | Identificador público que el proxy puede exigir (`SITE_KEYS`). |
|
|
95
|
+
| `identity` | `true`, URL o ficha — quién es el agente: nombre, aspecto, voz, contacto (ver «Identidad del agente»). |
|
|
96
|
+
| `agent` | `{ name, role, siteName, language, instructions, expressive }` — personalidad e instrucciones del negocio. |
|
|
97
|
+
| `avatar` | `{ url, body: 'F'\|'M', mood, cameraView }` o `false`. Sin WebGL cae a un avatar 2D. |
|
|
98
|
+
| `voice` | `{ tts: 'proxy'\|'browser'\|false, stt: true, lang }` o `false`. |
|
|
99
|
+
| `auth` | `{ token }`, `{ getToken, getUser }` o `{ credentials: 'include' }` (cookies), `exposeClaims`. |
|
|
100
|
+
| `mcp` | `[{ url, name, requiresAuth, filter, confirm }]` — servidores MCP (Streamable HTTP). |
|
|
101
|
+
| `actions` | Acciones propias (ver abajo). |
|
|
102
|
+
| `templates` | `{ nombre: (data, { escape }) => html }` — HTML de confianza para `show_modal` / `open_sidebar`. |
|
|
103
|
+
| `navigation` | `{ allowedOrigins, router }` — `router` para SPAs (`url => router.push(url)`). |
|
|
104
|
+
| `proactive` | `{ level: 'quiet'\|'normal'\|'bold', greetDelayMs, dwellMs, idleMs, cooldownMs, maxPerSession, rules, … }` o `false` (ver «Proactividad»). |
|
|
105
|
+
| `context` | `{ privateSelectors, ignoreSelectors, extra: () => ({...}) }`. |
|
|
106
|
+
| `contact` | `{ apuchat: true, apumail: true \| { categories } }` o `false`. |
|
|
107
|
+
| `builtins` | `{ exclude: ['click', …], confirmClicks: 'submit'\|'all'\|'none' }`. |
|
|
108
|
+
| `pointer` | `{ speed, visible }` o `false` — ratón y teclado virtuales (ver abajo). |
|
|
109
|
+
| `pageTools` | `true` (defecto) — convierte formularios/botones con `data-ots-tool` en herramientas. |
|
|
110
|
+
| `mode` | `'panel'` (defecto) o `'companion'` — el avatar sale del chat y se pasea por la página. |
|
|
111
|
+
| `companion` | `{ size: 150, idleHomeMs: 30000, follow: true, wanderMs, watchCursor }` (solo en modo compañero). |
|
|
112
|
+
| `theme` | `{ primary, radius, position: 'right'\|'left', font }`. |
|
|
113
|
+
|
|
114
|
+
La referencia completa está comentada en [`src/index.js`](src/index.js).
|
|
115
|
+
|
|
116
|
+
### API en tiempo de ejecución
|
|
117
|
+
|
|
118
|
+
```js
|
|
119
|
+
agent.registerAction({...}); // añade una herramienta
|
|
120
|
+
agent.registerMcp({ url, name }); // conecta otro MCP
|
|
121
|
+
agent.setAuthToken(jwt); // al hacer login/logout (null)
|
|
122
|
+
agent.ask('¿qué plan me conviene?'); // como si lo escribiera el usuario
|
|
123
|
+
agent.notify('El carrito tiene 3 productos'); // evento: el agente decide si habla
|
|
124
|
+
agent.say('¡Bienvenido de nuevo!'); // mensaje directo, sin LLM
|
|
125
|
+
agent.setProactivity('bold'); // 'quiet' | 'normal' | 'bold'
|
|
126
|
+
agent.setIdentity({ name: 'Brisa', look: { color: '#e11d48' } }); // identidad en caliente
|
|
127
|
+
agent.open(); agent.close(); agent.reset(); agent.destroy();
|
|
128
|
+
agent.on('action:end', ({ name, result }) => analytics.track(name));
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Eventos: `ready`, `user:message`, `agent:message`, `agent:thinking`, `agent:error`,
|
|
132
|
+
`action:start`, `action:end`, `handoff:start`, `handoff:message`, `handoff:end`,
|
|
133
|
+
`context:route`, `context:section`, `context:alert`, `proactive:trigger`, `proactive:level`, `identity:change`, `auth:change`,
|
|
134
|
+
`voice:start`, `voice:end`, `widget:open`, `widget:close`.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Identidad del agente: cara, voz y contacto
|
|
139
|
+
|
|
140
|
+
Muchos agentes necesitan lo mismo: un nombre, un aspecto reconocible, una voz propia y formas de
|
|
141
|
+
localizarlo. 7ots lo resuelve con **una sola ficha declarativa**. La usan el widget, la voz del
|
|
142
|
+
proxy, los canales (correo, chat, meet) y una tarjeta pública. Puedes usar este módulo sin el chat
|
|
143
|
+
y con tu propio cerebro.
|
|
144
|
+
|
|
145
|
+
```js
|
|
146
|
+
{
|
|
147
|
+
id: 'nube', name: 'Nube', role: 'asesora de Nimbus', tagline: 'Te ayudo con tu almacenamiento',
|
|
148
|
+
bio: '…', language: 'es', languages: ['es', 'en'],
|
|
149
|
+
personality: { tone: 'cercano y claro', traits: ['paciente'], instructions: '…' }, // instructions: privado
|
|
150
|
+
look: { color: '#4f46e5', accent: '#22d3ee', emoji: '☁️', image: 'https://…/retrato.png',
|
|
151
|
+
avatar: { url: '/avatars/nube.glb', body: 'F', mood: 'happy', cameraView: 'upper' },
|
|
152
|
+
face: { skin: '#…', eyes: '#…' }, meetAvatar: 'vivi', meetScene: '' },
|
|
153
|
+
voice: { provider: 'auto'|'openai'|'elevenlabs'|'browser', voiceId: 'coral', model: '',
|
|
154
|
+
style: 'cálida y clara', lang: 'es-ES', rate: 1.05, pitch: 1, browserVoice: '' },
|
|
155
|
+
contact: { site: 'https://nimbus.example', email: 'nube@nimbus.example', apuchat: 'nube',
|
|
156
|
+
meet: true, call: true, hours: 'L-V 9-18' },
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
`defineIdentity()` normaliza la ficha y nunca lanza excepciones. Descarta los colores que no son
|
|
161
|
+
hex, las URLs que no son http(s) y los valores fuera de rango. **La ficha no contiene secretos.**
|
|
162
|
+
Las claves de TTS, apumail y apuchat siguen en el entorno del proxy.
|
|
163
|
+
|
|
164
|
+
**De dónde sale.** Cada fuente pisa a la anterior: valores por defecto ← entorno (`AGENT_NAME`,
|
|
165
|
+
`AGENT_ROLE`, `APUCHAT_AGENT_AVATAR`, `APUMAIL_AGENT_INBOX`…) ← `data/identity.json`. La variable
|
|
166
|
+
`IDENTITY_FILE` cambia la ruta del fichero, y el backoffice lo escribe. Hay un ejemplo en
|
|
167
|
+
[`examples/identity.example.json`](examples/identity.example.json).
|
|
168
|
+
|
|
169
|
+
**Tarjeta pública.** Rutas del proxy (todas con CORS `*`):
|
|
170
|
+
|
|
171
|
+
| Ruta | Qué es |
|
|
172
|
+
|---|---|
|
|
173
|
+
| `GET /api/agent/identity` · `GET /.well-known/7ots-agent.json` | Tarjeta JSON (`@type: 7ots/agent`). Lleva solo campos de una lista blanca y nunca las instrucciones. `contact.answers` dice qué canales atiende el agente ahora mismo. |
|
|
174
|
+
| `GET /api/agent/identity.vcf` | vCard 4.0, para «guardar contacto». |
|
|
175
|
+
| `POST /api/agent/identity/call` | Abre una videollamada de meet.apuchat.com con el agente ya dentro y devuelve `{ call_url }`. Necesita apuchat; tiene límite por IP y pasa por `SITE_KEYS`. |
|
|
176
|
+
|
|
177
|
+
**En el widget.** La opción `identity` acepta tres formas: `true` (pide `{endpoint}/identity`), una
|
|
178
|
+
URL o el objeto de la ficha. La identidad fija nombre, colores, avatar y voz, pero lo que pongas
|
|
179
|
+
explícitamente en `agent`, `avatar`, `voice` o `theme` manda sobre ella. Para cambiarla en caliente
|
|
180
|
+
usa `agent.setIdentity(ficha)`, que emite el evento `identity:change`.
|
|
181
|
+
|
|
182
|
+
**Solo la cara y la voz (`createFace`).** Sirve si ya tienes tu chat o tu agente:
|
|
183
|
+
|
|
184
|
+
```js
|
|
185
|
+
import { createFace } from '@7ots/cli';
|
|
186
|
+
const face = await createFace(document.querySelector('#cara'), { identity: '/api/agent/identity', endpoint: '/api/agent' });
|
|
187
|
+
await face.say('Hola, soy Nube'); // voz de la identidad con lip-sync
|
|
188
|
+
face.mood('happy'); face.gesture('thumbup');
|
|
189
|
+
face.morph('wings'); face.effect('hearts'); // solo con el personaje 2D
|
|
190
|
+
const texto = await face.listen(); // micrófono → texto
|
|
191
|
+
face.setIdentity(otraFicha); // en caliente
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**Tarjeta de contacto (`<ots-identity>`).** Muestra retrato, nombre y rol, y botones para chatear
|
|
195
|
+
(si hay widget), escribir un correo, copiar el @handle de apuchat, iniciar una videollamada,
|
|
196
|
+
escucharle y guardar la vCard:
|
|
197
|
+
|
|
198
|
+
```html
|
|
199
|
+
<ots-identity src="/api/agent/identity" face greeting="Hola, soy Nube."></ots-identity>
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Emite los eventos `ots-identity:load`, `ots-identity:call` y `ots-identity:error`. También puedes
|
|
203
|
+
asignarle `el.identity = ficha` para no pedir nada a la red.
|
|
204
|
+
|
|
205
|
+
**En tu servidor, con tu propio cerebro (`7ots/server`):**
|
|
206
|
+
|
|
207
|
+
```js
|
|
208
|
+
import { createAgentIdentity } from '7ots/server';
|
|
209
|
+
|
|
210
|
+
const nube = createAgentIdentity({
|
|
211
|
+
identity: { name: 'Nube', voice: { provider: 'openai', voiceId: 'coral' }, contact: { email: 'nube@nimbus.example' } },
|
|
212
|
+
brain: async ({ text, channel, history, identity }) => miAgente.responder(text), // sin brain: el LLM de 7ots
|
|
213
|
+
apumail: { inbox: 'nube@nimbus.example', token: process.env.NUBE_MAIL_TOKEN }, // contesta su correo
|
|
214
|
+
apuchat: { identityKey: process.env.NUBE_APUCHAT_KEY }, // mensajes y llamadas
|
|
215
|
+
});
|
|
216
|
+
await nube.start();
|
|
217
|
+
http.createServer(async (req, res) => (await nube.handler(req, res)) || miApp(req, res));
|
|
218
|
+
|
|
219
|
+
nube.card(); nube.vcard(); await nube.speak('Hola'); // tarjeta, vCard, audio con su voz
|
|
220
|
+
await nube.respond('¿Qué planes hay?', { channel: 'dm' });
|
|
221
|
+
await nube.call('@ana', 'Tu copia de seguridad ha terminado'); // hace sonar la app de apuchat
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Para probarlo, abre la demo [`/examples/identidad.html`](examples/identidad.html). Al editar la ficha
|
|
225
|
+
cambian a la vez la cara, la tarjeta, el widget y la tarjeta pública.
|
|
226
|
+
|
|
227
|
+
## Backoffice: configúralo todo sin tocar código
|
|
228
|
+
|
|
229
|
+
El proxy sirve un panel en **`/backoffice/`** (por ejemplo `http://localhost:8787/backoffice/`):
|
|
230
|
+
|
|
231
|
+
| Sección | Qué configura |
|
|
232
|
+
|---|---|
|
|
233
|
+
| Resumen | Estado del modelo, la voz y los canales; botón «Probar el modelo». |
|
|
234
|
+
| Identidad | Nombre, papel, lema, biografía, idiomas, tono, rasgos e instrucciones privadas. |
|
|
235
|
+
| Aspecto | **Editor de avatar** (ver abajo), colores, emoji, retrato, ánimo, encuadre 3D y avatar de videollamada. |
|
|
236
|
+
| Voz | Proveedor, voz, modelo, acento, velocidad, tono y estilo, con botón «Escuchar». |
|
|
237
|
+
| Contacto | Web, correo, @apuchat, horario, videollamadas y el menú «hablar con una persona». |
|
|
238
|
+
| Comportamiento | Proactividad (nivel, saludo, límites, señales), modo compañero, puntero, acciones y micrófono. |
|
|
239
|
+
| Widget y marca | Lado, redondeo, tipografía, color, y JSON avanzado de `init()`. |
|
|
240
|
+
| Privacidad y navegación | `privateSelectors`, `ignoreSelectors` y `navigation.allowedOrigins`. |
|
|
241
|
+
| Servidor y claves | Todas las variables del proxy: LLM, TTS, apumail/apuchat, límites, orígenes… |
|
|
242
|
+
| Instalar | El snippet de una línea (`embed.js`) y los de código, tarjeta y editor. |
|
|
243
|
+
|
|
244
|
+
- **Vista previa en vivo**: los cambios sin guardar se ven al momento en un widget de prueba.
|
|
245
|
+
- **Guardar** (o Ctrl+S) escribe `data/identity.json` y `data/config.json`. El snippet
|
|
246
|
+
`<script src="…/api/agent/embed.js">` carga esa configuración, así que tu web no se toca.
|
|
247
|
+
- **Claves**: se escriben en `data/secrets.json` (permisos 0600) y nunca vuelven al navegador; solo
|
|
248
|
+
se ve si están puestas. Lo guardado manda sobre `.env`, y casi todo se aplica sin reiniciar
|
|
249
|
+
(el panel avisa de lo que necesita reinicio: `PORT`, `TRUST_PROXY`, `DEMO`…).
|
|
250
|
+
- **Acceso**: sin `ADMIN_PASSWORD` solo abre desde la propia máquina (localhost). Con ella se entra
|
|
251
|
+
desde fuera con una cookie firmada (HttpOnly, SameSite=Strict). Las escrituras exigen el mismo
|
|
252
|
+
origen y la cabecera `X-7ots-Admin`. Rutas: `/api/agent/admin/*`.
|
|
253
|
+
- Lo que no son datos (acciones con `handler`, `auth`, plantillas) sigue en tu página, en
|
|
254
|
+
`window.SevenOtsConfig` o en `init()`.
|
|
255
|
+
|
|
256
|
+
## Personaje 2D y editor de avatar
|
|
257
|
+
|
|
258
|
+
El avatar por defecto es un **personaje 2D vectorial al estilo Pou**, animado (parpadea, mira el
|
|
259
|
+
cursor, mueve la boca con la voz, tiene ánimos y gestos). Se describe con un objeto JSON y se edita
|
|
260
|
+
visualmente:
|
|
261
|
+
|
|
262
|
+
- **Cuerpo**: 16 formas de partida; luego se arrastran sus 16 puntos (con simetría opcional).
|
|
263
|
+
- **Cara**: 33 ojos, 28 bocas, 9 cejas, 8 mejillas; posición y tamaño arrastrando.
|
|
264
|
+
- **Color y acabado**: color libre, 7 acabados (mate, brillo, neón…), 10 estampados, contorno.
|
|
265
|
+
- **Accesorios**: ~70 (gorros, coronas, lentes, pipas, barbas, capas, alas…), cada uno movible,
|
|
266
|
+
escalable (rueda), rotable (Mayús+rueda), con colores propios. Hasta 16 a la vez.
|
|
267
|
+
- **Estilos completos**: 26 presets (pirata, rey, reina, mago, chef, vaquero, astronauta, robot,
|
|
268
|
+
vikingo, ninja, fantasma…) que se pueden aplicar conservando tu forma y color.
|
|
269
|
+
- **Realista**: la misma pestaña permite elegir un avatar 3D `.glb` (TalkingHead) o un retrato.
|
|
270
|
+
- Deshacer/rehacer, exportar SVG/PNG y copiar JSON.
|
|
271
|
+
|
|
272
|
+
En la identidad se guarda en `look.kind` (`character` | `realistic` | `image`) y `look.character`.
|
|
273
|
+
Todo el arte es propio (MIT).
|
|
274
|
+
|
|
275
|
+
```js
|
|
276
|
+
import { createAvatarEditor, createCharacter } from '@7ots/cli';
|
|
277
|
+
|
|
278
|
+
// El editor, en tu propia app (también como <ots-avatar-editor>):
|
|
279
|
+
const ed = createAvatarEditor(el, { value: identity.look, onChange: (v) => guardar(v) });
|
|
280
|
+
|
|
281
|
+
// Solo el personaje, animado:
|
|
282
|
+
const ch = createCharacter(el, { preset: 'pirate' });
|
|
283
|
+
ch.mood('happy'); ch.gesture('jump'); ch.mouth(0.6); ch.lookAtPoint(x, y);
|
|
284
|
+
|
|
285
|
+
// Gestos (CHARACTER_GESTURES): jump, bounce, wiggle, nod, shake, spin, wave, think, celebrate,
|
|
286
|
+
// surprise, shrug, sneeze, hiccup, laugh, dance, yawn, shiver, clap, thumbsup, thumbsdown, point,
|
|
287
|
+
// bow, fly, stomp, dizzy. Alias: handup, hi, index, ok, yes, no, thumbup, side, namaste, hop, cheer.
|
|
288
|
+
ch.gesture('celebrate'); ch.gesture('point', { mirror: true, ms: 2000 });
|
|
289
|
+
|
|
290
|
+
// Transformaciones temporales del cuerpo (CHARACTER_MORPHS) y efectos (CHARACTER_EFFECTS):
|
|
291
|
+
ch.morph('wings', { ms: 3000 }); // spikes, wings, hands, horns, puff, squish, stretch, melt, jelly
|
|
292
|
+
ch.morph('hands', { pose: 'wave', hold: true }); ch.unmorph('hands');
|
|
293
|
+
ch.morph('shape:heart'); // cambia de forma un momento y vuelve
|
|
294
|
+
ch.effect('hearts'); // sparkles, sweat, blush, hearts, zzz, exclaim, question, anger…
|
|
295
|
+
|
|
296
|
+
// Lip-sync:
|
|
297
|
+
const s = ch.speak('Hola, ¿qué tal?', { durationMs: 1800 }); // visemas del texto; s.stop(), s.sync(charIndex)
|
|
298
|
+
const stop = ch.lipsync(analyserNode, { text }); // audio real (AnalyserNode) + visemas
|
|
299
|
+
ch.viseme('O'); // X A E I O U M F S C
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Demo: [`/examples/avatar.html`](examples/avatar.html) (editor más galería de estilos) y
|
|
303
|
+
[`/examples/character-lab.html`](examples/character-lab.html) (gestos, transformaciones, efectos y lip-sync).
|
|
304
|
+
|
|
305
|
+
## Idiomas (i18n)
|
|
306
|
+
|
|
307
|
+
Todo el texto que ve una persona sale de catálogos de traducción: el widget, el editor de avatar,
|
|
308
|
+
la tarjeta de contacto, el backoffice y los mensajes del proxy. Vienen **español, inglés y
|
|
309
|
+
portugués** completos.
|
|
310
|
+
|
|
311
|
+
- **Widget**: `init({ locale: 'en' })`. Sin `locale` se usa `<html lang>` y luego el idioma del
|
|
312
|
+
navegador; un idioma que no existe cae a inglés. En caliente: `agent.setLocale('pt')`.
|
|
313
|
+
El agente responde en el idioma en que le escribe el visitante.
|
|
314
|
+
- **Componentes**: `createAvatarEditor(el, { locale })`, `<ots-avatar-editor lang="en">`,
|
|
315
|
+
`<ots-identity lang="pt">`, `createFace(el, { locale })`.
|
|
316
|
+
- **Proxy**: responde en el idioma de la cabecera `Accept-Language` (el widget la envía). Los avisos
|
|
317
|
+
al equipo (correo, apuchat) usan `LOCALE` o, si no está, el idioma de la identidad.
|
|
318
|
+
- **Backoffice**: selector de idioma en la barra lateral.
|
|
319
|
+
|
|
320
|
+
**Añadir un idioma o cambiar textos:**
|
|
321
|
+
|
|
322
|
+
```js
|
|
323
|
+
import { addMessages } from '@7ots/cli'; // o SevenOts.i18n.addMessages con <script>
|
|
324
|
+
addMessages('fr', { widget: { /* … */ } }); // claves que falten → inglés
|
|
325
|
+
addMessages('es', { widget: { /* … */ } }); // sobrescribe solo esas claves
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Los catálogos de serie están en `src/i18n/messages/` y son buena plantilla para traducir. Formato:
|
|
329
|
+
`{nombre}` para variables y `{ one: '# mensaje', other: '# mensajes' }` para plurales
|
|
330
|
+
(`Intl.PluralRules`).
|
|
331
|
+
|
|
332
|
+
## Acciones propias
|
|
333
|
+
|
|
334
|
+
Cada acción es una herramienta que el LLM puede llamar. El handler corre **en el navegador**,
|
|
335
|
+
con la sesión del usuario:
|
|
336
|
+
|
|
337
|
+
```js
|
|
338
|
+
agent.registerAction({
|
|
339
|
+
name: 'agregar_al_carrito', // [a-zA-Z0-9_-], único
|
|
340
|
+
description: 'Añade un producto al carrito del usuario.',
|
|
341
|
+
parameters: {
|
|
342
|
+
type: 'object',
|
|
343
|
+
properties: { sku: { type: 'string' }, cantidad: { type: 'integer', minimum: 1 } },
|
|
344
|
+
required: ['sku'],
|
|
345
|
+
},
|
|
346
|
+
requiresAuth: true, // solo se ofrece con sesión
|
|
347
|
+
confirm: ({ sku }) => `¿Añadir **${sku}** al carrito?`, // diálogo real antes de ejecutar
|
|
348
|
+
handler: async ({ sku, cantidad = 1 }, ctx) => {
|
|
349
|
+
const r = await ctx.auth.fetch('/api/cart', { // adjunta el JWT; el LLM nunca lo ve
|
|
350
|
+
method: 'POST', body: JSON.stringify({ sku, cantidad }),
|
|
351
|
+
});
|
|
352
|
+
if (!r.ok) return { ok: false, error: 'No se pudo añadir' };
|
|
353
|
+
ctx.ui.toast({ message: 'Añadido ✓', type: 'success' });
|
|
354
|
+
return r.json(); // vuelve al LLM como resultado
|
|
355
|
+
},
|
|
356
|
+
});
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
`ctx` = `{ auth, ui, context, bus, agent, signal, pointer }`. Los errores lanzados vuelven al LLM como
|
|
360
|
+
resultado de error; nunca rompen la conversación.
|
|
361
|
+
|
|
362
|
+
**Acciones incluidas:** `get_page_context`, `find_on_page`, `read_element`, `navigate`,
|
|
363
|
+
`scroll_to`, `click`, `hover`, `type_text`, `press_key`, `drag_and_drop`, `scroll`, `fill_form`,
|
|
364
|
+
`highlight`, `show_toast`, `show_modal`, `open_sidebar`, `close_panels`, más
|
|
365
|
+
`send_email_ticket` (apumail) y `escalate_to_human` (apuchat).
|
|
366
|
+
|
|
367
|
+
El agente prefiere siempre las **herramientas internas** (tus acciones, tu MCP, tus
|
|
368
|
+
`data-ots-tool`): son exactas. El ratón y el teclado quedan para lo que no tiene herramienta o
|
|
369
|
+
cuando el usuario pide *«enséñame cómo se hace»*.
|
|
370
|
+
|
|
371
|
+
## Ratón y teclado
|
|
372
|
+
|
|
373
|
+
Con `pointer` activo (por defecto) el agente mueve un **cursor visible con su nombre** y opera la
|
|
374
|
+
página como una persona: `click` (doble, derecho), `hover`, `type_text` (tecla a tecla, compatible
|
|
375
|
+
con React/Vue), `press_key` (Enter, Tab, flechas, Escape, `Control+a`…), `drag_and_drop` (HTML5 y
|
|
376
|
+
ratón) y `scroll`.
|
|
377
|
+
|
|
378
|
+
- **Esc** (del usuario) detiene al instante lo que esté haciendo y corta el turno.
|
|
379
|
+
- Comprueba qué hay realmente bajo el punto: si otro elemento tapa el objetivo, falla y lo dice.
|
|
380
|
+
- Con `prefers-reduced-motion` o la pestaña oculta, actúa sin animación.
|
|
381
|
+
- Nunca escribe en contraseñas, campos de tarjeta, códigos OTP ni `data-ots-private`.
|
|
382
|
+
- Enter o click que **envía un formulario** pide confirmación. El sitio decide con
|
|
383
|
+
`data-ots-confirm` en el botón o en el `<form>`: `"false"` = sin preguntar, o el texto de la pregunta.
|
|
384
|
+
- Funciona también dentro de `<dialog>` modales del sitio (cursor y confirmaciones van en la capa superior).
|
|
385
|
+
|
|
386
|
+
Límite: son eventos sintéticos (`isTrusted: false`). Cubren formularios, menús, pestañas,
|
|
387
|
+
listas y drag & drop normales, pero el navegador no deja simular acciones protegidas: abrir el
|
|
388
|
+
selector de archivos, pantalla completa, copiar al portapapeles o pasar un CAPTCHA.
|
|
389
|
+
|
|
390
|
+
## Herramientas declaradas en el HTML (`data-ots-tool`)
|
|
391
|
+
|
|
392
|
+
Un formulario o botón que ya existe se convierte en herramienta con un atributo, sin JS:
|
|
393
|
+
|
|
394
|
+
```html
|
|
395
|
+
<form data-ots-tool="invitar_miembro"
|
|
396
|
+
data-ots-description="Invita a una persona al equipo"
|
|
397
|
+
data-ots-confirm="¿Invitar a {email} como {rol}?"
|
|
398
|
+
data-ots-auth>
|
|
399
|
+
<input name="email" type="email" required>
|
|
400
|
+
<select name="rol"><option>lector</option><option>editor</option></select>
|
|
401
|
+
<button>Invitar</button>
|
|
402
|
+
</form>
|
|
403
|
+
<button data-ots-tool="exportar_csv">Exportar</button>
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
Los campos con `name` son los parámetros (select/radio → enum, checkbox → boolean,
|
|
407
|
+
number/range → número; `required` se respeta). El agente rellena, valida y envía. Tu código
|
|
408
|
+
puede devolverle el resultado con
|
|
409
|
+
`form.dispatchEvent(new CustomEvent('ots-result', { detail: {...} }))`; si no, recibe el texto
|
|
410
|
+
de `[role=status]`/`[role=alert]`. La lista se actualiza sola al cambiar la página.
|
|
411
|
+
|
|
412
|
+
Demo completa: [`examples/dashboard.html`](examples/dashboard.html) — panel con ajustes,
|
|
413
|
+
carpetas, drag & drop e invitaciones, manejable por herramientas internas o con el ratón.
|
|
414
|
+
|
|
415
|
+
## Modo compañero (`mode: 'companion'`)
|
|
416
|
+
|
|
417
|
+
Un modo más lúdico: en vez de quedarse en el chat, el avatar flota sobre la página, camina
|
|
418
|
+
hasta los elementos, los **señala** (gesto + flecha + resaltado) y habla en un bocadillo.
|
|
419
|
+
|
|
420
|
+
```js
|
|
421
|
+
init({ mode: 'companion', companion: { size: 150 }, avatar: { cameraView: 'head', /* … */ } });
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
- Pulsarlo abre o cierra el chat; arrastrarlo lo deja donde quieras.
|
|
425
|
+
- El modelo tiene tres acciones más: `point_at {target, message}`, `tour {steps:[{target, message}]}`
|
|
426
|
+
(visita guiada, máx. 8 pasos) y `go_home`.
|
|
427
|
+
- Con `follow: true` se acerca a lo que toca el ratón virtual (click, type_text, drag…), así se
|
|
428
|
+
ve quién está actuando.
|
|
429
|
+
- Con el chat cerrado, las respuestas salen en su bocadillo (y en voz alta si la voz está activa).
|
|
430
|
+
- Vuelve a su rincón tras `idleHomeMs` sin actividad. Respeta `prefers-reduced-motion`.
|
|
431
|
+
- Con `wanderMs` se da paseos solo: se acerca con curiosidad a un título, imagen o botón visible y
|
|
432
|
+
vuelve. Con `watchCursor` sigue tu ratón con la mirada. Ambos los fija el nivel de proactividad.
|
|
433
|
+
|
|
434
|
+
Pruébalo en las demos con `?modo=companion` (`examples/?modo=companion`,
|
|
435
|
+
`examples/dashboard.html?modo=companion`) o con el enlace «Modo compañero» del menú.
|
|
436
|
+
|
|
437
|
+
## Proactividad: cuánto se atreve el agente
|
|
438
|
+
|
|
439
|
+
El agente no espera a que le escriban: observa la página y, cuando tiene sentido, **se mueve,
|
|
440
|
+
señala, resalta y despliega tarjetas de información** junto a lo que estás mirando. El nivel de
|
|
441
|
+
iniciativa lo fija el sitio (`proactive.level`) y el visitante puede cambiarlo en el menú
|
|
442
|
+
«⋯ → Iniciativa» (Discreta · Normal · Atrevida); su elección se recuerda en la sesión.
|
|
443
|
+
|
|
444
|
+
| Señal | `quiet` | `normal` (defecto) | `bold` |
|
|
445
|
+
|---|---|---|---|
|
|
446
|
+
| Saludo inicial | sí | sí | sí, con tarjeta de bienvenida |
|
|
447
|
+
| Cambio de ruta (`onRoute`) | — | sí | sí |
|
|
448
|
+
| Entrar en una sección (`sectionEnterMs`) | — | — | a los 1,5 s |
|
|
449
|
+
| Permanencia en una sección (`dwellMs`) | — | 25 s | 12 s |
|
|
450
|
+
| Inactividad (`idleMs`) | 120 s, solo formularios | 45 s, solo formularios | 20 s, en cualquier parte |
|
|
451
|
+
| Duda sobre un botón o enlace (`hesitationMs`) | — | 4,5 s | 2,5 s |
|
|
452
|
+
| Clics de frustración (`rageClicks`) | sí | sí | sí |
|
|
453
|
+
| Texto seleccionado (`selection`) | — | — | sí |
|
|
454
|
+
| Intención de salir (`exitIntent`) | — | sí | sí |
|
|
455
|
+
| Enfriamiento / máx. por sesión | 120 s / 3 | 40 s / 8 | 10 s / 40 |
|
|
456
|
+
| Paseo del compañero (`wanderMs`) / mira el cursor | — / no | 45 s / sí | 18 s / sí |
|
|
457
|
+
|
|
458
|
+
Cualquier valor del nivel se puede sobrescribir en `proactive` (p. ej. `{ level: 'bold', exitIntent: false }`).
|
|
459
|
+
Cada evento le dice al modelo el nivel de iniciativa: en `bold` prefiere **mostrar** (`point_at`,
|
|
460
|
+
`show_card`, `tour`) a preguntar «¿te ayudo?». Siempre puede contestar `NOOP` y no pasa nada.
|
|
461
|
+
|
|
462
|
+
**Tarjetas (`show_card`).** Acción integrada: `{ target?, title, content, facts: [{label, value}],
|
|
463
|
+
questions, tone, seconds }`. Se ancla al lado del elemento (o del compañero), lo trae a la vista si
|
|
464
|
+
hace falta, y las preguntas son chips que el visitante pulsa para seguir la conversación.
|
|
465
|
+
|
|
466
|
+
Pruébalo: `examples/?modo=companion`, menú «⋯ → Iniciativa → Atrevida», y baja hasta «Precios».
|
|
467
|
+
|
|
468
|
+
## MCP autenticado
|
|
469
|
+
|
|
470
|
+
Si tu sitio expone un servidor MCP (transporte Streamable HTTP), sus herramientas aparecen solas
|
|
471
|
+
con prefijo: `mcp: [{ url: '/mcp', name: 'acme' }]` → `acme__listar_pedidos`, `acme__cambiar_plan`…
|
|
472
|
+
|
|
473
|
+
- El JWT del usuario viaja como `Authorization: Bearer` (o cookies con `credentials: 'include'`).
|
|
474
|
+
- Las herramientas sin `readOnlyHint: true` piden confirmación al usuario antes de ejecutarse.
|
|
475
|
+
- Al cambiar la sesión (`setAuthToken`) se reconecta y se vuelve a listar.
|
|
476
|
+
|
|
477
|
+
## Autenticación
|
|
478
|
+
|
|
479
|
+
El widget se engancha a la sesión que **ya tiene** tu sitio; no gestiona logins.
|
|
480
|
+
|
|
481
|
+
- El token se usa en `ctx.auth.fetch()` y en el MCP. **Nunca se envía al LLM.**
|
|
482
|
+
- Al LLM solo llegan los claims de `exposeClaims` (p. ej. nombre y plan) o lo que devuelva `getUser()`.
|
|
483
|
+
- Llama a `agent.setAuthToken(token)` al hacer login y `setAuthToken(null)` al salir.
|
|
484
|
+
|
|
485
|
+
## Contacto: apuchat y apumail
|
|
486
|
+
|
|
487
|
+
**apuchat.com — hablar con una persona.** Cuando el usuario lo pide (o el agente no puede
|
|
488
|
+
resolver), `escalate_to_human` crea un canal efímero en el hub de apuchat y avisa al operador por DM
|
|
489
|
+
con un resumen y el enlace. El chat del widget pasa a ser un chat en vivo con el operador, con
|
|
490
|
+
opción de videollamada (el visitante entra por `call_url_public` y "llama a la puerta").
|
|
491
|
+
|
|
492
|
+
El proxy hace de **relé** entre el widget y el canal (`/contact/apuchat/{send,wait,end}`): el hub no
|
|
493
|
+
acepta CORS de otros sitios y el canal exige una `identity_key` que no debe salir del servidor. El
|
|
494
|
+
navegador solo recibe un id y un token opacos; el enlace de operador (con claves y PIN) **nunca** le llega.
|
|
495
|
+
|
|
496
|
+
```bash
|
|
497
|
+
APUCHAT_HUB=https://apuchat.com
|
|
498
|
+
APUCHAT_NOTIFIER_IDENTITY_KEY=... # identidad que envía el aviso por DM
|
|
499
|
+
APUCHAT_OPERATOR_HANDLE=soporte # quién lo recibe (sin @; con @ el DM se pierde en silencio)
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
> - El aviso por DM se limita a 4096 caracteres (se recorta el resumen, nunca el enlace).
|
|
503
|
+
> - El hub permite 15 videollamadas nuevas por minuto **por IP**: todos tus visitantes comparten
|
|
504
|
+
> la IP del proxy.
|
|
505
|
+
> - El estado del relé está en memoria: con varias instancias del proxy usa sesiones *sticky* (o
|
|
506
|
+
> cambia el `Map` por Redis). Reiniciar el proxy cierra las conversaciones abiertas.
|
|
507
|
+
|
|
508
|
+
**apumail.com — correo / ticket.** `send_email_ticket` redacta un ticket con asunto, categoría y
|
|
509
|
+
(opcionalmente) la transcripción, lo enseña al usuario para confirmar y lo envía por el proxy.
|
|
510
|
+
|
|
511
|
+
El correo sale de **un buzón tuyo de apumail** hacia el buzón del equipo; responder a ese correo
|
|
512
|
+
llega directamente al visitante (`reply_to`). El destinatario es fijo en el servidor: el LLM no lo elige.
|
|
513
|
+
|
|
514
|
+
```bash
|
|
515
|
+
APUMAIL_INBOX=soporte@apumail.com # buzón remitente (permanente o de dominio propio)
|
|
516
|
+
APUMAIL_INBOX_TOKEN=... # token del buzón o PAT acct_… de la cuenta dueña
|
|
517
|
+
APUMAIL_TO=equipo@tuempresa.com # quién recibe los tickets
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
> Los buzones gratis de apumail caducan a las 24 h sin actividad y envían como mucho 5 destinatarios
|
|
521
|
+
> al día; para producción usa un buzón permanente o de dominio propio (100 envíos/h).
|
|
522
|
+
|
|
523
|
+
## Hablar con el agente por apumail, apuchat y meet
|
|
524
|
+
|
|
525
|
+
Además del widget, el mismo agente puede atender **fuera de la web**. Cada canal se activa solo si
|
|
526
|
+
están sus variables; sin ellas el proxy arranca igual. El agente usa el mismo LLM y
|
|
527
|
+
`SERVER_INSTRUCTIONS`, recuerda los últimos mensajes de cada contacto (24 h, en memoria) y, como
|
|
528
|
+
aquí no ve la página, manda a la gente a la web para cualquier cosa de su cuenta.
|
|
529
|
+
|
|
530
|
+
| Cómo le hablas | Qué pasa |
|
|
531
|
+
| --- | --- |
|
|
532
|
+
| Correo a su buzón de apumail | Contesta en el mismo hilo (`Re:`), sin citas ni firmas automáticas. |
|
|
533
|
+
| Mensaje a su @handle en la app de apuchat | Contesta por mensaje, corto y cercano. |
|
|
534
|
+
| "Llámame" por mensaje | Crea una videollamada de meet con su avatar, entra y te manda el enlace: **suena la app de apuchat** del móvil. |
|
|
535
|
+
| En meet.apuchat.com, 📞 *Ring* a su @handle | Le llega la invitación (Channel id / Token / PIN), entra en tu llamada, pone su avatar y te responde por voz. |
|
|
536
|
+
|
|
537
|
+
```bash
|
|
538
|
+
# Persona (común a los canales)
|
|
539
|
+
AGENT_NAME=Nube
|
|
540
|
+
AGENT_ROLE="asistente de Nimbus"
|
|
541
|
+
AGENT_SITE_URL=https://nimbus.ejemplo.com
|
|
542
|
+
AGENT_LANG=es
|
|
543
|
+
|
|
544
|
+
# apumail: buzón propio del agente
|
|
545
|
+
APUMAIL_AGENT_INBOX=nube@tudominio.com
|
|
546
|
+
APUMAIL_AGENT_TOKEN=... # token del buzón o PAT de la cuenta dueña
|
|
547
|
+
APUMAIL_AGENT_WEBHOOK_SECRET=... # opcional: webhook en vez de long-poll
|
|
548
|
+
APUMAIL_AGENT_DAILY=20 # respuestas al día como máximo
|
|
549
|
+
|
|
550
|
+
# apuchat / meet: identidad propia del agente
|
|
551
|
+
APUCHAT_AGENT_IDENTITY_KEY=... # su X-Identity-Key (handle permanente)
|
|
552
|
+
APUCHAT_AGENT_AVATAR=vivi # avatar en meet
|
|
553
|
+
APUCHAT_AGENT_SCENE=office # opcional: fondo de la llamada
|
|
554
|
+
APUCHAT_AGENT_ALLOW= # opcional: @handles permitidos (coma)
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
- **Webhook de apumail:** si defines `APUMAIL_AGENT_WEBHOOK_SECRET`, registra en apumail la URL
|
|
558
|
+
pública `https://tu-proxy/api/agent/channels/apumail`. Se verifica `X-Apumail-Signature` (HMAC
|
|
559
|
+
SHA-256 del cuerpo) y se responde al momento. Sin secreto, el proxy hace long-poll a `/wait` y no
|
|
560
|
+
necesita URL pública.
|
|
561
|
+
- **Avatares de meet:** shibu, shino, fumiriya, victoria, vita, vivi, kuro, maya, ingrid, kenji,
|
|
562
|
+
viktor, marcus, leo, mei, claire, doctor, amira. **Escenas:** studio, beach, castle, space, sunset,
|
|
563
|
+
forest, night, office, neon.
|
|
564
|
+
- **Voz:** en meet tú hablas y meet lo transcribe; el agente contesta en texto corto y tu navegador
|
|
565
|
+
lo lee con el avatar. Si eliges idioma en meet (`[lang]`), responde en ese idioma.
|
|
566
|
+
- **"Teléfono"** es la llamada de la app de apuchat (push VoIP), no telefonía clásica. El agente solo
|
|
567
|
+
llama a quien le está escribiendo.
|
|
568
|
+
- **Límites:** 0,5 mensajes/s (cola en el proxy), 30 mensajes por persona y hora, 5 correos por
|
|
569
|
+
remitente y día. Llamadas: `AGENT_MAX_CALLS=3` simultáneas, `AGENT_CALL_MAX_MINUTES=15`,
|
|
570
|
+
`AGENT_CALL_MAX_TURNS=40`. Cuelga si nadie entra en 2 min o tras 3 min de silencio.
|
|
571
|
+
- **No contesta** correos automáticos, rebotes ni listas, ni correos o mensajes antiguos al
|
|
572
|
+
arrancar. Tampoco entra en invitaciones de hace más de 3 min.
|
|
573
|
+
- Usa un buzón y un handle **permanentes**: los gratis caducan.
|
|
574
|
+
- Estado en `GET /api/agent/health` → `channels`.
|
|
575
|
+
|
|
576
|
+
## Avatar y voz
|
|
577
|
+
|
|
578
|
+
- Avatar 3D con [TalkingHead](https://github.com/met4citizen/TalkingHead) (Three.js): modelos `.glb`
|
|
579
|
+
con blendshapes ARKit + visemas Oculus (p. ej. exportados de Avaturn o Ready Player Me).
|
|
580
|
+
- Lip-sync: TalkingHead no trae módulo de español; se usa el finés (`fi`), fonéticamente cercano.
|
|
581
|
+
- Expresiones: con `agent.expressive: true` el modelo puede añadir `[[happy]]`, `[[thumbup]]`…
|
|
582
|
+
(lista cerrada), que el widget quita del texto y aplica al avatar.
|
|
583
|
+
- TTS: `TTS_PROVIDER=openai|elevenlabs` en el proxy; si no hay, usa la voz del navegador.
|
|
584
|
+
- STT: Web Speech API (botón de micrófono) si el navegador la soporta.
|
|
585
|
+
- Por defecto, y sin WebGL o si el modelo 3D falla, se usa el personaje 2D (boca sincronizada con el audio).
|
|
586
|
+
|
|
587
|
+
## Proveedores de LLM
|
|
588
|
+
|
|
589
|
+
| `LLM_PROVIDER` | Notas |
|
|
590
|
+
|---|---|
|
|
591
|
+
| `anthropic` | Claude vía `@anthropic-ai/sdk`. `LLM_EFFORT` (`low` por defecto: chat en vivo) controla profundidad vs. latencia. Conserva los bloques de razonamiento entre pasos de un mismo turno. Activa los *fallbacks* del lado del servidor (si una petición es rechazada se reintenta en otro modelo); desactívalos con `LLM_FALLBACKS=off` en Bedrock/Vertex/Foundry. |
|
|
592
|
+
| `openai` | Chat Completions. `LLM_BASE_URL` para APIs compatibles (DeepSeek, Groq, Ollama, OpenRouter…). |
|
|
593
|
+
| `mock` | Sin claves. Determinista, para desarrollar UI y acciones. |
|
|
594
|
+
|
|
595
|
+
## Modelo de seguridad
|
|
596
|
+
|
|
597
|
+
1. **Claves solo en el servidor.** El navegador recibe `siteKey` (público) y nada más.
|
|
598
|
+
2. **El proxy es abusable si no lo cierras.** El `system` lo envía el widget, así que en producción
|
|
599
|
+
configura `ALLOWED_ORIGINS`, `SITE_KEYS` y `RATE_LIMIT_PER_MIN`, y si quieres fija instrucciones
|
|
600
|
+
con `SERVER_INSTRUCTIONS` (se anteponen siempre).
|
|
601
|
+
3. **La página es dato, no orden.** El contenido del DOM entra marcado como no confiable; el prompt
|
|
602
|
+
indica ignorar instrucciones que aparezcan en él.
|
|
603
|
+
4. **Lo privado no sale.** No se leen `input[type=password]`, campos de tarjeta, ni nada bajo
|
|
604
|
+
`data-ots-private` o `privateSelectors`.
|
|
605
|
+
5. **Confirmación humana** para envíos de formularios, herramientas MCP con efectos, correo,
|
|
606
|
+
derivación a humano y cualquier acción con `confirm`.
|
|
607
|
+
6. **Navegación acotada** a tu origen y a `navigation.allowedOrigins`.
|
|
608
|
+
7. **HTML de confianza.** Los modales solo muestran markdown saneado o plantillas que tú defines.
|
|
609
|
+
|
|
610
|
+
## Estructura
|
|
611
|
+
|
|
612
|
+
```
|
|
613
|
+
src/
|
|
614
|
+
index.js init(), exports, window.SevenOts
|
|
615
|
+
core/AgentWidget.js <ots-agent>: Shadow DOM, UI, orquesta todos los módulos
|
|
616
|
+
core/AgentBrain.js loop de tool calling, historial, prompt de sistema
|
|
617
|
+
core/Proactivity.js niveles quiet/normal/bold y señales (ruta, sección, duda, salida…) → notify()
|
|
618
|
+
core/EventBus.js · core/storage.js
|
|
619
|
+
context/ContextManager.js snapshot semántico del DOM + MutationObserver + rutas SPA
|
|
620
|
+
actions/ActionRegistry.js registro, validación, confirmación, MCP
|
|
621
|
+
actions/builtins.js navegar, click, teclado, arrastrar, formularios, modales…
|
|
622
|
+
actions/PageTools.js [data-ots-tool] del HTML → herramientas
|
|
623
|
+
ui/VirtualPointer.js cursor, ratón y teclado virtuales (eventos sintéticos)
|
|
624
|
+
ui/Companion.js modo compañero: el avatar se pasea y señala (point_at, tour)
|
|
625
|
+
mcp/McpClient.js cliente MCP Streamable HTTP
|
|
626
|
+
auth/AuthManager.js JWT / cookies, claims expuestos
|
|
627
|
+
ui/UIManager.js capa de UI sobre la página (toasts, modales, sidebar, foco)
|
|
628
|
+
avatar/AvatarStage.js TalkingHead 3D + fallback 2D
|
|
629
|
+
voice/VoiceEngine.js TTS (proxy/navegador) + STT
|
|
630
|
+
integrations/apumail.js · integrations/apuchat.js
|
|
631
|
+
identity/schema.js ficha de identidad: defineIdentity, publicIdentity, vCard (navegador y Node)
|
|
632
|
+
identity/Face.js createFace: cara + voz sin chat
|
|
633
|
+
identity/ContactCard.js <ots-identity>: tarjeta de contacto
|
|
634
|
+
i18n/index.js traducciones: t, translator, addMessages, resolveLocale (navegador y Node)
|
|
635
|
+
i18n/messages/ catálogos es/en/pt por módulo (widget, character, identity, server)
|
|
636
|
+
character/Character.js personaje 2D estilo Pou: render, animación, ánimos y gestos
|
|
637
|
+
character/parts.js catálogo: formas, ojos, bocas, cejas, accesorios, estilos
|
|
638
|
+
character/AvatarEditor.js <ots-avatar-editor>: editor visual del personaje (y 3D/retrato)
|
|
639
|
+
backoffice/ panel /backoffice/: index.html, app.js, app.css, preview.html
|
|
640
|
+
server/ proxy Node (sin frameworks): llm, tts, contact, demo
|
|
641
|
+
identity.mjs identidad vigente: entorno + data/identity.json
|
|
642
|
+
settings.mjs data/config.json (widget + servidor) y data/secrets.json sobre el entorno
|
|
643
|
+
admin.mjs API del backoffice (/api/agent/admin/*): sesión, estado, guardar, pruebas
|
|
644
|
+
sdk.mjs 7ots/server: createAgentIdentity (identidad y canales para cualquier agente)
|
|
645
|
+
channels/ el agente por apumail, apuchat y meet (fuera de la web)
|
|
646
|
+
examples/ sitio de demo "Nimbus" (index, docs, dashboard) e identidad.html
|
|
647
|
+
site/ la web de 7ots.com (landing es/en/pt con demo, editor y agente en vivo)
|
|
648
|
+
brand/ logo, favicon, tokens.css y la mascota Ots (ver docs/BRAND.md)
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
Detalles de diseño en [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md).
|
|
652
|
+
|
|
653
|
+
## Licencia
|
|
654
|
+
|
|
655
|
+
MIT
|
|
Binary file
|