@johpaz/hive-sdk 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/CHANGELOG.md +306 -0
  2. package/README.md +11 -3
  3. package/package.json +10 -4
  4. package/packages/core/src/agent/agent-catalog.ts +81 -24
  5. package/packages/core/src/agent/agent-loop.ts +2 -2
  6. package/packages/core/src/agent/compaction.ts +20 -1
  7. package/packages/core/src/agent/context-compiler.ts +7 -4
  8. package/packages/core/src/agent/conversation-store.ts +136 -2
  9. package/packages/core/src/agent/curator.ts +12 -3
  10. package/packages/core/src/agent/llm-providers/nvidia.ts +39 -0
  11. package/packages/core/src/agent/llm-providers/openai-compat-base.ts +38 -2
  12. package/packages/core/src/agent/playbook-selector.ts +18 -3
  13. package/packages/core/src/agent/prompt-builder.ts +2 -2
  14. package/packages/core/src/agent/providers/index.ts +37 -2
  15. package/packages/core/src/agent/reflector.ts +32 -9
  16. package/packages/core/src/agent/skill-selector.ts +2 -2
  17. package/packages/core/src/agent/thread-store.ts +43 -0
  18. package/packages/core/src/agent/tool-selector.ts +2 -0
  19. package/packages/core/src/api/createAgent.ts +68 -2
  20. package/packages/core/src/artifacts/index.ts +15 -0
  21. package/packages/core/src/artifacts/store.ts +77 -2
  22. package/packages/core/src/canvas/index.ts +9 -0
  23. package/packages/core/src/ethics/EthicsGuard.ts +7 -1
  24. package/packages/core/src/events/index.ts +18 -0
  25. package/packages/core/src/events/tool-narration.ts +4 -0
  26. package/packages/core/src/gateway/channel-notify.ts +103 -6
  27. package/packages/core/src/gateway/durable-queue.ts +13 -1
  28. package/packages/core/src/gateway/index.ts +3 -0
  29. package/packages/core/src/gateway/job-store.ts +6 -0
  30. package/packages/core/src/harness/executors.ts +493 -0
  31. package/packages/core/src/harness/index.ts +12 -2
  32. package/packages/core/src/hooks/index.ts +203 -0
  33. package/packages/core/src/images/index.ts +161 -0
  34. package/packages/core/src/index.ts +1 -0
  35. package/packages/core/src/multimodal/vision-service.ts +45 -13
  36. package/packages/core/src/resilience/index.ts +13 -0
  37. package/packages/core/src/scheduler/CronScheduler.ts +48 -21
  38. package/packages/core/src/scheduler/cron/expression.ts +165 -0
  39. package/packages/core/src/scheduler/cron/index.ts +10 -0
  40. package/packages/core/src/scheduler/cron/job.ts +339 -0
  41. package/packages/core/src/scheduler/cron/next-run.ts +121 -0
  42. package/packages/core/src/scheduler/cron/zoned-time.ts +138 -0
  43. package/packages/core/src/scheduler/index.ts +21 -3
  44. package/packages/core/src/scheduler/integration.ts +16 -5
  45. package/packages/core/src/scheduler/types.ts +3 -18
  46. package/packages/core/src/services/agents.ts +268 -0
  47. package/packages/core/src/services/cron.ts +257 -0
  48. package/packages/core/src/services/endpoints.ts +289 -0
  49. package/packages/core/src/services/ethics.ts +107 -0
  50. package/packages/core/src/services/images.ts +212 -0
  51. package/packages/core/src/services/index.ts +112 -0
  52. package/packages/core/src/services/mcp.ts +201 -0
  53. package/packages/core/src/services/memory.ts +133 -0
  54. package/packages/core/src/services/models.ts +179 -0
  55. package/packages/core/src/services/providers.ts +152 -0
  56. package/packages/core/src/services/setup.ts +222 -0
  57. package/packages/core/src/services/skills.ts +241 -0
  58. package/packages/core/src/services/swarms.ts +307 -0
  59. package/packages/core/src/services/tools.ts +106 -0
  60. package/packages/core/src/sessions/index.ts +5 -3
  61. package/packages/core/src/sessions/resolve.ts +108 -0
  62. package/packages/core/src/skills/SkillLoader.ts +8 -1
  63. package/packages/core/src/skills/bundled/artifacts/artifact_reader/SKILL.md +105 -0
  64. package/packages/core/src/skills/bundled/cron_manager/SKILL.md +21 -11
  65. package/packages/core/src/skills/bundled/images/image_editor/SKILL.md +120 -0
  66. package/packages/core/src/skills/bundled/web/browser_automate/SKILL.md +12 -3
  67. package/packages/core/src/skills/bundled/web/browser_scrape/SKILL.md +22 -7
  68. package/packages/core/src/skills/bundled-data.generated.ts +110 -12
  69. package/packages/core/src/storage/bootstrap.ts +74 -5
  70. package/packages/core/src/storage/collections.ts +106 -1
  71. package/packages/core/src/storage/crypto.ts +24 -7
  72. package/packages/core/src/storage/hive.ts +9 -3
  73. package/packages/core/src/storage/index.ts +2 -1
  74. package/packages/core/src/storage/onboarding.ts +59 -43
  75. package/packages/core/src/storage/reconcile.ts +6 -1
  76. package/packages/core/src/storage/seed.ts +98 -14
  77. package/packages/core/src/swarm/types.ts +3 -18
  78. package/packages/core/src/tool-runtime/embedded-worker.generated.ts +21 -0
  79. package/packages/core/src/tool-runtime/index.ts +129 -14
  80. package/packages/core/src/tools/agents/index.ts +18 -60
  81. package/packages/core/src/tools/cli/index.ts +55 -0
  82. package/packages/core/src/tools/core/index.ts +50 -2
  83. package/packages/core/src/tools/cron/index.ts +4 -4
  84. package/packages/core/src/tools/images/index.ts +130 -0
  85. package/packages/core/src/tools/index.ts +14 -1
  86. package/packages/core/src/tools/office/office-escribir-xlsx.ts +2 -1
  87. package/packages/core/src/tools/office/office-leer-xlsx.ts +2 -1
  88. package/packages/core/src/tools/office/xlsx-loader.ts +19 -0
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Resolver quién habla y en qué hilo, cuando el mensaje llega por un canal.
3
+ *
4
+ * Un mensaje de Telegram trae un id de Telegram, no un usuario de la colmena.
5
+ * Esto traduce: busca la identidad en `userIdentities` y devuelve el usuario, el
6
+ * hilo y el agente que deben atenderlo — creando el hilo si hace falta.
7
+ *
8
+ * Ojo con el comportamiento de auto-vinculación: si la identidad no existe, se
9
+ * asocia al **único usuario existente**. Es coherente con hive, que es
10
+ * mono-usuario, pero en un despliegue con varios significa que el primer
11
+ * desconocido que escriba por un canal quedaría vinculado a quien estuviera.
12
+ * Para eso está `security/pairing.ts`, que exige aprobación antes de crear la
13
+ * identidad: un host multi-usuario debe usarlo delante de esto.
14
+ */
15
+
16
+ import { col } from "../storage/hive.ts"
17
+ import type { UserIdentityDoc, UserDoc, AgentDoc } from "../storage/collections.ts"
18
+ import { ensureThread, mostRecentWebThread, createWebConversation } from "../agent/thread-store.ts"
19
+
20
+ export interface ResolveContextResult {
21
+ userId: string
22
+ threadId: string
23
+ agentId: string
24
+ isNewUser: boolean
25
+ }
26
+
27
+ export interface ResolveContextOptions {
28
+ channel: string
29
+ channelUserId: string
30
+ /** Channel account the message arrived on — persisted so replies can be routed back to it. */
31
+ accountId?: string
32
+ /**
33
+ * Conversación concreta dentro del canal: el contacto o grupo en mensajería, el id
34
+ * de conversación en la web. Si falta, en los canales se usa `channelUserId` y en
35
+ * la web la conversación más reciente (o una nueva, si no hay ninguna).
36
+ */
37
+ peerId?: string
38
+ peerKind?: "direct" | "group"
39
+ }
40
+
41
+ export async function resolveContext(options: ResolveContextOptions): Promise<ResolveContextResult> {
42
+ const { channel, channelUserId, accountId } = options
43
+
44
+ const identitiesCol = await col<UserIdentityDoc>("userIdentities")
45
+ const usersCol = await col<UserDoc>("users")
46
+ const agentsCol = await col<AgentDoc>("agents")
47
+
48
+ const allIdentities = await identitiesCol.scan({})
49
+ const identity = allIdentities.find(e => e.doc.channel === channel && e.doc.channel_user_id === channelUserId)
50
+
51
+ let userId: string
52
+ let isNewUser = false
53
+
54
+ if (identity) {
55
+ userId = identity.doc.user_id
56
+ // Backfill/refresh the owning account so replies survive a restart, when
57
+ // the in-memory session→account map in ChannelManager is empty.
58
+ if (accountId && identity.doc.account_id !== accountId) {
59
+ await identitiesCol.put(identity.id, { ...identity.doc, account_id: accountId })
60
+ }
61
+ } else {
62
+ // Sistema mono-usuario: reutilizar el usuario del onboarding
63
+ const allUsers = await usersCol.scan({})
64
+ const existingUser = [...allUsers].sort((a, b) => a.doc.created_at - b.doc.created_at)[0]
65
+
66
+ if (!existingUser) {
67
+ throw new Error("No user found in database. Please run the onboarding process first.")
68
+ }
69
+
70
+ userId = existingUser.id
71
+
72
+ // Vincular este canal al usuario existente (auto-link en el primer mensaje)
73
+ // put(): si ya existe una fila (user_id, channel), actualiza channel_user_id
74
+ // con el valor real del canal (e.g. chat ID numérico de Telegram).
75
+ await identitiesCol.put(`${userId}:${channel}`, {
76
+ user_id: userId, channel, channel_user_id: channelUserId,
77
+ account_id: accountId, linked_at: Date.now(),
78
+ })
79
+ }
80
+
81
+ const coordinators = await agentsCol.findBy("role", "coordinator", { limit: 1 })
82
+ const agentId = coordinators[0]?.id || "bee"
83
+
84
+ // Un hilo por canal Y por contacto (`${user}/${canal}/${peer}` — ver
85
+ // agent/thread-id.ts). Antes todos los canales compartían un único hilo
86
+ // (`threadId = userId`), así que lo hablado por Telegram entraba en el mismo
87
+ // contexto que la web y un grupo de WhatsApp escribía en el chat privado del
88
+ // dueño. Los session IDs de transporte siguen enrutando las respuestas.
89
+ const threadId = options.peerId
90
+ ? await ensureThread({
91
+ userId,
92
+ channel,
93
+ peerId: options.peerId,
94
+ peerKind: options.peerKind,
95
+ })
96
+ : channel === "webchat"
97
+ // Sin conversación indicada, la web sigue donde se quedó: la más reciente
98
+ // —que puede ser el hilo previo a la separación por canal— o una nueva.
99
+ ? ((await mostRecentWebThread(userId))?.id ?? (await createWebConversation(userId)).id)
100
+ : await ensureThread({
101
+ userId,
102
+ channel,
103
+ peerId: channelUserId,
104
+ peerKind: options.peerKind,
105
+ })
106
+
107
+ return { userId, threadId, agentId, isNewUser }
108
+ }
@@ -134,7 +134,14 @@ export interface Skill {
134
134
  examples?: SkillExample[];
135
135
  }
136
136
 
137
- function parseFrontmatter(content: string): { frontmatter: Record<string, unknown>; body: string } {
137
+ /**
138
+ * Separa el frontmatter YAML del cuerpo markdown de un `SKILL.md`.
139
+ *
140
+ * Exportada porque `services/skills.ts` importa skills del disco a la BD y
141
+ * necesita exactamente este parseo: duplicarlo garantizaría que un día
142
+ * acepten formatos distintos.
143
+ */
144
+ export function parseFrontmatter(content: string): { frontmatter: Record<string, unknown>; body: string } {
138
145
  const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
139
146
 
140
147
  if (!match) {
@@ -0,0 +1,105 @@
1
+ ---
2
+ name: artifact_reader
3
+ description: "Leer archivos grandes que llegaron como artifact_ref: por tramos o buscando dentro, sin volcarlos enteros al contexto."
4
+ version: 1.0.0
5
+ author: Hive Team
6
+ icon: "📎"
7
+ category: artifacts
8
+ permissions:
9
+ - artifact_read
10
+ dependencies: []
11
+ tools: [artifact_read, artifact_inspect]
12
+
13
+ # Structured skill fields
14
+ triggers:
15
+ - "leé el archivo adjunto"
16
+ - "read the attachment"
17
+ - "qué dice el documento"
18
+ - "what does the document say"
19
+ - "buscá en el archivo"
20
+ - "search in the file"
21
+ - "artifact_ref"
22
+ - "el resultado quedó truncado"
23
+ - "the result was truncated"
24
+ - "seguí leyendo"
25
+ - "keep reading"
26
+
27
+ preferred_agents: []
28
+
29
+ steps:
30
+ - step: 1
31
+ action: artifact_inspect
32
+ instruction: "Ver tamaño y tipo antes de leer. Un artefacto de 2 MB no se lee entero: se busca dentro"
33
+ params:
34
+ artifactId: "id del artifact_ref"
35
+ output: metadatos
36
+
37
+ - step: 2
38
+ action: artifact_read
39
+ instruction: "Si se busca algo puntual, usar `search` — devuelve extractos alrededor de cada coincidencia y cuesta una fracción de paginar. Si hace falta el texto seguido, usar offset/limit"
40
+ params:
41
+ artifactId: "id del artefacto"
42
+ search: "término a buscar (opcional)"
43
+ offset: "desde qué carácter (opcional)"
44
+ limit: "cuántos caracteres (opcional)"
45
+ output: contenido
46
+
47
+ - step: 3
48
+ action: synthesize
49
+ instruction: "Responder con lo encontrado, citando de dónde salió"
50
+ output: respuesta
51
+
52
+ rules:
53
+ - "**Buscar antes que paginar.** `search` devuelve extractos de todas las coincidencias en una sola llamada; paginar un archivo grande con offset/limit gasta varios turnos y llena el contexto con texto que no se necesitaba."
54
+ - "`artifact_inspect` no devuelve contenido: sirve para decidir cómo leer sin gastar contexto en averiguarlo."
55
+ - "Para continuar una lectura, usar el `next_offset` que devolvió la llamada anterior. No adivinar la posición."
56
+ - "Un `artifact_ref` aparece cuando un resultado fue demasiado grande para el contexto. No es un error: es el archivo esperando a que lo leas por partes."
57
+ - "Los artefactos de imagen no se leen con `artifact_read` — para eso están `image_metadata` e `image_transform`."
58
+
59
+ output_format:
60
+ structure: markdown
61
+ sections:
62
+ - "lo encontrado"
63
+ - "de qué parte del archivo salió"
64
+ max_length: "Sólo lo relevante, nunca el archivo entero"
65
+
66
+ examples:
67
+ - user_input: "buscá 'error de conexión' en el log adjunto"
68
+ expected_behavior: "artifact_read con search='error de conexión' — una llamada, no paginar"
69
+
70
+ - user_input: "qué dice el documento"
71
+ expected_behavior: "artifact_inspect para ver el tamaño → artifact_read del primer tramo → resumir"
72
+
73
+ - user_input: "seguí leyendo"
74
+ expected_behavior: "artifact_read con el next_offset de la llamada anterior"
75
+ ---
76
+
77
+ # Artifact Reader Skill
78
+
79
+ ## Cuándo se Activa
80
+
81
+ Cuando aparece un **`artifact_ref`**: un archivo, un adjunto o el resultado de
82
+ una tool que era demasiado grande para entrar en el contexto.
83
+
84
+ ## Herramientas Disponibles
85
+
86
+ | Tool | Qué hace | Cuándo usarla |
87
+ |------|----------|---------------|
88
+ | `artifact_inspect` | Tamaño, tipo MIME, integridad | Antes de leer, para decidir cómo |
89
+ | `artifact_read` | Contenido, por tramos o buscando | Para leer de verdad |
90
+
91
+ ## Lo Que Hay Que Entender
92
+
93
+ **Un `artifact_ref` no es un error.** Es el mecanismo por el que un archivo
94
+ grande queda fuera de la ventana de contexto y a la vez disponible. El archivo
95
+ está entero; lo que cambia es que se lee a pedido en vez de entrar completo en
96
+ cada turno de la conversación.
97
+
98
+ **Buscar cuesta mucho menos que paginar.** `artifact_read` con `search` recorre
99
+ el archivo del lado del servidor y devuelve extractos de cada coincidencia con
100
+ su contexto alrededor. Paginar el mismo archivo con `offset`/`limit` gasta un
101
+ turno por tramo y mete en el contexto un montón de texto que no hacía falta.
102
+ Cuando se sabe qué se busca, se busca.
103
+
104
+ **Continuar es explícito.** Cada lectura devuelve `next_offset`. Ese es el valor
105
+ que se pasa para seguir — no se calcula a mano.
@@ -54,8 +54,8 @@ steps:
54
54
  cron_expression: "Cron expression for recurring (e.g., '0 9 * * *')"
55
55
  fire_at: "ISO datetime for one_shot (e.g., '2026-04-20T09:00:00')"
56
56
  channel: "Notification channel (telegram, discord, webchat)"
57
- start_at: "Optional ISO datetime - start of execution window (Croner startAt)"
58
- stop_at: "Optional ISO datetime - end of execution window (Croner stopAt)"
57
+ start_at: "Optional ISO datetime - start of execution window"
58
+ stop_at: "Optional ISO datetime - end of execution window"
59
59
  dom_and_dow: "0 = OR logic (default), 1 = AND logic for day-of-month + day-of-week"
60
60
  max_runs: "Optional max executions"
61
61
  output: cron_id
@@ -133,22 +133,32 @@ Para gestionar tareas programadas (cron jobs): crear, listar, actualizar, pausar
133
133
  | `cron_expression` | string | Expresión cron (solo para recurring) |
134
134
  | `fire_at` | string | Datetime ISO (solo para one_shot) |
135
135
  | `channel` | string | Canal de notificación |
136
- | `start_at` | string | Inicio de ventana opcional (Croner startAt) |
137
- | `stop_at` | string | Fin de ventana opcional (Croner stopAt) |
136
+ | `start_at` | string | Inicio de ventana opcional |
137
+ | `stop_at` | string | Fin de ventana opcional |
138
138
  | `dom_and_dow` | number | 0=OR (default), 1=AND (día mes + día semana) |
139
+ | `max_runs` | number | Deja de correr después de N corridas ("recordámelo 3 veces") |
140
+ | `payload` | object | Datos que recibe el agente al ejecutarse |
141
+ | `agent_id` | string | Agente concreto que debe ejecutarla. Si se omite, decide el coordinador |
142
+ | `tool_name` | string | Ejecutar una tool directamente, sin pasar por un agente |
143
+
144
+ > **La zona horaria no se pasa acá**: sale del perfil del usuario. No la
145
+ > inventes ni la pidas — si el usuario dice "a las 9", son las 9 de su reloj.
139
146
 
140
147
  ## Cron Expression Format
141
148
 
142
149
  ```
143
- * * * * *
144
- │ │
145
- │ │ └── Día semana (0-6, 0=Domingo)
146
- │ │ │ └──── Mes (1-12)
147
- │ │ └────── Día del mes (1-31)
148
- └──────── Hora (0-23)
149
- └────────── Minuto (0-59)
150
+ ┌───────── segundos (0-59) ← opcional, sólo si hacen falta
151
+ ┌─────── minuto (0-59)
152
+ │ │ ┌───── hora (0-23)
153
+ │ │ │ ┌─── día del mes (1-31)
154
+ │ │ ┌─ mes (1-12 o JAN-DEC)
155
+ │ │ ┌ día de semana (0-7 o SUN-SAT, 0 y 7 = domingo)
156
+ * * * * * *
150
157
  ```
151
158
 
159
+ Cinco campos, o seis poniendo los segundos adelante. Acepta `*`, listas `1,15`,
160
+ rangos `1-5`, pasos `*/2`, y nombres de mes y de día.
161
+
152
162
  ## Ejemplos Comunes
153
163
 
154
164
  | Expresión | Significado |
@@ -0,0 +1,120 @@
1
+ ---
2
+ name: image_editor
3
+ description: "Convertir, redimensionar y rotar imágenes con Bun.Image. Inspeccionar dimensiones y formato sin cargar la imagen al contexto."
4
+ version: 1.0.0
5
+ author: Hive Team
6
+ icon: "🖼️"
7
+ category: images
8
+ permissions:
9
+ - image_processing
10
+ dependencies: []
11
+ tools: [image_metadata, image_transform, artifact_inspect]
12
+
13
+ # Structured skill fields
14
+ triggers:
15
+ - "convertí la imagen"
16
+ - "convert image"
17
+ - "redimensioná la imagen"
18
+ - "resize image"
19
+ - "achicá la foto"
20
+ - "make it smaller"
21
+ - "pasala a webp"
22
+ - "convert to webp"
23
+ - "rotá la imagen"
24
+ - "rotate image"
25
+ - "qué tamaño tiene"
26
+ - "image dimensions"
27
+ - "comprimí la imagen"
28
+ - "compress image"
29
+ - "hacé una miniatura"
30
+ - "make a thumbnail"
31
+
32
+ preferred_agents: []
33
+
34
+ steps:
35
+ - step: 1
36
+ action: image_metadata
37
+ instruction: "Leer ancho, alto y formato ANTES de transformar. Sin esto no se puede decidir un tamaño con criterio, y redimensionar a ciegas agranda imágenes chicas"
38
+ params:
39
+ artifact_id: "id del artefacto con la imagen"
40
+ output: dimensiones_originales
41
+
42
+ - step: 2
43
+ action: image_transform
44
+ instruction: "Transformar. Dando sólo ancho O sólo alto se mantiene la proporción; dando los dos, la imagen se deforma"
45
+ params:
46
+ artifact_id: "id del artefacto de origen"
47
+ width: "ancho en píxeles (opcional)"
48
+ format: "jpeg | png | webp | avif | heic (opcional)"
49
+ quality: "1-100, sólo para formatos con pérdida (opcional)"
50
+ output: artefacto_nuevo
51
+
52
+ - step: 3
53
+ action: notify
54
+ instruction: "Avisar con el id del artefacto resultante y su tamaño"
55
+ output: aviso
56
+
57
+ rules:
58
+ - "Las imágenes se manejan por `artifact_id`, nunca por base64 en el mensaje: una imagen incrustada llena la ventana de contexto y se reenvía en cada turno."
59
+ - "`image_transform` NO modifica el original: crea un artefacto nuevo y devuelve su id. El original queda intacto."
60
+ - "Para conservar la proporción, dar sólo `width` o sólo `height`. Dar los dos deforma la imagen; hacelo únicamente si el usuario lo pidió."
61
+ - "`quality` sólo aplica a formatos con pérdida (jpeg, webp, avif). En png se ignora."
62
+ - "`rotate` acepta 90, 180 o 270. Otros valores se rechazan."
63
+ - "Si no sabés qué formato quiere el usuario, webp es la mejor opción por defecto: pesa menos que jpeg y png con calidad equivalente."
64
+
65
+ output_format:
66
+ structure: markdown
67
+ sections:
68
+ - "artifact_id resultante"
69
+ - "formato y dimensiones"
70
+ - "tamaño en bytes"
71
+ max_length: "Breve — la imagen no se incrusta en la respuesta"
72
+
73
+ examples:
74
+ - user_input: "convertí esta imagen a webp"
75
+ expected_behavior: "image_metadata para ver el formato → image_transform con format=webp → devolver el id nuevo"
76
+
77
+ - user_input: "hacela de 800 de ancho"
78
+ expected_behavior: "image_transform con width=800 y SIN height, para no deformarla"
79
+
80
+ - user_input: "hacé una miniatura"
81
+ expected_behavior: "image_transform con width≈200, format=webp, quality≈80"
82
+
83
+ - user_input: "qué tamaño tiene esta imagen"
84
+ expected_behavior: "image_metadata solo — no hace falta transformar nada"
85
+ ---
86
+
87
+ # Image Editor Skill
88
+
89
+ ## Cuándo se Activa
90
+
91
+ Cuando el usuario quiere **cambiar** una imagen (formato, tamaño, rotación) o
92
+ **saber** sus características. Corre sobre `Bun.Image`, nativo del runtime.
93
+
94
+ ## Herramientas Disponibles
95
+
96
+ | Tool | Qué hace | Cuándo usarla |
97
+ |------|----------|---------------|
98
+ | `image_metadata` | Ancho, alto y formato | Siempre antes de transformar |
99
+ | `image_transform` | Convierte, redimensiona, rota | El trabajo en sí |
100
+ | `artifact_inspect` | Tipo MIME, integridad, tamaño | Cuando no está claro si el artefacto es una imagen |
101
+
102
+ ## Lo Que Hay Que Entender
103
+
104
+ **Todo pasa por artefactos.** Una imagen no viaja en el mensaje: vive como
105
+ artefacto y se la nombra por su `artifact_id`. Es lo que evita que una foto de 4
106
+ MB entre a la ventana de contexto y se reenvíe en cada turno de la conversación.
107
+
108
+ **Transformar no destruye.** `image_transform` devuelve un artefacto **nuevo**.
109
+ El original sigue disponible, así que se puede probar un tamaño, ver que no
110
+ gustó y probar otro sin haber perdido nada.
111
+
112
+ **La proporción se pierde en silencio.** Si se pasan `width` y `height` juntos,
113
+ la imagen se estira sin avisar. Pasando uno solo, el otro se calcula.
114
+
115
+ ## Formatos
116
+
117
+ `jpeg` · `png` · `webp` · `avif` · `heic`
118
+
119
+ Ante la duda, **webp**: pesa bastante menos que jpeg y png a calidad comparable,
120
+ y lo entienden todos los navegadores actuales.
@@ -8,7 +8,7 @@ category: web
8
8
  permissions:
9
9
  - browser_control
10
10
  dependencies: []
11
- tools: [browser_navigate, browser_click, browser_type, browser_screenshot]
11
+ tools: [browser_navigate, browser_wait, browser_click, browser_type, browser_screenshot, browser_script, computer_use_task]
12
12
 
13
13
  # Structured skill fields
14
14
  triggers:
@@ -38,13 +38,20 @@ steps:
38
38
  output: page_loaded
39
39
 
40
40
  - step: 2
41
+ action: browser_wait
42
+ instruction: "Wait for the element before acting on it. Clicking or typing before the element exists is the most common failure in web automation, and it fails silently"
43
+ params:
44
+ selector: "CSS selector of the element about to be used"
45
+ output: element_ready
46
+
47
+ - step: 3
41
48
  action: browser_click
42
49
  instruction: "Click on elements (buttons, links) to navigate or trigger actions"
43
50
  params:
44
51
  selector: "CSS selector for element"
45
52
  output: click_result
46
53
 
47
- - step: 3
54
+ - step: 4
48
55
  action: browser_type
49
56
  instruction: "Type text into form fields (inputs, textareas)"
50
57
  params:
@@ -52,12 +59,14 @@ steps:
52
59
  text: "text to type"
53
60
  output: type_result
54
61
 
55
- - step: 4
62
+ - step: 5
56
63
  action: browser_screenshot
57
64
  instruction: "Take screenshot to verify state after interactions"
58
65
  output: verification_screenshot
59
66
 
60
67
  rules:
68
+ - "Escalar sólo cuando haga falta, en este orden: `browser_click`/`browser_type` con un selector → `browser_script` si el elemento no se puede alcanzar con un selector estable → `computer_use_task` si la página no tiene selectores usables. Empezar por el último es lento y caro: mira la pantalla y razona en cada paso."
69
+ - "Esperar con `browser_wait` antes de cada clic o tipeo, no confiar en que la página ya cargó: es la falla más común de la automatización web, y falla en silencio."
61
70
  - "Wait for page to fully load after each navigation or significant interaction"
62
71
  - "Use specific, stable CSS selectors (IDs preferred over classes)"
63
72
  - "Take screenshots after critical steps for verification"
@@ -8,7 +8,7 @@ category: web
8
8
  permissions:
9
9
  - browser_control
10
10
  dependencies: []
11
- tools: [browser_navigate, browser_screenshot, web_fetch]
11
+ tools: [browser_navigate, browser_screenshot, browser_extract, browser_wait]
12
12
 
13
13
  # Structured skill fields
14
14
  triggers:
@@ -39,16 +39,26 @@ steps:
39
39
  output: screenshot
40
40
 
41
41
  - step: 3
42
- action: web_fetch
43
- instruction: "Extract text content from rendered page as markdown"
44
- output: extracted_content
42
+ action: browser_wait
43
+ instruction: "Wait for the content selector before extracting an SPA renders after load fires"
44
+ params:
45
+ selector: "CSS selector of the content that must be present"
46
+ output: content_ready
45
47
 
46
48
  - step: 4
49
+ action: browser_extract
50
+ instruction: "Extract the rendered DOM. Omit the selector for a compact accessibility snapshot of the whole page"
51
+ params:
52
+ selector: "CSS selector, or 'body' for the whole page"
53
+ output: extracted_content
54
+
55
+ - step: 5
47
56
  action: synthesize
48
57
  instruction: "Combine screenshot and text content for comprehensive capture"
49
58
  output: scraped_data
50
59
 
51
60
  rules:
61
+ - "Extraer con `browser_extract`, NUNCA con `web_fetch`: web_fetch vuelve a pedir la URL al servidor y recibe el HTML sin renderizar, que es justamente lo que esta skill existe para evitar. En un SPA devuelve una cáscara vacía."
52
62
  - "Wait for full page load including JavaScript-rendered content"
53
63
  - "Take screenshot before extracting text to capture initial state"
54
64
  - "For infinite scroll pages, scroll down and capture multiple screenshots"
@@ -66,10 +76,10 @@ output_format:
66
76
 
67
77
  examples:
68
78
  - user_input: "capturá el contenido de https://example.com/dashboard"
69
- expected_behavior: "browser_navigate → wait for JS render → browser_screenshot → browser_fetch → return both"
79
+ expected_behavior: "browser_navigate → browser_wait → browser_screenshot → browser_extract → return both"
70
80
 
71
81
  - user_input: "obtené la página renderizada de la app"
72
- expected_behavior: "Navigate → wait for SPA to load → screenshot + fetch content"
82
+ expected_behavior: "Navigate → browser_wait for the SPA root → screenshot + browser_extract"
73
83
 
74
84
  - user_input: "scrapeá este sitio con javascript"
75
85
  expected_behavior: "Full browser render → capture visual and text content"
@@ -87,7 +97,12 @@ Esta skill se activa para sitios web dinámicos que requieren JavaScript renderi
87
97
  |------|----------|---------------|
88
98
  | `browser_navigate` | Navega y renderiza página completa | Sitios con JavaScript/SPA |
89
99
  | `browser_screenshot` | Captura estado visual | Evidencia de contenido renderizado |
90
- | `web_fetch` | Extrae texto como markdown | Contenido textual de página renderizada |
100
+ | `browser_extract` | Extrae del DOM ya renderizado | Contenido textual o estructurado del SPA |
101
+ | `browser_wait` | Espera a que aparezca un selector | Antes de extraer, en páginas que cargan por partes |
102
+
103
+ > **No usar `web_fetch` acá.** Vuelve a pedir la URL al servidor y recibe el HTML
104
+ > sin JavaScript ejecutado — en un SPA, una cáscara vacía. Para eso está
105
+ > `browser_extract`, que lee el DOM que el navegador ya renderizó.
91
106
 
92
107
  ## Workflow
93
108