@johpaz/hive-sdk 0.1.6 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. package/CHANGELOG.md +441 -0
  2. package/README.md +21 -3
  3. package/package.json +27 -5
  4. package/packages/core/src/agent/acceptance-checks.ts +9 -9
  5. package/packages/core/src/agent/agent-catalog.ts +82 -25
  6. package/packages/core/src/agent/agent-loop.ts +115 -26
  7. package/packages/core/src/agent/capability-search.ts +2 -2
  8. package/packages/core/src/agent/catalog-selector.ts +4 -4
  9. package/packages/core/src/agent/compaction.ts +30 -10
  10. package/packages/core/src/agent/context-compiler.ts +63 -36
  11. package/packages/core/src/agent/conversation-store.ts +167 -9
  12. package/packages/core/src/agent/curator.ts +16 -7
  13. package/packages/core/src/agent/delegation-runtime.ts +5 -5
  14. package/packages/core/src/agent/goal-runner.ts +9 -9
  15. package/packages/core/src/agent/index.ts +1 -0
  16. package/packages/core/src/agent/llm-client.ts +98 -37
  17. package/packages/core/src/agent/llm-providers/anthropic.ts +4 -4
  18. package/packages/core/src/agent/llm-providers/deepseek.ts +1 -1
  19. package/packages/core/src/agent/llm-providers/gemini.ts +4 -4
  20. package/packages/core/src/agent/llm-providers/groq.ts +1 -1
  21. package/packages/core/src/agent/llm-providers/hiveagents.ts +3 -3
  22. package/packages/core/src/agent/llm-providers/interface.ts +2 -2
  23. package/packages/core/src/agent/llm-providers/kimi.ts +1 -1
  24. package/packages/core/src/agent/llm-providers/minimax.ts +1 -1
  25. package/packages/core/src/agent/llm-providers/mistral.ts +1 -1
  26. package/packages/core/src/agent/llm-providers/modelscope.ts +1 -1
  27. package/packages/core/src/agent/llm-providers/nvidia.ts +40 -1
  28. package/packages/core/src/agent/llm-providers/ollama.ts +4 -4
  29. package/packages/core/src/agent/llm-providers/openai-compat-base.ts +47 -7
  30. package/packages/core/src/agent/llm-providers/openai.ts +1 -1
  31. package/packages/core/src/agent/llm-providers/opencode-go.ts +1 -1
  32. package/packages/core/src/agent/llm-providers/openrouter.ts +1 -1
  33. package/packages/core/src/agent/llm-providers/qwen.ts +1 -1
  34. package/packages/core/src/agent/llm-providers/z-ai.ts +1 -1
  35. package/packages/core/src/agent/mcp-result-normalizer.ts +192 -0
  36. package/packages/core/src/agent/playbook-selector.ts +22 -7
  37. package/packages/core/src/agent/prompt-builder.ts +5 -5
  38. package/packages/core/src/agent/proof-packet.ts +5 -5
  39. package/packages/core/src/agent/providers/index.ts +39 -4
  40. package/packages/core/src/agent/realtime-providers/gemini-live.ts +238 -0
  41. package/packages/core/src/agent/realtime-providers/index.ts +29 -0
  42. package/packages/core/src/agent/realtime-providers/interface.ts +108 -0
  43. package/packages/core/src/agent/reflector.ts +38 -15
  44. package/packages/core/src/agent/run-store.ts +8 -8
  45. package/packages/core/src/agent/service.ts +10 -10
  46. package/packages/core/src/agent/skill-selector.ts +6 -6
  47. package/packages/core/src/agent/thread-id.ts +71 -0
  48. package/packages/core/src/agent/thread-store.ts +293 -0
  49. package/packages/core/src/agent/tool-selector.ts +9 -5
  50. package/packages/core/src/agent/tracer.ts +5 -5
  51. package/packages/core/src/api/createAgent.ts +67 -2
  52. package/packages/core/src/artifacts/index.ts +15 -0
  53. package/packages/core/src/artifacts/store.ts +161 -5
  54. package/packages/core/src/canvas/emitter.ts +2 -2
  55. package/packages/core/src/canvas/index.ts +9 -0
  56. package/packages/core/src/channels/telegram.ts +1 -1
  57. package/packages/core/src/channels/webchat.ts +1 -1
  58. package/packages/core/src/config/loader.ts +14 -5
  59. package/packages/core/src/ethics/EthicsGuard.ts +7 -1
  60. package/packages/core/src/events/agent-bus.ts +3 -3
  61. package/packages/core/src/events/channel-narration.ts +3 -3
  62. package/packages/core/src/events/event-bus.ts +1 -1
  63. package/packages/core/src/events/index.ts +18 -0
  64. package/packages/core/src/events/narration.ts +3 -3
  65. package/packages/core/src/events/tool-narration.ts +4 -0
  66. package/packages/core/src/gateway/channel-notify.ts +103 -6
  67. package/packages/core/src/gateway/delegation-groups.ts +4 -4
  68. package/packages/core/src/gateway/durable-queue.ts +18 -6
  69. package/packages/core/src/gateway/index.ts +3 -0
  70. package/packages/core/src/gateway/job-store.ts +11 -5
  71. package/packages/core/src/gateway/notification-inbox.ts +2 -2
  72. package/packages/core/src/gateway/server.ts +2 -2
  73. package/packages/core/src/harness/executors.ts +493 -0
  74. package/packages/core/src/harness/index.ts +12 -2
  75. package/packages/core/src/hooks/index.ts +203 -0
  76. package/packages/core/src/images/index.ts +161 -0
  77. package/packages/core/src/index.ts +1 -0
  78. package/packages/core/src/mcp/MCPClient.ts +3 -3
  79. package/packages/core/src/mcp/hot-reload.ts +5 -5
  80. package/packages/core/src/mcp/tool-sync.ts +5 -5
  81. package/packages/core/src/mcp/transports/index.ts +2 -2
  82. package/packages/core/src/mcp/transports/sse.ts +1 -1
  83. package/packages/core/src/models/index.ts +36 -0
  84. package/packages/core/src/multimodal/index.ts +2 -2
  85. package/packages/core/src/multimodal/vision-service.ts +51 -19
  86. package/packages/core/src/plugins/loader.ts +4 -1
  87. package/packages/core/src/resilience/circuit-breaker.ts +16 -5
  88. package/packages/core/src/resilience/index.ts +13 -0
  89. package/packages/core/src/resilience/retry.ts +1 -1
  90. package/packages/core/src/scheduler/CronScheduler.ts +54 -27
  91. package/packages/core/src/scheduler/cron/expression.ts +165 -0
  92. package/packages/core/src/scheduler/cron/index.ts +10 -0
  93. package/packages/core/src/scheduler/cron/job.ts +339 -0
  94. package/packages/core/src/scheduler/cron/next-run.ts +121 -0
  95. package/packages/core/src/scheduler/cron/zoned-time.ts +138 -0
  96. package/packages/core/src/scheduler/index.ts +21 -3
  97. package/packages/core/src/scheduler/integration.ts +25 -14
  98. package/packages/core/src/scheduler/types.ts +3 -18
  99. package/packages/core/src/services/agents.ts +268 -0
  100. package/packages/core/src/services/cron.ts +257 -0
  101. package/packages/core/src/services/endpoints.ts +289 -0
  102. package/packages/core/src/services/ethics.ts +107 -0
  103. package/packages/core/src/services/images.ts +212 -0
  104. package/packages/core/src/services/index.ts +112 -0
  105. package/packages/core/src/services/mcp.ts +201 -0
  106. package/packages/core/src/services/memory.ts +133 -0
  107. package/packages/core/src/services/models.ts +179 -0
  108. package/packages/core/src/services/providers.ts +152 -0
  109. package/packages/core/src/services/setup.ts +222 -0
  110. package/packages/core/src/services/skills.ts +241 -0
  111. package/packages/core/src/services/swarms.ts +307 -0
  112. package/packages/core/src/services/tools.ts +106 -0
  113. package/packages/core/src/sessions/index.ts +268 -0
  114. package/packages/core/src/sessions/resolve.ts +108 -0
  115. package/packages/core/src/skills/SkillLoader.ts +8 -1
  116. package/packages/core/src/skills/bundled/artifacts/artifact_reader/SKILL.md +105 -0
  117. package/packages/core/src/skills/bundled/cron_manager/SKILL.md +21 -11
  118. package/packages/core/src/skills/bundled/images/image_editor/SKILL.md +120 -0
  119. package/packages/core/src/skills/bundled/web/browser_automate/SKILL.md +12 -3
  120. package/packages/core/src/skills/bundled/web/browser_scrape/SKILL.md +22 -7
  121. package/packages/core/src/skills/bundled-data.generated.ts +110 -12
  122. package/packages/core/src/storage/bootstrap.ts +107 -12
  123. package/packages/core/src/storage/causal-events.ts +1 -1
  124. package/packages/core/src/storage/collections.ts +138 -2
  125. package/packages/core/src/storage/crypto.ts +35 -4
  126. package/packages/core/src/storage/hive.ts +1 -1
  127. package/packages/core/src/storage/hivedb.ts +10 -1
  128. package/packages/core/src/storage/index.ts +2 -1
  129. package/packages/core/src/storage/onboarding.ts +61 -45
  130. package/packages/core/src/storage/reconcile.ts +11 -6
  131. package/packages/core/src/storage/seed.ts +191 -23
  132. package/packages/core/src/storage/usage.ts +3 -3
  133. package/packages/core/src/swarm/AgentExecutor.ts +2 -2
  134. package/packages/core/src/swarm/Coordinator.ts +8 -8
  135. package/packages/core/src/swarm/EventBridge.ts +2 -2
  136. package/packages/core/src/swarm/RoleSwarm.ts +234 -0
  137. package/packages/core/src/swarm/TaskGraph.ts +2 -2
  138. package/packages/core/src/swarm/index.ts +7 -0
  139. package/packages/core/src/swarm/presets/HiveLearnPreset.ts +2 -2
  140. package/packages/core/src/swarm/presets/ResearchPreset.ts +2 -2
  141. package/packages/core/src/swarm/strategies/ParallelStrategy.ts +1 -1
  142. package/packages/core/src/swarm/strategies/PriorityStrategy.ts +3 -3
  143. package/packages/core/src/swarm/types.ts +3 -18
  144. package/packages/core/src/tool-runtime/embedded-worker.generated.ts +21 -0
  145. package/packages/core/src/tool-runtime/index.ts +129 -14
  146. package/packages/core/src/tools/ToolExecutor.ts +7 -3
  147. package/packages/core/src/tools/agents/index.ts +18 -60
  148. package/packages/core/src/tools/cli/index.ts +55 -0
  149. package/packages/core/src/tools/core/index.ts +52 -4
  150. package/packages/core/src/tools/cron/index.ts +8 -8
  151. package/packages/core/src/tools/images/index.ts +130 -0
  152. package/packages/core/src/tools/index.ts +14 -1
  153. package/packages/core/src/tools/office/office-escribir-xlsx.ts +2 -1
  154. package/packages/core/src/tools/office/office-leer-xlsx.ts +2 -1
  155. package/packages/core/src/tools/office/xlsx-loader.ts +19 -0
  156. package/packages/core/src/tools/web/artifact-inspect.ts +2 -2
  157. package/packages/core/src/tools/web/artifact-read.ts +162 -0
  158. package/packages/core/src/tools/web/browser-backend.ts +141 -44
  159. package/packages/core/src/tools/web/browser-click.ts +2 -2
  160. package/packages/core/src/tools/web/browser-extract.ts +2 -2
  161. package/packages/core/src/tools/web/browser-navigate.ts +2 -2
  162. package/packages/core/src/tools/web/browser-screenshot.ts +12 -5
  163. package/packages/core/src/tools/web/browser-script.ts +2 -2
  164. package/packages/core/src/tools/web/browser-service.ts +63 -384
  165. package/packages/core/src/tools/web/browser-session.ts +125 -0
  166. package/packages/core/src/tools/web/browser-type.ts +2 -2
  167. package/packages/core/src/tools/web/browser-wait.ts +2 -2
  168. package/packages/core/src/tools/web/computer-use.ts +553 -0
  169. package/packages/core/src/tools/web/index.ts +8 -1
  170. package/packages/core/src/tools/web/webview-backend.ts +460 -21
  171. package/packages/core/src/utils/index.ts +1 -0
  172. package/packages/core/src/utils/logger.ts +12 -4
  173. package/packages/core/src/utils/redact-binary.ts +17 -0
  174. package/packages/core/src/utils/toon.ts +1 -1
  175. package/packages/core/src/voice/index.ts +6 -6
  176. package/bun.lock +0 -859
  177. package/bunfig.toml +0 -9
  178. package/docs/API-AGENTS.md +0 -367
  179. package/docs/API-CONTEXT-COMPILER.md +0 -249
  180. package/docs/API-DAG-SCHEDULER.md +0 -273
  181. package/docs/API-TOOLS-SKILLS-CHANNELS.md +0 -446
  182. package/docs/API-WORKERS-EVENTS.md +0 -299
  183. package/docs/HIVE-HARNESS.md +0 -113
  184. package/docs/INDEX.md +0 -190
  185. package/docs/TEMPLATE-HIVE-APP.md +0 -360
  186. package/packages/cli/package.json +0 -17
  187. package/packages/cli/src/commands/create-app.test.ts +0 -180
  188. package/packages/core/package.json +0 -70
  189. package/packages/core/src/api/createAgent.test.ts +0 -160
  190. package/packages/core/src/canvas/canvas.test.ts +0 -36
  191. package/packages/core/src/channels/channels.test.ts +0 -18
  192. package/packages/core/src/ethics/EthicsGuard.test.ts +0 -108
  193. package/packages/core/src/gateway/gateway.test.ts +0 -38
  194. package/packages/core/src/memory/Scratchpad.test.ts +0 -68
  195. package/packages/core/src/scheduler/scheduler.test.ts +0 -15
  196. package/packages/core/src/skills/skills.test.ts +0 -62
  197. package/packages/core/src/swarm/swarm.test.ts +0 -24
  198. package/packages/core/src/tool-runtime/tool-runtime.test.ts +0 -99
  199. package/packages/core/src/tools/ToolRegistry.test.ts +0 -98
  200. package/packages/core/src/tools/api/api-request.test.ts +0 -164
  201. package/packages/core/src/tools/web/browser-service.test.ts +0 -83
  202. package/packages/core/src/workers/workers.test.ts +0 -41
  203. package/scripts/bump-version.ts +0 -248
  204. package/scripts/generate-skill-bundle.ts +0 -108
  205. package/test/acceptance-checks.test.ts +0 -403
  206. package/test/agent-loop-terminal-synthesis.test.ts +0 -32
  207. package/test/browser-backend.test.ts +0 -308
  208. package/test/catalog-agents-stay-enabled.test.ts +0 -117
  209. package/test/causal-events.test.ts +0 -117
  210. package/test/compaction.test.ts +0 -105
  211. package/test/context-compiler.test.ts +0 -269
  212. package/test/curator.test.ts +0 -130
  213. package/test/durable-queue.test.ts +0 -114
  214. package/test/harness-barrel.test.ts +0 -64
  215. package/test/hive-helpers.test.ts +0 -130
  216. package/test/hivedb-search.test.ts +0 -189
  217. package/test/internal-turns.test.ts +0 -166
  218. package/test/job-idempotency.test.ts +0 -68
  219. package/test/job-retry-backoff.test.ts +0 -184
  220. package/test/job-store.test.ts +0 -381
  221. package/test/llm-retry.test.ts +0 -97
  222. package/test/memory-perf.test.ts +0 -774
  223. package/test/minimal-loadout.test.ts +0 -78
  224. package/test/model-catalog.test.ts +0 -105
  225. package/test/preload.ts +0 -12
  226. package/test/reflector.test.ts +0 -320
  227. package/test/retention-cap.test.ts +0 -91
  228. package/test/retired-capabilities-pruned.test.ts +0 -192
  229. package/test/run-store.test.ts +0 -355
  230. package/test/scratchpad.test.ts +0 -74
  231. package/test/secrets-durability.test.ts +0 -119
  232. package/test/seed-model-reseed.test.ts +0 -155
  233. package/test/setup-agent-seed.test.ts +0 -264
  234. package/test/tool-inventory.test.ts +0 -65
  235. package/test/tool-runtime.test.ts +0 -258
  236. package/test/tool-selector-runtime-tools.test.ts +0 -117
  237. package/test/toon.test.ts +0 -429
  238. package/tsconfig.json +0 -42
@@ -0,0 +1,268 @@
1
+ /**
2
+ * Sessions — la conversación de un usuario, como una sola cosa.
3
+ *
4
+ * Hasta acá "sesión" estaba repartida en cuatro capas que nadie unía:
5
+ *
6
+ * - `agent/thread-store.ts` — identidad y catálogo (`conversationThreads`)
7
+ * - `agent/conversation-store.ts`— los mensajes (`conversations`, por prefijo)
8
+ * - `agent/run-store.ts` — la ejecución: checkpoint, lease, resume
9
+ * - `state/store.ts` — un `Map` en memoria que muere con el proceso
10
+ *
11
+ * El resultado eran dos identificadores para lo mismo —`thread_id` para la
12
+ * conversación y `run_id` para la ejecución— y ninguna forma de preguntar "qué
13
+ * sesiones tiene este usuario" sin escanear mensajes.
14
+ *
15
+ * Este módulo **no agrega una tercera persistencia**: `Session` es una vista
16
+ * compuesta sobre las colecciones que ya existen. `Session.id` ES el `threadId`.
17
+ * Agregar una colección propia habría recreado exactamente la duplicación que
18
+ * este módulo viene a cerrar.
19
+ *
20
+ * `state/store.ts` queda para métricas efímeras; no es la sesión.
21
+ */
22
+
23
+ import type { ContentPart } from "../multimodal/types.ts"
24
+ import type { AgentRunDoc, ConversationThreadDoc } from "../storage/collections.ts"
25
+ import {
26
+ addMessage,
27
+ getHistory,
28
+ type StoredMessage,
29
+ } from "../agent/conversation-store.ts"
30
+ import {
31
+ archiveThread,
32
+ createWebConversation,
33
+ deleteThread,
34
+ ensureThread,
35
+ getThread,
36
+ listThreads,
37
+ mostRecentWebThread,
38
+ renameThread,
39
+ threadForChannel,
40
+ unarchiveThread,
41
+ } from "../agent/thread-store.ts"
42
+ import {
43
+ deserializeCheckpoint,
44
+ findRunsByThread,
45
+ type RunCheckpointState,
46
+ } from "../agent/run-store.ts"
47
+
48
+ export * from "../agent/thread-id.ts"
49
+ export * from "./resolve.ts"
50
+
51
+ /** El estado de ejecución más reciente del hilo, si alguna vez corrió. */
52
+ export interface SessionRun {
53
+ runId: string
54
+ agentId: string
55
+ status: AgentRunDoc["status"]
56
+ kind: AgentRunDoc["kind"]
57
+ updatedAt: number
58
+ /** true cuando quedó a medias y `resumeSession` puede retomarla. */
59
+ resumable: boolean
60
+ }
61
+
62
+ /**
63
+ * Una sesión: la identidad del hilo más, opcionalmente, su última ejecución.
64
+ * Es una vista, no una fila: se compone de `conversationThreads` + `agentRuns`.
65
+ */
66
+ export interface Session {
67
+ /** ES el threadId (`${user}/${canal}/${peer}`). */
68
+ id: string
69
+ userId: string
70
+ channel: string
71
+ peerId: string
72
+ peerKind: "direct" | "group"
73
+ title: string | null
74
+ archived: boolean
75
+ createdAt: number
76
+ lastMessageAt: number
77
+ messageCount: number
78
+ lastRun?: SessionRun
79
+ }
80
+
81
+ /** Una ejecución interrumpida sigue siendo retomable; una terminada no. */
82
+ const RESUMABLE_STATUSES: ReadonlySet<AgentRunDoc["status"]> = new Set([
83
+ "interrupted",
84
+ "running",
85
+ ])
86
+
87
+ function toSessionRun(run: AgentRunDoc): SessionRun {
88
+ return {
89
+ runId: run.id,
90
+ agentId: run.agent_id,
91
+ status: run.status,
92
+ kind: run.kind,
93
+ updatedAt: run.updated_at ?? run.created_at,
94
+ resumable: RESUMABLE_STATUSES.has(run.status),
95
+ }
96
+ }
97
+
98
+ function toSession(doc: ConversationThreadDoc, lastRun?: SessionRun): Session {
99
+ return {
100
+ id: doc.id,
101
+ userId: doc.user_id,
102
+ channel: doc.channel,
103
+ peerId: doc.peer_id,
104
+ peerKind: doc.peer_kind,
105
+ title: doc.title,
106
+ archived: doc.archived,
107
+ createdAt: doc.created_at,
108
+ lastMessageAt: doc.last_message_at,
109
+ messageCount: doc.message_count,
110
+ ...(lastRun ? { lastRun } : {}),
111
+ }
112
+ }
113
+
114
+ /** La ejecución más reciente del hilo, o undefined si nunca corrió. */
115
+ async function latestRun(threadId: string): Promise<SessionRun | undefined> {
116
+ const runs = await findRunsByThread(threadId)
117
+ if (runs.length === 0) return undefined
118
+ const newest = runs.reduce((a, b) =>
119
+ (b.updated_at ?? b.created_at) > (a.updated_at ?? a.created_at) ? b : a,
120
+ )
121
+ return toSessionRun(newest)
122
+ }
123
+
124
+ export interface CreateSessionInput {
125
+ userId: string
126
+ channel: string
127
+ peerId: string
128
+ peerKind?: "direct" | "group"
129
+ }
130
+
131
+ /**
132
+ * Abre la sesión de un usuario en un canal, o devuelve la que ya existía.
133
+ * Idempotente: se puede llamar en cada turno.
134
+ */
135
+ export async function createSession(input: CreateSessionInput): Promise<Session> {
136
+ const threadId = await ensureThread(input)
137
+ const doc = await getThread(threadId)
138
+ if (!doc) throw new Error(`No se pudo abrir la sesión ${threadId}`)
139
+ return toSession(doc)
140
+ }
141
+
142
+ /** Una conversación nueva de la web, con su propio id (no reusa la anterior). */
143
+ export async function createWebSession(userId: string, title?: string): Promise<Session> {
144
+ return toSession(await createWebConversation(userId, title))
145
+ }
146
+
147
+ export async function getSession(sessionId: string): Promise<Session | null> {
148
+ const doc = await getThread(sessionId)
149
+ if (!doc) return null
150
+ return toSession(doc, await latestRun(sessionId))
151
+ }
152
+
153
+ export interface ListSessionsOptions {
154
+ channel?: string
155
+ includeArchived?: boolean
156
+ /**
157
+ * Adjunta la última ejecución de cada sesión. Cuesta una consulta por sesión,
158
+ * así que está apagado por defecto: la lista de conversaciones no lo necesita.
159
+ */
160
+ withRuns?: boolean
161
+ }
162
+
163
+ /**
164
+ * Las sesiones de un usuario, de la más reciente a la más vieja.
165
+ *
166
+ * Es la consulta que antes no existía: el estado en memoria se perdía en cada
167
+ * reinicio y los mensajes sólo se podían leer por prefijo de un hilo conocido.
168
+ */
169
+ export async function listSessions(
170
+ userId: string,
171
+ opts?: ListSessionsOptions,
172
+ ): Promise<Session[]> {
173
+ const docs = await listThreads(userId, {
174
+ channel: opts?.channel,
175
+ includeArchived: opts?.includeArchived,
176
+ })
177
+ if (!opts?.withRuns) return docs.map((d) => toSession(d))
178
+ return Promise.all(docs.map(async (d) => toSession(d, await latestRun(d.id))))
179
+ }
180
+
181
+ /** La sesión de webchat en la que el usuario seguiría escribiendo. */
182
+ export async function mostRecentWebSession(userId: string): Promise<Session | null> {
183
+ const doc = await mostRecentWebThread(userId)
184
+ return doc ? toSession(doc) : null
185
+ }
186
+
187
+ /**
188
+ * La sesión por la que hablarle a alguien en un canal cuando no venimos de un
189
+ * mensaje suyo (un aviso de tarea programada, por ejemplo). null si no hay.
190
+ */
191
+ export async function sessionForChannel(
192
+ userId: string,
193
+ channel: string,
194
+ ): Promise<string | null> {
195
+ return threadForChannel(userId, channel)
196
+ }
197
+
198
+ /**
199
+ * Agrega un mensaje y mantiene al día el catálogo (título, orden, contador):
200
+ * `addMessage` ya llama a `touchThread`, así que no hay que hacerlo acá.
201
+ *
202
+ * Ojo: esa actualización del catálogo es deliberadamente *fire-and-forget* —
203
+ * persistir el mensaje nunca se bloquea por el contador. En la práctica eso
204
+ * significa que `messageCount` y `title` son de consistencia eventual: leer la
205
+ * sesión inmediatamente después de escribir puede devolver el valor anterior.
206
+ * El historial (`getSessionHistory`) sí es consistente al instante.
207
+ */
208
+ export async function appendMessage(
209
+ sessionId: string,
210
+ role: StoredMessage["role"],
211
+ content: string | ContentPart[],
212
+ opts?: Parameters<typeof addMessage>[3],
213
+ ): Promise<number> {
214
+ return addMessage(sessionId, role, content, opts)
215
+ }
216
+
217
+ export async function getSessionHistory(
218
+ sessionId: string,
219
+ limit?: number,
220
+ ): Promise<StoredMessage[]> {
221
+ return getHistory(sessionId, limit)
222
+ }
223
+
224
+ export interface ResumableSession {
225
+ run: SessionRun
226
+ checkpoint: RunCheckpointState
227
+ }
228
+
229
+ /**
230
+ * El trabajo a medias de una sesión, listo para retomar.
231
+ *
232
+ * Devuelve null cuando no hay nada que retomar — que es el caso normal. Un run
233
+ * `running` sin checkpoint tampoco sirve: murió antes de guardar estado.
234
+ */
235
+ export async function resumeSession(sessionId: string): Promise<ResumableSession | null> {
236
+ const runs = await findRunsByThread(sessionId)
237
+ const candidates = runs
238
+ .filter((r) => RESUMABLE_STATUSES.has(r.status))
239
+ .sort((a, b) => (b.updated_at ?? b.created_at) - (a.updated_at ?? a.created_at))
240
+
241
+ for (const run of candidates) {
242
+ const checkpoint = deserializeCheckpoint(run)
243
+ if (checkpoint) return { run: toSessionRun(run), checkpoint }
244
+ }
245
+ return null
246
+ }
247
+
248
+ export async function renameSession(sessionId: string, title: string): Promise<void> {
249
+ return renameThread(sessionId, title)
250
+ }
251
+
252
+ /**
253
+ * Cierra la sesión sin perder nada: sale de la lista pero el historial queda.
254
+ * Para borrarla de verdad, `deleteSession`.
255
+ */
256
+ export async function closeSession(sessionId: string): Promise<void> {
257
+ return archiveThread(sessionId)
258
+ }
259
+
260
+ /** Reabre una sesión archivada. */
261
+ export async function reopenSession(sessionId: string): Promise<void> {
262
+ return unarchiveThread(sessionId)
263
+ }
264
+
265
+ /** Borra la sesión entera: mensajes, resumen, notas y su fila del catálogo. */
266
+ export async function deleteSession(sessionId: string): Promise<void> {
267
+ return deleteThread(sessionId)
268
+ }
@@ -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.