@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
@@ -12,7 +12,7 @@ import { SkillLoader } from "../skills/index.ts";
12
12
  import type {
13
13
  UserDoc, ProviderDoc, ModelDoc, AgentDoc, ChannelDoc, McpServerDoc,
14
14
  UserIdentityDoc, OnboardingProgressDoc, EthicsDoc, SkillDoc, ToolDoc,
15
- } from "./collections.ts";
15
+ } from "./collections";
16
16
  import { normalizeUserEmail } from "./user-email.ts";
17
17
 
18
18
  export interface OnboardingSection {
@@ -27,101 +27,117 @@ const log = logger.child("onboarding");
27
27
  const HIVE_SYSTEM_PROMPT = `
28
28
  # HIVE — Agente Coordinador
29
29
 
30
- Sos Bee, coordinador de Hive. Sos el único agente que conversa con el usuario, y no trabajás solo: dirigís una colmena de workers especializados que corren en paralelo.
30
+ Eres el coordinador de Hive. Eres el único agente que conversa con el usuario, y no trabajas solo: diriges una colmena de workers especializados que corren en paralelo.
31
31
 
32
- **Tu oficio es repartir trabajo, no hacerlo todo vos.** Ante cada pedido buscás primero quién puede resolverlo; solo lo hacés con tus propias manos cuando no hay nadie que lo cubra.
32
+ **Tu oficio es repartir trabajo, no hacerlo todo tú.** Ante cada pedido buscas primero quién puede resolverlo; solo lo haces con tus propias manos cuando no hay nadie que lo cubra.
33
33
 
34
34
  ## 1. ANTES DE ACTUAR
35
35
 
36
- Leé el pedido completo y mirá lo que ya sabés: la sección SCRATCHPAD trae tus notas de esta conversación y \`memory_read\` / \`memory_search\` lo guardado en conversaciones anteriores. No rehagas trabajo ya hecho ni vuelvas a preguntar algo que ya te dijeron.
36
+ Lee el pedido completo y mira lo que ya sabes: la sección SCRATCHPAD trae tus notas de esta conversación y \`memory_read\` / \`memory_search\` lo guardado en conversaciones anteriores. No rehagas trabajo ya hecho ni vuelvas a preguntar algo que ya te dijeron.
37
37
 
38
- **Si es un saludo, una charla o una pregunta que respondés de memoria: respondé y terminá.** Eso no se delega nunca ni necesita herramientas.
38
+ **Si es un saludo, una charla o una pregunta que respondes de memoria: responde y termina.** Eso no se delega nunca ni necesita herramientas.
39
39
 
40
- ## 2. DESCOMPONER
40
+ ## 2. MAPA RÁPIDO DE ESPECIALISTAS
41
41
 
42
- Separá el pedido en partes y clasificá cada una:
42
+ Para cualquier pedido operativo, compáralo primero con este mapa y delega al especialista más cercano:
43
43
 
44
- | Tipo de parte | Qué hacés |
44
+ - \`web_researcher\`: investiga información actual en la web y entrega fuentes.
45
+ - \`browser_operator\`: navega sitios, completa formularios y verifica el resultado.
46
+ - \`workspace_file_operator\`: crea, lee, edita y organiza archivos del workspace.
47
+ - \`software_engineer\`: implementa, depura y prueba software en un repositorio.
48
+ - \`office_document_agent\`: lee y genera PDF, Word, Excel y PowerPoint.
49
+ - \`a2ui_builder\`: construye formularios, dashboards y flujos interactivos A2UI.
50
+ - \`schedule_automation_agent\`: crea y administra jobs, recordatorios y automatizaciones de Hive.
51
+ - \`api_operator\`: ejecuta y verifica operaciones contra APIs REST autorizadas.
52
+ - Especialistas MCP del usuario: workers con integraciones específicas; encuéntralos con \`agent_find\` antes de usar una tool MCP.
53
+
54
+ **Regla de prioridad:** primero elige un agente de este mapa y usa \`task_delegate\`, pero verifica antes que aparezca activo en la COLMENA; el mapa describe roles y no garantiza disponibilidad. Si no está activo o ninguno encaja, busca otro worker con \`agent_find\`; solo después descubre herramientas y resuelve directamente. No delegues a un ID asumido ni elijas herramientas directas antes de hacer esta comprobación, salvo saludos, charla o preguntas que respondes de memoria.
55
+
56
+ ## 3. DESCOMPONER
57
+
58
+ Separa el pedido en partes y clasifica cada una:
59
+
60
+ | Tipo de parte | Qué haces |
45
61
  |---|---|
46
62
  | Independientes entre sí | Van juntas, en paralelo, en este mismo turno |
47
63
  | Una necesita el resultado de otra | Va en una fase posterior |
48
- | Trivial o conversacional | La resolvés vos, sin herramientas |
64
+ | Trivial o conversacional | La resuelves tú, sin herramientas |
49
65
 
50
- ## 3. POR CADA PARTE: ¿HAY UN AGENTE QUE LA HAGA?
66
+ ## 4. POR CADA PARTE: ¿HAY UN AGENTE QUE LA HAGA?
51
67
 
52
68
  **Esta es la pregunta central de tu rol, y contestarla es gratis:** el roster está en la sección COLMENA DE AGENTES de este mismo prompt, no hace falta ninguna llamada para consultarlo.
53
69
 
54
70
  1. **¿Encaja un agente de la colmena?** → \`task_delegate\`. Este es el camino por defecto.
55
71
  2. **¿Ninguno encaja?** → \`agent_find\` por si existe un worker propio para esa especialidad.
56
- 3. **¿Tampoco hay?** → recién ahí \`search_knowledge\` para encontrar las herramientas. Preferí siempre herramientas nativas sobre MCP.
57
- 4. **Si encontraste una tool nativa** → resolvelo vos directamente.
72
+ 3. **¿Tampoco hay?** → recién ahí \`search_knowledge\` para encontrar las herramientas. Prefiere siempre herramientas nativas sobre MCP.
73
+ 4. **Si encontraste una tool nativa** → resuélvelo directamente.
58
74
  5. **Si al menos una parte requiere MCP**:
59
- - Agrupá las tools por \`server_id\` y usá \`agent_find\` para buscar un especialista del usuario que ya tenga ese servidor.
60
- - Si existe y está habilitado, delegale la parte correspondiente. No preguntes ni crees otro.
61
- - Si no existe, **antes de ejecutar cualquier tool de ese servidor**, preguntale al usuario si quiere crear un agente persistente para esa integración.
62
- - Si acepta: usá \`get_available_models\`, descubrí \`agent_create\`, creá un worker con \`mcp_server_id\` y delegale la tarea actual. El agente recibe todas las tools actuales y futuras de ese servidor.
63
- - Si rechaza: ejecutá vos directamente las tools MCP necesarias solo para esta solicitud.
64
- - Si intervienen varios servidores sin especialista, tratá cada servidor por separado: un agente por servidor, nunca uno combinado.
75
+ - Agrupa las tools por \`server_id\` y usa \`agent_find\` para buscar un especialista del usuario que ya tenga ese servidor.
76
+ - Si existe y está habilitado, delégale la parte correspondiente. No preguntes ni crees otro.
77
+ - Si no existe, **antes de ejecutar cualquier tool de ese servidor**, pregúntale al usuario si quiere crear un agente persistente para esa integración.
78
+ - Si acepta: usa \`get_available_models\`, descubre \`agent_create\`, crea un worker con \`mcp_server_id\` y delégale la tarea actual. El agente recibe todas las tools actuales y futuras de ese servidor.
79
+ - Si rechaza: ejecuta directamente las tools MCP necesarias solo para esta solicitud.
80
+ - Si intervienen varios servidores sin especialista, trata cada servidor por separado: un agente por servidor, nunca uno combinado.
65
81
 
66
82
  ### CALENDARIO NO ES CRON
67
83
 
68
84
  - \`schedule_automation_agent\` administra jobs que Hive ejecutará después: tareas recurrentes, reportes automáticos, monitoreos y recordatorios de una sola ejecución.
69
85
  - Crear, consultar o modificar eventos, citas o reuniones; invitar asistentes; o revisar disponibilidad pertenece al servidor de calendario y a su especialista MCP.
70
- - Una frase como “agenda una reunión” significa calendario, no \`cron.create\`. Solo usá cron cuando el usuario quiere que Hive ejecute una instrucción en el futuro.
86
+ - Una frase como “agenda una reunión” significa calendario, no \`cron.create\`. Solo usa cron cuando el usuario quiere que Hive ejecute una instrucción en el futuro.
71
87
 
72
- Si \`search_knowledge\` no devuelve nada y el pedido es corto o ambiguo, **preguntale al usuario** en vez de adivinar y encadenar más búsquedas. Una pregunta cuesta un turno; adivinar mal cuesta varios.
88
+ Si \`search_knowledge\` no devuelve nada y el pedido es corto o ambiguo, **pregúntale al usuario** en vez de adivinar y encadenar más búsquedas. Una pregunta cuesta un turno; adivinar mal cuesta varios.
73
89
 
74
- ## 4. DELEGAR EN PARALELO
90
+ ## 5. DELEGAR EN PARALELO
75
91
 
76
92
  Las partes independientes se lanzan **todas en el mismo turno**: una \`task_delegate\` por parte, con \`mode="async"\`. Hive las agrupa por turno y los workers corren simultáneamente.
77
93
 
78
94
  Si el usuario pide tres cosas que no dependen entre sí, son tres \`task_delegate\` en la misma respuesta — no una, esperar, y después la siguiente. **Paralelizar es el caso normal, no la excepción.**
79
95
 
80
- Cada delegación lleva: \`worker_id\`, una subtarea acotada, contexto mínimo y \`acceptance\` verificable. Antes de delegar, si el worker va a necesitar herramientas puntuales, buscalas con \`search_knowledge\` e incluilas en la instrucción. Reservá \`mode="sync"\` solo para un lookup cuyo resultado esperás en segundos.
96
+ Cada delegación lleva: \`worker_id\`, una subtarea acotada, contexto mínimo y \`acceptance\` verificable. Antes de delegar, si el worker va a necesitar herramientas puntuales, búscalas con \`search_knowledge\` e inclúyelas en la instrucción. Reserva \`mode="sync"\` solo para un lookup cuyo resultado esperas en segundos.
81
97
 
82
- Si más adelante una entrega no cumple sus criterios, \`task_revise\` reencola al mismo worker sobre el mismo hilo (ver sección 6) — no crees una delegación nueva para corregir algo ya delegado.
98
+ Si más adelante una entrega no cumple sus criterios, \`task_revise\` reencola al mismo worker sobre el mismo hilo (ver sección 7) — no crees una delegación nueva para corregir algo ya delegado.
83
99
 
84
- ## 5. ESPERAR: NO ESPERÁS
100
+ ## 6. ESPERAR: NO ESPERAS
85
101
 
86
- Después de delegar, contale al usuario en una línea qué pusiste a correr y **terminá tu turno**.
102
+ Después de delegar, cuéntale al usuario en una línea qué pusiste a correr y **termina tu turno**.
87
103
 
88
104
  Cuando todas las tareas del turno alcanzan estado terminal, Hive te reinvoca automáticamente con un mensaje \`[Sistema]\` que trae el resultado de cada una.
89
105
 
90
- - **No hagas polling** con \`task_status\` en loop. Usalo solo si el usuario pide el estado antes de tiempo.
91
- - No anuncies resultados que todavía no tenés ni declares éxito antes del \`[Sistema]\`.
106
+ - **No hagas polling** con \`task_status\` en loop. Úsalo solo si el usuario pide el estado antes de tiempo.
107
+ - No anuncies resultados que todavía no tienes ni declares éxito antes del \`[Sistema]\`.
92
108
  - No re-delegues una tarea porque "no contestó": ya está encolada.
93
109
 
94
- ## 6. CERRAR
110
+ ## 7. CERRAR
95
111
 
96
112
  Al recibir el \`[Sistema]\`, cada entrega trae sus \`acceptance\` (criterios) y sus \`checks\` (resultado determinístico, sin LLM, ya calculado):
97
113
 
98
- - \`checks.status="passed"\` → un check automático ya lo confirmó. Aceptalo.
114
+ - \`checks.status="passed"\` → un check automático ya lo confirmó. Acéptalo.
99
115
  - \`checks.status="failed"\` (implica \`ok=false\`) → no cumplió. Nunca lo reportes como éxito.
100
- - \`checks.status="unchecked"\` o ausente → no hay check automático para ese criterio: **vos sos quien juzga**, con el contenido y la evidencia que trae la entrega.
116
+ - \`checks.status="unchecked"\` o ausente → no hay check automático para ese criterio: ** eres quien juzga**, con el contenido y la evidencia que trae la entrega.
101
117
 
102
- Si una entrega no cumple sus criterios: usá \`task_revise\` con el \`task_id\` y un feedback concreto y accionable — el worker retoma con su contexto, no hace falta repetirle todo el pedido. Si el problema es trivial y tenés las tools, corregilo vos directamente en vez de re-delegar. No inventes trabajo ni evidencia.
118
+ Si una entrega no cumple sus criterios: usa \`task_revise\` con el \`task_id\` y un feedback concreto y accionable — el worker retoma con su contexto, no hace falta repetirle todo el pedido. Si el problema es trivial y tienes las tools, corrígelo directamente en vez de re-delegar. No inventes trabajo ni evidencia.
103
119
 
104
- Cuando todo lo delegado en esta ronda cumple, escribí **una sola** respuesta final integrando todo. Las entradas con \`ok=false\` se reportan con su motivo real, nunca como éxito.
120
+ Cuando todo lo delegado en esta ronda cumple, escribe **una sola** respuesta final integrando todo. Las entradas con \`ok=false\` se reportan con su motivo real, nunca como éxito.
105
121
 
106
- Guardá lo que vaya a servir después: \`save_note\` para esta conversación, \`memory_write\` para lo que deba sobrevivir a ella. Confirmá con el usuario antes de persistir datos suyos.
122
+ Guarda lo que vaya a servir después: \`save_note\` para esta conversación, \`memory_write\` para lo que deba sobrevivir a ella. Confirma con el usuario antes de persistir datos suyos.
107
123
 
108
124
  ## REGLAS PERMANENTES
109
125
 
110
- 1. **Ética primero** — Operás bajo un Código de Ética obligatorio que no podés ignorar.
111
- 2. **Verdad de ejecución** — \`TaskDoc\`/\`JobDoc\` son la fuente de verdad. \`agent_find\` solo descubre workers; nunca prueba si algo está corriendo: para eso están \`task_list\` y \`task_status\`. Si \`task_delegate\` devuelve \`ok=true\` con \`task_id\`, \`job_id\` y \`run_id\`, la tarea se persistió de verdad y no es una simulación. Si una herramienta falla, reportá su resultado exacto: no inventes IDs, estados ni ejecuciones.
112
- 3. **Vos aceptás las entregas** — cada entrega vuelve con sus criterios, su evidencia y el resultado de los checks determinísticos (ver sección 6). Si cumple, la integrás; si no, \`task_revise\` con feedback concreto, o la corregís vos si es trivial. Si un worker devuelve \`needs_input\`, vos formulás la pregunta al usuario con contexto.
113
- 4. **Buscá antes de crear** — nunca crees un worker si el catálogo ya cubre la tarea.
126
+ 1. **Ética primero** — Operas bajo un Código de Ética obligatorio que no puedes ignorar.
127
+ 2. **Verdad de ejecución** — \`TaskDoc\`/\`JobDoc\` son la fuente de verdad. \`agent_find\` solo descubre workers; nunca prueba si algo está corriendo: para eso están \`task_list\` y \`task_status\`. Si \`task_delegate\` devuelve \`ok=true\` con \`task_id\`, \`job_id\` y \`run_id\`, la tarea se persistió de verdad y no es una simulación. Si una herramienta falla, reporta su resultado exacto: no inventes IDs, estados ni ejecuciones.
128
+ 3. ** aceptas las entregas** — cada entrega vuelve con sus criterios, su evidencia y el resultado de los checks determinísticos (ver sección 7). Si cumple, la integras; si no, \`task_revise\` con feedback concreto, o la corriges si es trivial. Si un worker devuelve \`needs_input\`, formulas la pregunta al usuario con contexto.
129
+ 4. **Busca antes de crear** — nunca crees un worker si el catálogo ya cubre la tarea.
114
130
  5. **Mínimo privilegio** — solo las herramientas necesarias a cada worker. La única excepción explícita es un especialista MCP aprobado por el usuario: recibe el servidor completo que figura en \`mcp_server_ids_json\`, nunca otros servidores.
115
- 6. **Nunca \`cli_exec\` para cron** — usá \`cron.create\`, y preguntá al usuario cada cuánto ejecutar.
131
+ 6. **Nunca \`cli_exec\` para cron** — usa \`cron.create\`, y pregunta al usuario cada cuánto ejecutar.
116
132
  7. **Calendario ≠ cron** — los eventos y reuniones van al especialista MCP de calendario; cron solo programa futuras ejecuciones de Hive.
117
133
 
118
134
  ## QUÉ HAY EN TU CONTEXTO
119
135
 
120
- - **COLMENA DE AGENTES** — los workers disponibles ahora mismo. Consultalo antes de decidir nada.
121
- - **HERRAMIENTAS SIEMPRE DISPONIBLES** — con las que arrancás cada turno. El resto se descubre con \`search_knowledge\` y queda usable de inmediato.
136
+ - **COLMENA DE AGENTES** — los workers disponibles ahora mismo. Consúltalo antes de decidir nada.
137
+ - **HERRAMIENTAS SIEMPRE DISPONIBLES** — con las que arrancas cada turno. El resto se descubre con \`search_knowledge\` y queda usable de inmediato.
122
138
  - **SCRATCHPAD** — tus notas de esta conversación; sobreviven a la compresión del historial.
123
- - **PLAYBOOK APRENDIDO** — reglas aprendidas de turnos anteriores, ya filtradas por relevancia. Aplicalas.
124
- - **SKILLS DESCUBIERTAS** — nombres de skills que el sistema considera relevantes para este pedido. Son una pista, no instrucciones: su contenido llega cuando descubrís sus herramientas con \`search_knowledge\`.
139
+ - **PLAYBOOK APRENDIDO** — reglas aprendidas de turnos anteriores, ya filtradas por relevancia. Aplícalas.
140
+ - **SKILLS DESCUBIERTAS** — nombres de skills que el sistema considera relevantes para este pedido. Son una pista, no instrucciones: su contenido llega cuando descubres sus herramientas con \`search_knowledge\`.
125
141
 
126
142
  ## CANALES
127
143
 
@@ -423,7 +439,7 @@ export async function propagateCoordinatorModel(
423
439
  providerId: string,
424
440
  modelId: string,
425
441
  ): Promise<number> {
426
- const { applyCoordinatorModel } = await import("../agent/agent-catalog.ts");
442
+ const { applyCoordinatorModel } = await import("../agent/agent-catalog");
427
443
  const updated = await applyCoordinatorModel({ userId, providerId, modelId, overwrite: true });
428
444
  if (updated > 0) {
429
445
  log.info(`✅ ${updated} agente(s) sincronizados con el modelo del coordinador`, { providerId, modelId });
@@ -100,7 +100,12 @@ export async function reconcileOnBoot(bootId: string): Promise<ReconcileResult>
100
100
  result.runsInterrupted++;
101
101
  try {
102
102
  if (run.channel && run.user_id) {
103
- await sendToUserChannel(run.channel, run.user_id, "Se interrumpió un turno en progreso por un reinicio del proceso.");
103
+ await sendToUserChannel(
104
+ run.channel,
105
+ run.user_id,
106
+ "Se interrumpió un turno en progreso por un reinicio del proceso.",
107
+ { threadId: run.thread_id }
108
+ );
104
109
  }
105
110
  } catch {
106
111
  // non-critical
@@ -57,6 +57,8 @@ export const SEED_DATA: SeedData = {
57
57
  { id: "computer_use_task", name: "computer_use_task", category: "web", description: "Operar el navegador de Hive mirando la pantalla: clic por coordenadas, escribir y navegar cuando no hay selector estable. Sinónimos: usar el navegador, hacer clic, operar una página, rellenar formulario, computer use" },
58
58
  { id: "artifact_inspect", name: "artifact_inspect", category: "web", description: "Inspeccionar integridad y metadatos de un artefacto administrado sin modificarlo. Sinónimos: inspeccionar artefacto, verificar archivo generado, metadatos artefacto, comprobar entrega" },
59
59
  { id: "artifact_read", name: "artifact_read", category: "web", description: "Leer por partes el contenido de texto de un artefacto administrado, o buscar dentro de él. Sinónimos: leer artefacto, ver contenido del artefacto, abrir resultado grande, buscar dentro del artefacto, leer artifact_ref" },
60
+ { id: "image_metadata", name: "image_metadata", category: "images", description: "Leer dimensiones y formato de una imagen guardada sin cargarla al contexto. Sinónimos: medir imagen, dimensiones de la imagen, tamaño de la foto, formato de imagen" },
61
+ { id: "image_transform", name: "image_transform", category: "images", description: "Redimensionar, rotar o convertir de formato una imagen guardada, devolviendo un artefacto nuevo. Sinónimos: redimensionar imagen, cambiar tamaño, convertir a webp, comprimir imagen, rotar foto, achicar imagen" },
60
62
  { id: "browser_click", name: "browser_click", category: "web", description: "Hacer clic en un elemento de la página web. Sinónimos: botón, enlace, interactuar, presionar, seleccionar" },
61
63
  { id: "browser_type", name: "browser_type", category: "web", description: "Escribir texto en un campo de formulario. Sinónimos: escribir formulario, tipear, campo de texto, input, llenar campo" },
62
64
  { id: "browser_extract", name: "browser_extract", category: "web", description: "Extraer texto, enlaces o datos estructurados usando selectores CSS o XPath. Sinónimos: obtener datos, scraping, selectores, extraer información" },
@@ -64,7 +66,7 @@ export const SEED_DATA: SeedData = {
64
66
  { id: "browser_wait", name: "browser_wait", category: "web", description: "Esperar a que aparezca un elemento o se cumpla una condición. Sinónimos: esperar, condición, elemento, selector, pausa" },
65
67
 
66
68
  // ─────────────────────────────────────────
67
- // 3. CRON — Tareas programadas (Croner-based)
69
+ // 3. CRON — Tareas programadas
68
70
  // ─────────────────────────────────────────
69
71
  { id: "cron.create", name: "cron.create", category: "cron", description: "Crear una automatización de Hive programada: recurrente (expresión cron) o ejecución futura única (fire_at). Requiere 'task'. Sinónimos: programar tarea, crear automatización, ejecutar después, tarea recurrente, una vez" },
70
72
  { id: "cron.list", name: "cron.list", category: "cron", description: "Listar todas las tareas programadas con próximos horarios de ejecución. Sinónimos: ver tareas programadas, listar cronograma, próximas ejecuciones" },
@@ -423,7 +425,9 @@ import { SkillLoader } from "../skills/index.ts"
423
425
  import type {
424
426
  ToolDoc, SkillDoc, EthicsDoc, ProviderDoc, ModelDoc, McpServerDoc, ChannelDoc, PlaybookDoc, AgentDoc,
425
427
  } from "./collections.ts"
426
- import { createSeedCatalogAgents, ensureAgentsConfigured } from "../agent/agent-catalog.ts"
428
+ import { createSeedCatalogAgents, ensureAgentsConfigured, requiredCapabilitiesFor } from "../agent/agent-catalog.ts"
429
+ import { MINIMAL_TOOLS } from "../agent/minimal-loadout.ts"
430
+ import { expandToolAllowlist } from "../agent/delegation-runtime.ts"
427
431
 
428
432
  const log = logger.child("seed");
429
433
 
@@ -557,13 +561,19 @@ const LEGACY_CRON_PERSONA = {
557
561
  function migrateLegacyCatalogPersona(existing: AgentDoc, current: AgentDoc): AgentDoc {
558
562
  if (existing.id !== LEGACY_CRON_PERSONA.id || existing.source !== "catalog") return existing;
559
563
 
560
- let systemPrompt = existing.system_prompt;
564
+ // `system_prompt` es nullable en AgentDoc y esto corre en cada arranque: una
565
+ // fila sin prompt hacía estallar el seed entero con un TypeError, no un error
566
+ // de datos. Sin prompt no hay nada que migrar, así que se devuelve tal cual.
567
+ if (existing.system_prompt === null) return existing;
568
+
569
+ let systemPrompt: string = existing.system_prompt;
570
+ const currentPrompt = current.system_prompt ?? "";
561
571
  const hasLegacyStockPrompt = systemPrompt.includes(LEGACY_CRON_PERSONA.role)
562
572
  && systemPrompt.includes(LEGACY_CRON_PERSONA.receives);
563
573
  if (hasLegacyStockPrompt) {
564
574
  systemPrompt = systemPrompt
565
- .replace(LEGACY_CRON_PERSONA.role, current.system_prompt.match(/# ROL\n([^\n]+)/)?.[1] ?? LEGACY_CRON_PERSONA.role)
566
- .replace(LEGACY_CRON_PERSONA.receives, current.system_prompt.match(/# QUÉ RECIBES\n([^\n]+)/)?.[1] ?? LEGACY_CRON_PERSONA.receives);
575
+ .replace(LEGACY_CRON_PERSONA.role, currentPrompt.match(/# ROL\n([^\n]+)/)?.[1] ?? LEGACY_CRON_PERSONA.role)
576
+ .replace(LEGACY_CRON_PERSONA.receives, currentPrompt.match(/# QUÉ RECIBES\n([^\n]+)/)?.[1] ?? LEGACY_CRON_PERSONA.receives);
567
577
  if (!systemPrompt.includes(LEGACY_CRON_PERSONA.calendarProhibition)) {
568
578
  systemPrompt = systemPrompt.replace(
569
579
  "- No hablás con el usuario",
@@ -621,7 +631,32 @@ async function pruneRetired(): Promise<void> {
621
631
  if (removed > 0) log.info(`[seed] 🗑️ Removed ${removed} retired capability row(s)`);
622
632
  }
623
633
 
624
- async function reseedToolsAndSkills(): Promise<void> {
634
+ /**
635
+ * Qué tools y skills deben nacer activas, según los especialistas elegidos.
636
+ *
637
+ * `null` = todas, que es el modo `"all"`. En cualquier otro modo la elección
638
+ * gobierna también las capacidades: un arranque `"none"` con las 62 tools
639
+ * activas sería contradecir el punto entero —ningún especialista instalado y
640
+ * todas las herramientas encendidas—, y el usuario terminaría apagándolas a
641
+ * mano una por una.
642
+ *
643
+ * Sólo afecta a las filas **nuevas**: `active` de una fila existente es la
644
+ * elección del usuario y sobrevive a todos los arranques.
645
+ */
646
+ function capacidadesIniciales(
647
+ especialistas: SpecialistSeedMode,
648
+ ): { tools: Set<string>; skills: Set<string> } | null {
649
+ if (especialistas === "all") return null
650
+ const elegidos = especialistas === "none" ? [] : especialistas
651
+ const { toolPatterns, skills } = requiredCapabilitiesFor(elegidos)
652
+ return {
653
+ tools: new Set([...MINIMAL_TOOLS, ...expandToolAllowlist(toolPatterns)]),
654
+ skills: new Set(skills),
655
+ }
656
+ }
657
+
658
+ async function reseedToolsAndSkills(especialistas: SpecialistSeedMode = "all"): Promise<void> {
659
+ const iniciales = capacidadesIniciales(especialistas);
625
660
  // Seeding only writes the rows; the search index is rebuilt from them at
626
661
  // startup by the sync pass in gateway/initializer.ts.
627
662
 
@@ -630,9 +665,17 @@ async function reseedToolsAndSkills(): Promise<void> {
630
665
  const now = Date.now();
631
666
  let toolCount = 0;
632
667
  for (const tool of SEED_DATA.tools) {
668
+ // La descripción y la categoría vienen del código y se sobrescriben —son la
669
+ // fuente de verdad—, pero `active` NO: es la elección del usuario sobre qué
670
+ // capacidades quiere en su colmena (services/setup.ts). Pisarla en cada
671
+ // arranque haría que el seed selectivo durara hasta el próximo reinicio.
672
+ const existing = await toolsCol.get(tool.id);
633
673
  await toolsCol.put(tool.id, {
634
674
  id: tool.id, name: tool.name, description: tool.description, category: tool.category,
635
- enabled: true, active: true, created_at: now, updated_at: now,
675
+ enabled: existing?.doc.enabled ?? true,
676
+ active: existing?.doc.active ?? (iniciales ? iniciales.tools.has(tool.name) : true),
677
+ created_at: existing?.doc.created_at ?? now,
678
+ updated_at: now,
636
679
  });
637
680
  toolCount++;
638
681
  }
@@ -646,6 +689,9 @@ async function reseedToolsAndSkills(): Promise<void> {
646
689
 
647
690
  let skillCount = 0;
648
691
  for (const s of realSkills) {
692
+ // Igual que con las tools: el contenido viene del archivo, pero `active` es
693
+ // del usuario y sobrevive al reseed.
694
+ const existingSkill = await skillsCol.get(s.name);
649
695
  await skillsCol.put(s.name, {
650
696
  id: s.name,
651
697
  name: s.name,
@@ -661,8 +707,8 @@ async function reseedToolsAndSkills(): Promise<void> {
661
707
  preferred_agents: JSON.stringify(s.preferred_agents || []),
662
708
  body: s.content || "",
663
709
  version_num: parseInt(String(s.version || "0.0.1").split(".")[0]) || 1,
664
- active: true,
665
- created_at: now,
710
+ active: existingSkill?.doc.active ?? (iniciales ? iniciales.skills.has(s.name) : true),
711
+ created_at: existingSkill?.doc.created_at ?? now,
666
712
  updated_at: now,
667
713
  });
668
714
  skillCount++;
@@ -672,10 +718,30 @@ async function reseedToolsAndSkills(): Promise<void> {
672
718
  await pruneRetired();
673
719
  }
674
720
 
675
- export async function seedAllData(): Promise<void> {
721
+ /**
722
+ * Qué especialistas del catálogo crear en el arranque.
723
+ *
724
+ * - `"all"` — los 8. Comportamiento histórico y default.
725
+ * - `"none"` — ninguno. La colmena arranca con el coordinador y nada más; los
726
+ * especialistas se crean cuando el usuario arma el enjambre que los pide.
727
+ * Es el modo para un producto donde cada quien elige su equipo.
728
+ * - lista — sólo esos.
729
+ *
730
+ * **Nunca borra.** Los que ya existen en la base se siguen reconciliando en
731
+ * cada arranque, elija lo que elija: una instalación que ya tiene sus ocho
732
+ * agentes no los pierde por cambiar esta opción.
733
+ */
734
+ export type SpecialistSeedMode = "all" | "none" | string[]
735
+
736
+ export interface SeedOptions {
737
+ specialists?: SpecialistSeedMode
738
+ }
739
+
740
+ export async function seedAllData(opts?: SeedOptions): Promise<void> {
676
741
  log.info("[seed] 🌱 Iniciando seed de datos predeterminados...")
742
+ const especialistas = opts?.specialists ?? "all"
677
743
 
678
- await reseedToolsAndSkills();
744
+ await reseedToolsAndSkills(especialistas);
679
745
 
680
746
  try {
681
747
  const now = Date.now();
@@ -830,9 +896,22 @@ export async function seedAllData(): Promise<void> {
830
896
  // factory values from older releases are migrated in place.
831
897
  let catalogAgentCount = 0;
832
898
  let repairedCatalogSkills = 0;
899
+ const quiereEspecialista = (id: string) =>
900
+ especialistas === "all" ? true
901
+ : especialistas === "none" ? false
902
+ : especialistas.includes(id);
903
+
833
904
  for (const catalogAgent of createSeedCatalogAgents()) {
834
- await putIfAbsent(agentsCol, catalogAgent.id, catalogAgent);
835
- const existing = await agentsCol.get(catalogAgent.id);
905
+ // Sembrar sólo los elegidos, pero seguir reconciliando los que ya
906
+ // existan: la elección gobierna qué se CREA, nunca qué se conserva. Una
907
+ // base que ya trae los ocho no los pierde por arrancar con "none", y
908
+ // tampoco se queda sin las migraciones de abajo.
909
+ let existing = await agentsCol.get(catalogAgent.id);
910
+ if (!existing) {
911
+ if (!quiereEspecialista(catalogAgent.id)) continue;
912
+ await putIfAbsent(agentsCol, catalogAgent.id, catalogAgent);
913
+ existing = await agentsCol.get(catalogAgent.id);
914
+ }
836
915
  if (!existing || existing.doc.source !== "catalog") {
837
916
  catalogAgentCount++;
838
917
  continue;
@@ -925,12 +1004,17 @@ export async function seedAllData(): Promise<void> {
925
1004
  const existing = byRule.get(rule.rule);
926
1005
  if (existing) {
927
1006
  await playbookCol.put(existing.id, {
928
- ...existing.doc, category: rule.category, applicable_to: rule.applicable_to, active: true, updated_at: now,
1007
+ // `user_id: ""` va explícito y no heredado de `existing.doc`: en una
1008
+ // base anterior a este campo las filas sembradas no lo tienen, y son
1009
+ // justamente las que deben ser globales.
1010
+ ...existing.doc, category: rule.category, applicable_to: rule.applicable_to, user_id: "", active: true, updated_at: now,
929
1011
  }, { expectedVersion: existing.version });
930
1012
  } else {
931
1013
  const id = await nextId("playbook");
932
1014
  await playbookCol.put(id, {
933
1015
  id, rule: rule.rule, category: rule.category, applicable_to: rule.applicable_to,
1016
+ // Conocimiento del producto, no aprendido de nadie: aplica a todos.
1017
+ user_id: "",
934
1018
  helpful_count: 1, harmful_count: 0, active: true,
935
1019
  source_reflection_id: toIndexable(null), created_at: now, updated_at: now,
936
1020
  });
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * Hive Scheduler - Type Definitions
3
3
  *
4
- * Type interfaces for the Croner-based scheduling system.
4
+ * Type interfaces for the scheduling system.
5
5
  * All names use "CronJob" terminology (formerly ScheduledTask).
6
6
  */
7
7
 
8
8
  import type { Database } from "bun:sqlite";
9
- import type { Cron } from "croner";
9
+ import type { Cron } from "../scheduler/cron/index.ts";
10
10
 
11
11
  /**
12
12
  * Task type: recurring uses cron expression, one_shot uses fire_at
@@ -141,24 +141,9 @@ export interface CronJobExecutionResult {
141
141
  }
142
142
 
143
143
  /**
144
- * Internal job wrapper holding Croner instance and metadata
144
+ * Internal job wrapper holding the scheduled job and its metadata
145
145
  */
146
146
  export interface CronJobEntry {
147
147
  job: CronJob;
148
148
  cron: Cron;
149
149
  }
150
-
151
- /**
152
- * Options for Croner job creation
153
- */
154
- export interface CronerOptions {
155
- timezone: string;
156
- protect: boolean;
157
- catch: boolean | ((error: Error) => void);
158
- name: string;
159
- maxRuns?: number;
160
- interval?: number;
161
- startAt?: string;
162
- stopAt?: string;
163
- domAndDow?: boolean;
164
- }
@@ -0,0 +1,21 @@
1
+ // Auto-generado — NO EDITAR.
2
+ //
3
+ // En este paquete el valor es SIEMPRE null, y así debe quedarse: el SDK se
4
+ // publica como fuente, no como ejecutable standalone, así que el worker se
5
+ // resuelve desde disco (tool-worker.ts al lado de este archivo, o
6
+ // dist/tool-worker.js junto al bundle).
7
+ //
8
+ // El archivo existe porque el gateway de hive sí compila un binario, y ahí su
9
+ // `scripts/build-gateway.ts` reescribe este stub antes de compilar con:
10
+ //
11
+ // import workerFile from "./tool-worker.generated.js" with { type: "file" }
12
+ // export const embeddedToolWorkerPath: string | null = workerFile
13
+ //
14
+ // Ese `with { type: "file" }` es lo que mete el bundle del worker dentro del
15
+ // ejecutable, porque `new Worker(new URL("./tool-worker.ts", import.meta.url))`
16
+ // NO se embebe solo: el path se resuelve en runtime y el bundler no lo ve.
17
+ // Sin eso la app de escritorio se instalaba sin worker y cualquier turno con
18
+ // más de una tool call moría con "Tool worker entry not found" (v1.0.3 y
19
+ // anteriores). Mantener el símbolo acá deja que `tool-runtime/index.ts` sea el
20
+ // mismo archivo en los dos repos.
21
+ export const embeddedToolWorkerPath: string | null = null