@johpaz/hive-sdk 0.1.4 → 0.1.6

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 (278) hide show
  1. package/CHANGELOG.md +129 -0
  2. package/README.md +78 -23
  3. package/bun.lock +55 -29
  4. package/bunfig.toml +4 -2
  5. package/docs/API-AGENTS.md +78 -27
  6. package/docs/API-CONTEXT-COMPILER.md +31 -34
  7. package/docs/API-TOOLS-SKILLS-CHANNELS.md +58 -22
  8. package/docs/HIVE-HARNESS.md +1 -1
  9. package/docs/INDEX.md +4 -4
  10. package/docs/TEMPLATE-HIVE-APP.md +10 -10
  11. package/package.json +17 -12
  12. package/packages/cli/package.json +2 -2
  13. package/packages/cli/src/commands/create-app.test.ts +36 -7
  14. package/packages/cli/src/commands/init.ts +3 -3
  15. package/packages/cli/src/commands/run.ts +1 -1
  16. package/packages/cli/src/commands/test.ts +37 -25
  17. package/packages/cli/src/commands/trace.ts +30 -28
  18. package/packages/cli/templates/hive-app/.env.example +10 -2
  19. package/packages/cli/templates/hive-app/README.md +103 -0
  20. package/packages/cli/templates/hive-app/hive.config.ts +9 -3
  21. package/packages/cli/templates/hive-app/src/agents/coordinator.ts +8 -1
  22. package/packages/cli/templates/hive-app/src/main.ts +12 -19
  23. package/packages/core/package.json +13 -12
  24. package/packages/core/src/agent/acceptance-checks.ts +172 -0
  25. package/packages/core/src/agent/agent-catalog.ts +348 -0
  26. package/packages/core/src/agent/agent-loop.ts +1373 -0
  27. package/packages/core/src/agent/capability-search.ts +186 -0
  28. package/packages/core/src/agent/catalog-selector.ts +103 -0
  29. package/packages/core/src/agent/{Compaction.ts → compaction.ts} +86 -63
  30. package/packages/core/src/agent/context-compiler.ts +689 -0
  31. package/packages/core/src/agent/conversation-store.ts +381 -0
  32. package/packages/core/src/agent/curator.ts +276 -0
  33. package/packages/core/src/agent/delegation-runtime.ts +241 -0
  34. package/packages/core/src/agent/goal-runner.ts +323 -0
  35. package/packages/core/src/agent/index.ts +17 -12
  36. package/packages/core/src/agent/llm-client.ts +266 -0
  37. package/packages/core/src/agent/llm-providers/anthropic.ts +264 -0
  38. package/packages/core/src/agent/llm-providers/deepseek.ts +8 -0
  39. package/packages/core/src/agent/{providers → llm-providers}/gemini.ts +98 -60
  40. package/packages/core/src/agent/llm-providers/groq.ts +5 -0
  41. package/packages/core/src/agent/llm-providers/hiveagents.ts +253 -0
  42. package/packages/core/src/agent/{providers → llm-providers}/interface.ts +73 -13
  43. package/packages/core/src/agent/llm-providers/kimi.ts +8 -0
  44. package/packages/core/src/agent/llm-providers/minimax.ts +13 -0
  45. package/packages/core/src/agent/llm-providers/mistral.ts +5 -0
  46. package/packages/core/src/agent/llm-providers/modelscope.ts +5 -0
  47. package/packages/core/src/agent/llm-providers/nvidia.ts +5 -0
  48. package/packages/core/src/agent/{providers → llm-providers}/ollama.ts +31 -5
  49. package/packages/core/src/agent/llm-providers/openai-compat-base.ts +418 -0
  50. package/packages/core/src/agent/llm-providers/openai.ts +5 -0
  51. package/packages/core/src/agent/llm-providers/opencode-go.ts +9 -0
  52. package/packages/core/src/agent/llm-providers/openrouter.ts +5 -0
  53. package/packages/core/src/agent/llm-providers/qwen.ts +5 -0
  54. package/packages/core/src/agent/llm-providers/z-ai.ts +5 -0
  55. package/packages/core/src/agent/minimal-loadout.ts +47 -0
  56. package/packages/core/src/agent/playbook-selector.ts +119 -0
  57. package/packages/core/src/agent/{PromptBuilder.ts → prompt-builder.ts} +21 -22
  58. package/packages/core/src/{harness → agent}/proof-packet.ts +16 -21
  59. package/packages/core/src/agent/providers/index.ts +35 -16
  60. package/packages/core/src/agent/reflector.ts +320 -0
  61. package/packages/core/src/agent/routing-intent.ts +22 -0
  62. package/packages/core/src/{harness → agent}/run-epoch.ts +4 -3
  63. package/packages/core/src/{harness → agent}/run-store.ts +142 -81
  64. package/packages/core/src/agent/{Service.ts → service.ts} +37 -26
  65. package/packages/core/src/agent/skill-selector.ts +374 -0
  66. package/packages/core/src/agent/stuck-loop.ts +209 -0
  67. package/packages/core/src/agent/{selectors/ToolSelector.ts → tool-selector.ts} +188 -178
  68. package/packages/core/src/{ace/Tracer.ts → agent/tracer.ts} +37 -27
  69. package/packages/core/src/api/createAgent.test.ts +139 -27
  70. package/packages/core/src/api/createAgent.ts +232 -44
  71. package/packages/core/src/artifacts/store.ts +162 -0
  72. package/packages/core/src/canvas/canvas-manager.ts +161 -0
  73. package/packages/core/src/canvas/canvas.test.ts +8 -4
  74. package/packages/core/src/canvas/emitter.ts +131 -80
  75. package/packages/core/src/canvas/index.ts +1 -3
  76. package/packages/core/src/channels/base.ts +9 -1
  77. package/packages/core/src/channels/discord.ts +5 -4
  78. package/packages/core/src/channels/manager.ts +122 -30
  79. package/packages/core/src/channels/slack.ts +5 -4
  80. package/packages/core/src/channels/telegram.ts +36 -6
  81. package/packages/core/src/channels/webchat.ts +11 -10
  82. package/packages/core/src/channels/whatsapp.ts +23 -7
  83. package/packages/core/src/config/index.ts +13 -2
  84. package/packages/core/src/config/loader.ts +76 -29
  85. package/packages/core/src/ethics/EthicsGuard.test.ts +90 -36
  86. package/packages/core/src/ethics/EthicsGuard.ts +51 -47
  87. package/packages/core/src/events/agent-bus.ts +44 -68
  88. package/packages/core/src/events/channel-narration.ts +150 -0
  89. package/packages/core/src/events/narration.ts +82 -0
  90. package/packages/core/src/events/tool-narration.ts +62 -0
  91. package/packages/core/src/gateway/delegation-groups.ts +258 -0
  92. package/packages/core/src/{harness → gateway}/durable-queue.ts +102 -42
  93. package/packages/core/src/{harness → gateway}/job-store.ts +85 -48
  94. package/packages/core/src/gateway/lane-queue.ts +173 -0
  95. package/packages/core/src/gateway/notification-inbox.ts +57 -0
  96. package/packages/core/src/gateway/server.ts +1 -1
  97. package/packages/core/src/harness/index.ts +46 -27
  98. package/packages/core/src/index.ts +33 -27
  99. package/packages/core/src/mcp/hot-reload.ts +32 -23
  100. package/packages/core/src/mcp/index.ts +6 -3
  101. package/packages/core/src/mcp/singleton.ts +1 -4
  102. package/packages/core/src/mcp/tool-sync.ts +138 -0
  103. package/packages/core/src/memory/Scratchpad.test.ts +39 -20
  104. package/packages/core/src/memory/Scratchpad.ts +27 -34
  105. package/packages/core/src/multimodal/vision-service.ts +44 -38
  106. package/packages/core/src/resilience/retry.ts +95 -0
  107. package/packages/core/src/scheduler/CronScheduler.ts +334 -287
  108. package/packages/core/src/scheduler/index.ts +9 -7
  109. package/packages/core/src/scheduler/integration.ts +46 -26
  110. package/packages/core/src/scheduler/scheduler.test.ts +9 -13
  111. package/packages/core/src/scheduler/types.ts +7 -2
  112. package/packages/core/src/security/Pairing.ts +1 -1
  113. package/packages/core/src/skills/bundled/a2ui/a2ui_dashboard/SKILL.md +176 -0
  114. package/packages/core/src/skills/bundled/a2ui/a2ui_form/SKILL.md +202 -0
  115. package/packages/core/src/skills/bundled/a2ui/a2ui_interactive/SKILL.md +206 -0
  116. package/packages/core/src/skills/bundled/agents/agent_spawner/SKILL.md +173 -0
  117. package/packages/core/src/skills/bundled/agents/memory_manager/SKILL.md +143 -0
  118. package/packages/core/src/skills/bundled/agents/research_and_remember/SKILL.md +139 -0
  119. package/packages/core/src/skills/bundled/agents/task_orchestrator/SKILL.md +98 -0
  120. package/packages/core/src/skills/bundled/api/api_client/SKILL.md +132 -0
  121. package/packages/core/src/skills/bundled/cli/cli_pipeline/SKILL.md +135 -0
  122. package/packages/core/src/skills/bundled/cli/cli_safe_exec/SKILL.md +125 -0
  123. package/packages/core/src/skills/bundled/cli/software_engineering/SKILL.md +23 -0
  124. package/packages/core/src/skills/bundled/cron_manager/SKILL.md +188 -0
  125. package/packages/core/src/skills/bundled/cron_reminder/SKILL.md +112 -0
  126. package/packages/core/src/skills/bundled/filesystem/file_manager/SKILL.md +118 -0
  127. package/packages/core/src/skills/bundled/filesystem/file_read_and_summarize/SKILL.md +109 -0
  128. package/packages/core/src/skills/bundled/filesystem/file_writer/SKILL.md +129 -0
  129. package/packages/core/src/skills/bundled/filesystem/workspace_file_operator/SKILL.md +22 -0
  130. package/packages/core/src/skills/bundled/office/office_document_manager/SKILL.md +262 -0
  131. package/packages/core/src/skills/bundled/search_knowledge/capability_discovery/SKILL.md +75 -0
  132. package/packages/core/src/skills/bundled/web/browser_automate/SKILL.md +120 -0
  133. package/packages/core/src/skills/bundled/web/browser_scrape/SKILL.md +109 -0
  134. package/packages/core/src/skills/bundled/web/web_monitor/SKILL.md +127 -0
  135. package/packages/core/src/skills/bundled/web/web_research/SKILL.md +119 -0
  136. package/packages/core/src/skills/bundled-data.generated.ts +731 -2678
  137. package/packages/core/src/skills/skills.test.ts +52 -11
  138. package/packages/core/src/{harness → storage}/boot-id.ts +5 -2
  139. package/packages/core/src/storage/bootstrap.ts +151 -0
  140. package/packages/core/src/storage/causal-events.ts +84 -0
  141. package/packages/core/src/storage/collections.ts +680 -0
  142. package/packages/core/src/storage/crypto.ts +205 -74
  143. package/packages/core/src/{harness/db-helpers.ts → storage/hive.ts} +63 -7
  144. package/packages/core/src/storage/hivedb.ts +61 -0
  145. package/packages/core/src/storage/index.ts +111 -18
  146. package/packages/core/src/storage/model-id.ts +53 -0
  147. package/packages/core/src/storage/onboarding.ts +540 -972
  148. package/packages/core/src/storage/reconcile.ts +238 -0
  149. package/packages/core/src/storage/seed.ts +572 -406
  150. package/packages/core/src/storage/usage.ts +285 -225
  151. package/packages/core/src/storage/user-email.ts +11 -0
  152. package/packages/core/src/swarm/AgentExecutor.ts +1 -1
  153. package/packages/core/src/swarm/EventBridge.ts +1 -1
  154. package/packages/core/src/swarm/index.ts +12 -9
  155. package/packages/core/src/tool-runtime/index.ts +146 -23
  156. package/packages/core/src/tool-runtime/tool-worker.ts +2 -2
  157. package/packages/core/src/tool-runtime/worker-tools.ts +27 -0
  158. package/packages/core/src/{canvas/a2ui-tools.ts → tools/a2ui/index.ts} +17 -8
  159. package/packages/core/src/tools/agents/get-available-models.ts +36 -54
  160. package/packages/core/src/tools/agents/index.ts +784 -292
  161. package/packages/core/src/tools/api/api-request.test.ts +164 -0
  162. package/packages/core/src/tools/api/api-request.ts +174 -0
  163. package/packages/core/src/tools/api/index.ts +16 -0
  164. package/packages/core/src/tools/cli/index.ts +4 -0
  165. package/packages/core/src/tools/core/index.ts +281 -112
  166. package/packages/core/src/tools/cron/index.ts +121 -124
  167. package/packages/core/src/tools/index.ts +63 -78
  168. package/packages/core/src/tools/office/office-escribir-xlsx.ts +3 -1
  169. package/packages/core/src/tools/types.ts +3 -1
  170. package/packages/core/src/tools/web/artifact-inspect.ts +23 -0
  171. package/packages/core/src/tools/web/browser-backend.ts +129 -0
  172. package/packages/core/src/tools/web/browser-screenshot.ts +26 -5
  173. package/packages/core/src/tools/web/browser-service.ts +80 -35
  174. package/packages/core/src/tools/web/browser-type.ts +3 -8
  175. package/packages/core/src/tools/web/index.ts +4 -4
  176. package/packages/core/src/tools/web/webview-backend.ts +412 -0
  177. package/packages/core/src/voice/index.ts +89 -63
  178. package/packages/core/src/workers/agent.worker.ts +2 -2
  179. package/packages/core/src/workers/workers.test.ts +3 -10
  180. package/scripts/bump-version.ts +248 -0
  181. package/scripts/generate-skill-bundle.ts +108 -0
  182. package/test/acceptance-checks.test.ts +403 -0
  183. package/test/agent-loop-terminal-synthesis.test.ts +32 -0
  184. package/test/browser-backend.test.ts +308 -0
  185. package/test/catalog-agents-stay-enabled.test.ts +117 -0
  186. package/test/causal-events.test.ts +117 -0
  187. package/test/compaction.test.ts +105 -0
  188. package/test/context-compiler.test.ts +269 -0
  189. package/test/curator.test.ts +130 -0
  190. package/test/durable-queue.test.ts +114 -0
  191. package/test/harness-barrel.test.ts +64 -0
  192. package/test/hive-helpers.test.ts +130 -0
  193. package/test/hivedb-search.test.ts +189 -0
  194. package/test/internal-turns.test.ts +166 -0
  195. package/test/job-idempotency.test.ts +68 -0
  196. package/test/job-retry-backoff.test.ts +184 -0
  197. package/test/job-store.test.ts +381 -0
  198. package/test/llm-retry.test.ts +97 -0
  199. package/test/memory-perf.test.ts +774 -0
  200. package/test/minimal-loadout.test.ts +78 -0
  201. package/test/model-catalog.test.ts +105 -0
  202. package/test/preload.ts +12 -0
  203. package/test/reflector.test.ts +320 -0
  204. package/test/retention-cap.test.ts +91 -0
  205. package/test/retired-capabilities-pruned.test.ts +192 -0
  206. package/test/run-store.test.ts +355 -0
  207. package/test/scratchpad.test.ts +74 -0
  208. package/test/secrets-durability.test.ts +119 -0
  209. package/test/seed-model-reseed.test.ts +155 -0
  210. package/test/setup-agent-seed.test.ts +264 -0
  211. package/test/tool-inventory.test.ts +65 -0
  212. package/test/tool-runtime.test.ts +258 -0
  213. package/test/tool-selector-runtime-tools.test.ts +117 -0
  214. package/test/toon.test.ts +429 -0
  215. package/tsconfig.json +2 -0
  216. package/packages/core/src/ace/Curator.ts +0 -158
  217. package/packages/core/src/ace/Reflector.ts +0 -200
  218. package/packages/core/src/ace/index.ts +0 -4
  219. package/packages/core/src/agent/AgentRunner.ts +0 -711
  220. package/packages/core/src/agent/ContextCompiler.ts +0 -567
  221. package/packages/core/src/agent/ContextGuard.ts +0 -91
  222. package/packages/core/src/agent/ConversationStore.ts +0 -254
  223. package/packages/core/src/agent/Hooks.ts +0 -166
  224. package/packages/core/src/agent/StuckLoop.ts +0 -133
  225. package/packages/core/src/agent/providers/LLMClient.ts +0 -149
  226. package/packages/core/src/agent/providers/anthropic.ts +0 -212
  227. package/packages/core/src/agent/providers/openai-compat.ts +0 -231
  228. package/packages/core/src/agent/selectors/PlaybookSelector.ts +0 -121
  229. package/packages/core/src/agent/selectors/SkillSelector.ts +0 -322
  230. package/packages/core/src/agent/selectors/index.ts +0 -6
  231. package/packages/core/src/auth/auth.ts +0 -121
  232. package/packages/core/src/auth/index.ts +0 -1
  233. package/packages/core/src/canvas/CanvasManager.ts +0 -390
  234. package/packages/core/src/canvas/canvas-tools.ts +0 -448
  235. package/packages/core/src/harness/collections.ts +0 -98
  236. package/packages/core/src/harness/goal-verifier.ts +0 -141
  237. package/packages/core/src/harness/harness.test.ts +0 -236
  238. package/packages/core/src/harness/reconcile.ts +0 -149
  239. package/packages/core/src/mcp/MCPToolAdapter.ts +0 -176
  240. package/packages/core/src/multimodal/VisionService.ts +0 -293
  241. package/packages/core/src/scheduler/dag/AgentExecutor.ts +0 -53
  242. package/packages/core/src/scheduler/dag/DAGScheduler.ts +0 -250
  243. package/packages/core/src/scheduler/dag/EventBridge.ts +0 -122
  244. package/packages/core/src/scheduler/dag/TaskGraph.ts +0 -192
  245. package/packages/core/src/scheduler/dag/TaskNode.ts +0 -97
  246. package/packages/core/src/scheduler/dag/TaskResult.ts +0 -22
  247. package/packages/core/src/scheduler/dag/errors.ts +0 -37
  248. package/packages/core/src/scheduler/dag/index.ts +0 -26
  249. package/packages/core/src/scheduler/dag/presets/ResearchPreset.ts +0 -97
  250. package/packages/core/src/scheduler/dag/strategies/ParallelStrategy.ts +0 -21
  251. package/packages/core/src/scheduler/dag/strategies/PriorityStrategy.ts +0 -46
  252. package/packages/core/src/storage/HiveDBStorage.ts +0 -64
  253. package/packages/core/src/storage/SQLiteStorage.ts +0 -414
  254. package/packages/core/src/storage/hiveSeed.ts +0 -308
  255. package/packages/core/src/storage/hiveStorage.test.ts +0 -38
  256. package/packages/core/src/storage/schema.ts +0 -689
  257. package/packages/core/src/storage/storage.test.ts +0 -37
  258. package/packages/core/src/swarm/AgentBus.ts +0 -460
  259. package/packages/core/src/swarm/EventBus.ts +0 -169
  260. package/packages/core/src/swarm/WorkerPool.ts +0 -236
  261. package/packages/core/src/tools/bridge-events.ts +0 -26
  262. package/packages/core/src/tools/canvas/index.ts +0 -375
  263. package/packages/core/src/tools/codebridge/index.ts +0 -342
  264. package/packages/core/src/tools/meeting/index.ts +0 -353
  265. package/packages/core/src/tools/projects/index.ts +0 -37
  266. package/packages/core/src/tools/projects/project-create.ts +0 -94
  267. package/packages/core/src/tools/projects/project-done.ts +0 -66
  268. package/packages/core/src/tools/projects/project-fail.ts +0 -66
  269. package/packages/core/src/tools/projects/project-list.ts +0 -96
  270. package/packages/core/src/tools/projects/project-update.ts +0 -72
  271. package/packages/core/src/tools/projects/task-create.ts +0 -68
  272. package/packages/core/src/tools/projects/task-evaluate.ts +0 -93
  273. package/packages/core/src/tools/projects/task-update.ts +0 -93
  274. package/packages/core/src/tools/voice/index.ts +0 -104
  275. package/packages/core/src/tools/web/api-request.test.ts +0 -170
  276. package/packages/core/src/tools/web/api-request.ts +0 -239
  277. package/test/setup-db.ts +0 -216
  278. /package/packages/core/src/agent/{NativeTools.ts → native-tools.ts} +0 -0
@@ -1,293 +0,0 @@
1
- import { getDb } from "../storage/SQLiteStorage.ts"
2
- import { decryptApiKey } from "../storage/crypto.ts"
3
- import { logger } from "../utils/logger.ts"
4
- import type { ImageInput, DocumentInput, VisionConfig } from "./types"
5
- import type { ContentPart } from "./types"
6
-
7
- const log = logger.child("multimodal")
8
-
9
- class MultimodalService {
10
- private static instance: MultimodalService
11
-
12
- private constructor() {}
13
-
14
- static getInstance(): MultimodalService {
15
- if (!MultimodalService.instance) {
16
- MultimodalService.instance = new MultimodalService()
17
- }
18
- return MultimodalService.instance
19
- }
20
-
21
- getChannelVisionConfig(channelId: string): VisionConfig {
22
- const db = getDb()
23
- const result = db.query(`
24
- SELECT vision_enabled, ocr_provider, vision_provider, vision_model_id
25
- FROM channels WHERE id = ?
26
- `).get(channelId) as {
27
- vision_enabled: number
28
- ocr_provider: string | null
29
- vision_provider: string | null
30
- vision_model_id: string | null
31
- } | undefined
32
-
33
- if (!result) {
34
- return { visionEnabled: false, ocrProvider: null, visionProvider: null, visionModelId: null }
35
- }
36
-
37
- return {
38
- visionEnabled: result.vision_enabled === 1,
39
- ocrProvider: result.ocr_provider,
40
- visionProvider: result.vision_provider,
41
- visionModelId: result.vision_model_id,
42
- }
43
- }
44
-
45
- async processImage(image: ImageInput, visionModelId?: string): Promise<ContentPart[]> {
46
- const parts: ContentPart[] = []
47
-
48
- if (image.caption) {
49
- parts.push({ type: "text", text: image.caption })
50
- }
51
-
52
- if (image.type === "url") {
53
- parts.push({ type: "image_url", image_url: { url: image.data as string } })
54
- } else if (image.type === "base64") {
55
- parts.push({
56
- type: "image_base64",
57
- base64: image.data as string,
58
- mimeType: image.mimeType || "image/jpeg",
59
- })
60
- } else if (image.type === "buffer") {
61
- const base64 = Buffer.from(image.data as Buffer).toString("base64")
62
- parts.push({
63
- type: "image_base64",
64
- base64,
65
- mimeType: image.mimeType || "image/jpeg",
66
- })
67
- }
68
-
69
- return parts
70
- }
71
-
72
- async ocrImage(image: ImageInput, providerId?: string): Promise<string> {
73
- const resolved = providerId || "openai"
74
-
75
- if (resolved === "openai") {
76
- return this.ocrWithOpenAI(image)
77
- } else if (resolved === "gemini") {
78
- return this.ocrWithGemini(image)
79
- } else if (resolved === "anthropic") {
80
- return this.ocrWithAnthropic(image)
81
- }
82
-
83
- log.warn(`Unknown OCR provider ${resolved}, defaulting to OpenAI`)
84
- return this.ocrWithOpenAI(image)
85
- }
86
-
87
- normalizeImageFromChannel(channelType: string, imageData: unknown): ImageInput {
88
- const data = imageData as { url?: string; base64?: string; buffer?: Buffer; mimeType?: string; caption?: string }
89
-
90
- if (data.url) {
91
- return { type: "url", data: data.url, mimeType: data.mimeType, caption: data.caption }
92
- }
93
- if (data.base64) {
94
- return { type: "base64", data: data.base64, mimeType: data.mimeType || "image/jpeg", caption: data.caption }
95
- }
96
- if (data.buffer) {
97
- return { type: "buffer", data: data.buffer, mimeType: data.mimeType || "image/jpeg", caption: data.caption }
98
- }
99
-
100
- throw new Error(`${channelType} image missing url, base64, or buffer`)
101
- }
102
-
103
- normalizeDocumentFromChannel(channelType: string, docData: unknown): DocumentInput {
104
- const data = docData as { url?: string; base64?: string; buffer?: Buffer; mimeType?: string; fileName?: string }
105
-
106
- if (data.url) {
107
- return { type: "url", data: data.url, mimeType: data.mimeType || "application/pdf", fileName: data.fileName }
108
- }
109
- if (data.base64) {
110
- return { type: "base64", data: data.base64, mimeType: data.mimeType || "application/pdf", fileName: data.fileName }
111
- }
112
- if (data.buffer) {
113
- return { type: "buffer", data: data.buffer, mimeType: data.mimeType || "application/pdf", fileName: data.fileName }
114
- }
115
-
116
- throw new Error(`${channelType} document missing url, base64, or buffer`)
117
- }
118
-
119
- async resolveImageUrl(image: ImageInput): Promise<string> {
120
- if (image.type === "url") return image.data as string
121
- if (image.type === "base64") {
122
- const mime = image.mimeType || "image/jpeg"
123
- return `data:${mime};base64,${image.data as string}`
124
- }
125
- if (image.type === "buffer") {
126
- const base64 = Buffer.from(image.data as Buffer).toString("base64")
127
- const mime = image.mimeType || "image/jpeg"
128
- return `data:${mime};base64,${base64}`
129
- }
130
- throw new Error("Cannot resolve image URL")
131
- }
132
-
133
- private async getProviderApiKey(providerId: string): Promise<string | null> {
134
- const db = getDb()
135
- const provider = db.query(`
136
- SELECT api_key_encrypted, api_key_iv FROM providers WHERE id = ?
137
- `).get(providerId) as { api_key_encrypted: string; api_key_iv: string } | undefined
138
-
139
- if (!provider?.api_key_encrypted) return null
140
-
141
- try {
142
- return await decryptApiKey(provider.api_key_encrypted, provider.api_key_iv)
143
- } catch (error) {
144
- log.error(`Failed to decrypt API key for provider ${providerId}: ${(error as Error).message}`)
145
- return null
146
- }
147
- }
148
-
149
- private async ocrWithOpenAI(image: ImageInput): Promise<string> {
150
- const key = await this.getProviderApiKey("openai") || process.env.OPENAI_API_KEY
151
- if (!key) throw new Error("OPENAI_API_KEY not configured for OCR")
152
-
153
- const imageUrl = await this.resolveImageUrl(image)
154
-
155
- const response = await fetch("https://api.openai.com/v1/chat/completions", {
156
- method: "POST",
157
- headers: { "Content-Type": "application/json", "Authorization": `Bearer ${key}` },
158
- body: JSON.stringify({
159
- model: "gpt-4o-mini",
160
- messages: [{
161
- role: "user",
162
- content: [
163
- { type: "text", text: "Describe el contenido de esta imagen en detalle. Si hay texto, transcríbelo exactamente." },
164
- { type: "image_url", image_url: { url: imageUrl } },
165
- ],
166
- }],
167
- max_tokens: 1000,
168
- }),
169
- })
170
-
171
- if (!response.ok) {
172
- const error = await response.text()
173
- throw new Error(`OpenAI OCR failed: ${error}`)
174
- }
175
-
176
- const data = await response.json() as { choices: Array<{ message: { content: string } }> }
177
- return data.choices[0]?.message?.content || ""
178
- }
179
-
180
- private async ocrWithGemini(image: ImageInput): Promise<string> {
181
- const key = await this.getProviderApiKey("gemini") || process.env.GEMINI_API_KEY
182
- if (!key) throw new Error("GEMINI_API_KEY not configured for OCR")
183
-
184
- let imagePart: any
185
- if (image.type === "url") {
186
- const imgResponse = await fetch(image.data as string)
187
- const buffer = Buffer.from(await imgResponse.arrayBuffer())
188
- imagePart = { inlineData: { data: buffer.toString("base64"), mimeType: image.mimeType || "image/jpeg" } }
189
- } else if (image.type === "base64") {
190
- imagePart = { inlineData: { data: image.data as string, mimeType: image.mimeType || "image/jpeg" } }
191
- } else {
192
- imagePart = { inlineData: { data: Buffer.from(image.data as Buffer).toString("base64"), mimeType: image.mimeType || "image/jpeg" } }
193
- }
194
-
195
- const response = await fetch(
196
- `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${key}`,
197
- {
198
- method: "POST",
199
- headers: { "Content-Type": "application/json" },
200
- body: JSON.stringify({
201
- contents: [{ parts: [{ text: "Describe el contenido de esta imagen en detalle. Si hay texto, transcríbelo exactamente." }, imagePart] }],
202
- }),
203
- },
204
- )
205
-
206
- if (!response.ok) {
207
- const error = await response.text()
208
- throw new Error(`Gemini OCR failed: ${error}`)
209
- }
210
-
211
- const data = await response.json() as { candidates: Array<{ content: { parts: Array<{ text?: string }> } }> }
212
- return data.candidates?.[0]?.content?.parts?.[0]?.text || ""
213
- }
214
-
215
- private async ocrWithAnthropic(image: ImageInput): Promise<string> {
216
- const key = await this.getProviderApiKey("anthropic") || process.env.ANTHROPIC_API_KEY
217
- if (!key) throw new Error("ANTHROPIC_API_KEY not configured for OCR")
218
-
219
- const imageUrl = await this.resolveImageUrl(image)
220
-
221
- let source: any
222
- if (imageUrl.startsWith("data:")) {
223
- const match = imageUrl.match(/^data:([^;]+);base64,(.+)$/)
224
- if (match) {
225
- source = { type: "base64", media_type: match[1], data: match[2] }
226
- } else {
227
- throw new Error("Invalid base64 data URL")
228
- }
229
- } else {
230
- source = { type: "url", url: imageUrl }
231
- }
232
-
233
- const response = await fetch("https://api.anthropic.com/v1/messages", {
234
- method: "POST",
235
- headers: {
236
- "Content-Type": "application/json",
237
- "x-api-key": key,
238
- "anthropic-version": "2023-06-01",
239
- "anthropic-dangerous-direct-browser-access": "true",
240
- },
241
- body: JSON.stringify({
242
- model: "claude-haiku-4-5-20251001",
243
- max_tokens: 1000,
244
- messages: [{
245
- role: "user",
246
- content: [
247
- { type: "image", source },
248
- { type: "text", text: "Describe el contenido de esta imagen en detalle. Si hay texto, transcríbelo exactamente." },
249
- ],
250
- }],
251
- }),
252
- })
253
-
254
- if (!response.ok) {
255
- const error = await response.text()
256
- throw new Error(`Anthropic OCR failed: ${error}`)
257
- }
258
-
259
- const data = await response.json() as { content: Array<{ type: string; text?: string }> }
260
- const textBlock = data.content?.find(b => b.type === "text" && b.text)
261
- return textBlock?.text || ""
262
- }
263
-
264
- getConfiguredVisionProviders(): Record<string, boolean> {
265
- const db = getDb()
266
- const hasDbKey = (providerId: string): boolean => {
267
- const row = db.query(
268
- `SELECT api_key_encrypted FROM providers WHERE id = ? AND api_key_encrypted IS NOT NULL AND api_key_encrypted != ''`
269
- ).get(providerId) as { api_key_encrypted: string } | undefined
270
- return !!row
271
- }
272
-
273
- return {
274
- openai: hasDbKey("openai") || !!(process.env.OPENAI_API_KEY),
275
- gemini: hasDbKey("gemini") || !!(process.env.GEMINI_API_KEY),
276
- anthropic: hasDbKey("anthropic") || !!(process.env.ANTHROPIC_API_KEY),
277
- }
278
- }
279
-
280
- modelSupportsVision(providerId: string, modelId: string): boolean {
281
- const db = getDb()
282
- const model = db.query(`SELECT capabilities FROM models WHERE id = ? AND provider_id = ?`).get(modelId, providerId) as { capabilities: string } | undefined
283
- if (!model?.capabilities) return false
284
- try {
285
- const caps = JSON.parse(model.capabilities) as string[]
286
- return caps.includes("vision")
287
- } catch {
288
- return false
289
- }
290
- }
291
- }
292
-
293
- export const multimodalService = MultimodalService.getInstance()
@@ -1,53 +0,0 @@
1
- /**
2
- * DAGScheduler — AgentExecutor
3
- *
4
- * Bridges the DAGScheduler to the existing runAgentIsolated() call.
5
- * Adds timeout enforcement via Promise.race().
6
- *
7
- * NOTE: There are no Bun Worker threads in Hive OSS. "Workers" are logical
8
- * agents stored in the DB and executed as async calls in the same process.
9
- * Parallelism is achieved by launching multiple runAgentIsolated() calls
10
- * concurrently without awaiting each one serially.
11
- */
12
-
13
- import { runAgentIsolated } from "../../agent/AgentRunner"
14
- import { TaskNode } from "./TaskNode"
15
- import { TaskTimeoutError } from "./errors"
16
-
17
- export class AgentExecutor {
18
- /**
19
- * Execute a TaskNode.
20
- * Injects dependency results into the task description as context.
21
- * Returns the final text output from the agent.
22
- */
23
- async execute(
24
- node: TaskNode,
25
- depResults: Record<string, string>,
26
- threadId: string
27
- ): Promise<string> {
28
- const hasDeps = Object.keys(depResults).length > 0
29
- const contextBlock = hasDeps
30
- ? `\n\n---\nContext from completed dependencies:\n${JSON.stringify(depResults, null, 2)}\n---`
31
- : ""
32
-
33
- const taskDescription = node.taskDescription + contextBlock
34
-
35
- const agentPromise = runAgentIsolated({
36
- agentId: node.agentId,
37
- taskDescription,
38
- threadId,
39
- })
40
-
41
- const timeoutPromise = new Promise<never>((_, reject) => {
42
- const t = setTimeout(() => {
43
- reject(new TaskTimeoutError(node.id, node.timeout))
44
- }, node.timeout)
45
- // Ensure the timeout timer doesn't prevent process exit
46
- if (typeof t === "object" && t !== null && "unref" in t) {
47
- (t as any).unref()
48
- }
49
- })
50
-
51
- return Promise.race([agentPromise, timeoutPromise])
52
- }
53
- }
@@ -1,250 +0,0 @@
1
- /**
2
- * DAGScheduler — Main orchestrator
3
- *
4
- * Executes a TaskGraph by:
5
- * 1. Identifying all nodes with no dependencies → mark READY
6
- * 2. Launching them concurrently via AgentExecutor (respecting maxConcurrentWorkers)
7
- * 3. When a node completes, finding newly unblocked nodes and launching them
8
- * 4. Propagating failures to dependent nodes
9
- * 5. Emitting progress via EventBridge → agentBus + canvas
10
- *
11
- * Parallelism model: Promise.race() over a Set of active promises + a FIFO/priority
12
- * queue of READY nodes waiting for a slot. No Bun Worker threads — workers are async
13
- * agent calls (runAgentIsolated) running concurrently in the same process.
14
- */
15
-
16
- import { writeFileSync, mkdirSync, existsSync } from "node:fs"
17
- import * as path from "node:path"
18
- import { logger } from "../../utils/logger"
19
- import { TaskGraph } from "./TaskGraph"
20
- import { TaskNode } from "./TaskNode"
21
- import { AgentExecutor } from "./AgentExecutor"
22
- import { EventBridge } from "./EventBridge"
23
- import { TaskFailureError } from "./errors"
24
- import type { DAGResult, NodeSummary } from "./TaskResult"
25
- import type { ExecutionStrategy } from "./strategies/ParallelStrategy"
26
- import { ParallelStrategy } from "./strategies/ParallelStrategy"
27
-
28
- const log = logger.child("dag-scheduler")
29
-
30
- export interface IAgentExecutor {
31
- execute(node: TaskNode, depResults: Record<string, string>, threadId: string): Promise<string>
32
- }
33
-
34
- export interface DAGSchedulerOptions {
35
- strategy?: ExecutionStrategy
36
- maxConcurrentWorkers?: number
37
- /** Project ID for agentBus/canvas events */
38
- projectId?: string
39
- /** Coordinator agent ID for agentBus events */
40
- coordinatorId?: string
41
- /** Disables ASCII log and file logging. Default: false in development */
42
- silent?: boolean
43
- /** Custom executor — defaults to AgentExecutor (runAgentIsolated). Override to bypass context-compiler. */
44
- executor?: IAgentExecutor
45
- }
46
-
47
- export class DAGScheduler {
48
- private strategy: ExecutionStrategy
49
- private maxConcurrentWorkers: number
50
- private executor: IAgentExecutor
51
- private aborted = false
52
-
53
- constructor(options: DAGSchedulerOptions = {}) {
54
- this.strategy = options.strategy ?? new ParallelStrategy()
55
- this.maxConcurrentWorkers = options.maxConcurrentWorkers ?? 2
56
- this.executor = options.executor ?? new AgentExecutor()
57
- }
58
-
59
- abort(): void {
60
- this.aborted = true
61
- }
62
-
63
- async execute(graph: TaskGraph, options: DAGSchedulerOptions = {}): Promise<DAGResult> {
64
- this.aborted = false
65
- const swarmId = crypto.randomUUID()
66
- const startedAt = Date.now()
67
-
68
- const projectId = options.projectId ?? `swarm:${swarmId}`
69
- const coordinatorId = options.coordinatorId ?? "dag-scheduler"
70
- const silent = options.silent ?? (process.env.NODE_ENV === "production")
71
-
72
- const bridge = new EventBridge(swarmId, projectId, coordinatorId)
73
-
74
- // Allow strategy to precompute (e.g. critical path)
75
- if (this.strategy.initialize) {
76
- this.strategy.initialize(graph.nodes)
77
- }
78
-
79
- bridge.onSwarmStarted(graph.nodes.size)
80
- this.logState(swarmId, graph, startedAt, silent, swarmId)
81
-
82
- // Seed the READY queue with nodes that have no dependencies
83
- const readyQueue: TaskNode[] = []
84
- const completedIds = graph.getCompletedIds()
85
-
86
- for (const node of graph.nodes.values()) {
87
- if (node.deps.length === 0) {
88
- node.markReady()
89
- readyQueue.push(node)
90
- }
91
- }
92
-
93
- // Active promise set — we track them with a wrapper so we can drain
94
- const running = new Set<Promise<void>>()
95
-
96
- const launchNode = (node: TaskNode): void => {
97
- if (this.aborted) return
98
-
99
- node.markRunning()
100
- bridge.onTaskStarted(node)
101
- this.logState(swarmId, graph, startedAt, silent, swarmId)
102
-
103
- const depResults = graph.getDepResults(node.id)
104
- const threadId = `dag-${swarmId}-${node.id}`
105
-
106
- const p: Promise<void> = this.executor
107
- .execute(node, depResults, threadId)
108
- .then(result => {
109
- node.markCompleted(result)
110
- log.info(`[DAG] ${node.name} COMPLETED in ${node.elapsedSeconds()}s`)
111
- bridge.onTaskCompleted(node, graph.getProgress())
112
- this.logState(swarmId, graph, startedAt, silent, swarmId)
113
-
114
- // Unlock dependent nodes
115
- const newlyReady = graph.getNewlyReadyNodes(graph.getCompletedIds())
116
- for (const n of newlyReady) {
117
- n.markReady()
118
- readyQueue.push(n)
119
- }
120
- })
121
- .catch(err => {
122
- const error = err instanceof Error ? err.message : String(err)
123
-
124
- if (node.canRetry()) {
125
- node.retryCount++
126
- log.warn(`[DAG] ${node.name} failed (retry ${node.retryCount}/${node.maxRetries}): ${error}`)
127
- node.status = "PENDING"
128
- node.markReady()
129
- readyQueue.push(node)
130
- } else {
131
- node.markFailed(error)
132
- log.error(`[DAG] ${node.name} FAILED permanently: ${error}`)
133
- bridge.onTaskFailed(node, graph.getProgress())
134
- graph.propagateFailure(node.id, error)
135
- this.logState(swarmId, graph, startedAt, silent, swarmId)
136
- }
137
- })
138
- .finally(() => {
139
- running.delete(p)
140
- drain()
141
- })
142
-
143
- running.add(p)
144
- }
145
-
146
- // Drain the ready queue into available worker slots
147
- const drain = (): void => {
148
- while (readyQueue.length > 0 && running.size < this.maxConcurrentWorkers && !this.aborted) {
149
- const node = this.strategy.pick(readyQueue)
150
- if (!node) break
151
- launchNode(node)
152
- }
153
- }
154
-
155
- // Start initial drain
156
- drain()
157
-
158
- // Wait until the graph is complete
159
- while (!graph.isComplete() && !this.aborted) {
160
- if (running.size === 0 && readyQueue.length === 0) {
161
- // Deadlock guard: no running, nothing ready, but graph not done
162
- // This can happen if all remaining nodes are FAILED
163
- break
164
- }
165
- // Wait for any active promise to settle
166
- if (running.size > 0) {
167
- await Promise.race([...running])
168
- drain()
169
- } else {
170
- // Brief yield to let microtasks settle
171
- await new Promise(resolve => setTimeout(resolve, 10))
172
- }
173
- }
174
-
175
- // Collect results
176
- const completed: NodeSummary[] = []
177
- const failed: NodeSummary[] = []
178
-
179
- for (const node of graph.nodes.values()) {
180
- const summary: NodeSummary = {
181
- id: node.id,
182
- name: node.name,
183
- status: node.status === "COMPLETED" ? "COMPLETED" : "FAILED",
184
- durationMs: node.startedAt ? (node.completedAt ?? Date.now()) - node.startedAt : 0,
185
- result: node.result,
186
- error: node.error,
187
- retries: node.retryCount,
188
- }
189
- if (node.status === "COMPLETED") completed.push(summary)
190
- else failed.push(summary)
191
- }
192
-
193
- const result: DAGResult = {
194
- swarmId,
195
- totalDurationMs: Date.now() - startedAt,
196
- completed,
197
- failed,
198
- success: failed.length === 0,
199
- }
200
-
201
- bridge.onSwarmCompleted(result)
202
- this.logState(swarmId, graph, startedAt, silent, swarmId)
203
-
204
- log.info(`[DAG] swarm ${swarmId} finished. ${completed.length} completed, ${failed.length} failed. Total: ${Math.round(result.totalDurationMs / 1000)}s`)
205
-
206
- return result
207
- }
208
-
209
- // ─── ASCII log ───────────────────────────────────────────────────────────────
210
-
211
- private logState(
212
- swarmId: string,
213
- graph: TaskGraph,
214
- startedAt: number,
215
- silent: boolean,
216
- sessionId: string
217
- ): void {
218
- const elapsed = Math.round((Date.now() - startedAt) / 1000)
219
- const lines: string[] = [`[DAG] swarm:${swarmId.slice(0, 8)} T+${elapsed}s`]
220
-
221
- for (const node of graph.nodes.values()) {
222
- const icon =
223
- node.status === "COMPLETED" ? "✓" :
224
- node.status === "FAILED" ? "✗" :
225
- node.status === "RUNNING" ? "●" : "○"
226
-
227
- const depStr = node.deps.length > 0 ? ` (deps: ${node.deps.join(", ")})` : ""
228
- const timeStr = node.startedAt ? ` (${node.elapsedSeconds()}s)` : ""
229
- const statusLabel = node.status.padEnd(10)
230
-
231
- lines.push(` ${icon} ${node.name.padEnd(24)} ${statusLabel}${timeStr}${depStr}`)
232
- }
233
-
234
- const output = lines.join("\n")
235
-
236
- if (!silent) {
237
- // Write to log file (never committed — in .gitignore)
238
- try {
239
- const logDir = path.join(process.cwd(), "packages", "core", "logs")
240
- if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true })
241
- const logFile = path.join(logDir, `dag-${sessionId.slice(0, 8)}.log`)
242
- writeFileSync(logFile, output + "\n\n", { flag: "a" })
243
- } catch {
244
- // Non-critical — never throw for logging
245
- }
246
- }
247
-
248
- log.debug(output)
249
- }
250
- }