@johpaz/hive-sdk 0.1.4 → 0.1.5

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 (272) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/README.md +78 -23
  3. package/bunfig.toml +4 -2
  4. package/docs/API-AGENTS.md +78 -27
  5. package/docs/API-CONTEXT-COMPILER.md +31 -34
  6. package/docs/API-TOOLS-SKILLS-CHANNELS.md +58 -22
  7. package/docs/HIVE-HARNESS.md +1 -1
  8. package/docs/INDEX.md +4 -4
  9. package/docs/TEMPLATE-HIVE-APP.md +10 -10
  10. package/package.json +9 -4
  11. package/packages/cli/package.json +2 -2
  12. package/packages/cli/src/commands/create-app.test.ts +36 -7
  13. package/packages/cli/src/commands/init.ts +3 -3
  14. package/packages/cli/src/commands/run.ts +1 -1
  15. package/packages/cli/src/commands/test.ts +37 -25
  16. package/packages/cli/src/commands/trace.ts +30 -28
  17. package/packages/cli/templates/hive-app/.env.example +10 -2
  18. package/packages/cli/templates/hive-app/README.md +103 -0
  19. package/packages/cli/templates/hive-app/hive.config.ts +9 -3
  20. package/packages/cli/templates/hive-app/src/agents/coordinator.ts +8 -1
  21. package/packages/cli/templates/hive-app/src/main.ts +12 -19
  22. package/packages/core/package.json +5 -4
  23. package/packages/core/src/agent/acceptance-checks.ts +166 -0
  24. package/packages/core/src/agent/agent-catalog.ts +348 -0
  25. package/packages/core/src/agent/agent-loop.ts +1373 -0
  26. package/packages/core/src/agent/capability-search.ts +186 -0
  27. package/packages/core/src/agent/catalog-selector.ts +103 -0
  28. package/packages/core/src/agent/{Compaction.ts → compaction.ts} +86 -63
  29. package/packages/core/src/agent/context-compiler.ts +689 -0
  30. package/packages/core/src/agent/conversation-store.ts +381 -0
  31. package/packages/core/src/agent/curator.ts +276 -0
  32. package/packages/core/src/agent/delegation-runtime.ts +241 -0
  33. package/packages/core/src/agent/goal-runner.ts +323 -0
  34. package/packages/core/src/agent/index.ts +17 -12
  35. package/packages/core/src/agent/llm-client.ts +266 -0
  36. package/packages/core/src/agent/llm-providers/anthropic.ts +264 -0
  37. package/packages/core/src/agent/llm-providers/deepseek.ts +8 -0
  38. package/packages/core/src/agent/{providers → llm-providers}/gemini.ts +98 -60
  39. package/packages/core/src/agent/llm-providers/groq.ts +5 -0
  40. package/packages/core/src/agent/llm-providers/hiveagents.ts +253 -0
  41. package/packages/core/src/agent/{providers → llm-providers}/interface.ts +73 -13
  42. package/packages/core/src/agent/llm-providers/kimi.ts +8 -0
  43. package/packages/core/src/agent/llm-providers/minimax.ts +13 -0
  44. package/packages/core/src/agent/llm-providers/mistral.ts +5 -0
  45. package/packages/core/src/agent/llm-providers/modelscope.ts +5 -0
  46. package/packages/core/src/agent/llm-providers/nvidia.ts +5 -0
  47. package/packages/core/src/agent/{providers → llm-providers}/ollama.ts +31 -5
  48. package/packages/core/src/agent/llm-providers/openai-compat-base.ts +418 -0
  49. package/packages/core/src/agent/llm-providers/openai.ts +5 -0
  50. package/packages/core/src/agent/llm-providers/opencode-go.ts +9 -0
  51. package/packages/core/src/agent/llm-providers/openrouter.ts +5 -0
  52. package/packages/core/src/agent/llm-providers/qwen.ts +5 -0
  53. package/packages/core/src/agent/llm-providers/z-ai.ts +5 -0
  54. package/packages/core/src/agent/minimal-loadout.ts +47 -0
  55. package/packages/core/src/agent/playbook-selector.ts +119 -0
  56. package/packages/core/src/agent/{PromptBuilder.ts → prompt-builder.ts} +21 -22
  57. package/packages/core/src/{harness → agent}/proof-packet.ts +16 -21
  58. package/packages/core/src/agent/providers/index.ts +35 -16
  59. package/packages/core/src/agent/reflector.ts +320 -0
  60. package/packages/core/src/agent/routing-intent.ts +22 -0
  61. package/packages/core/src/{harness → agent}/run-epoch.ts +4 -3
  62. package/packages/core/src/{harness → agent}/run-store.ts +142 -81
  63. package/packages/core/src/agent/{Service.ts → service.ts} +37 -26
  64. package/packages/core/src/agent/skill-selector.ts +374 -0
  65. package/packages/core/src/agent/stuck-loop.ts +209 -0
  66. package/packages/core/src/agent/{selectors/ToolSelector.ts → tool-selector.ts} +188 -178
  67. package/packages/core/src/{ace/Tracer.ts → agent/tracer.ts} +37 -27
  68. package/packages/core/src/api/createAgent.test.ts +139 -27
  69. package/packages/core/src/api/createAgent.ts +232 -44
  70. package/packages/core/src/artifacts/store.ts +162 -0
  71. package/packages/core/src/canvas/canvas-manager.ts +161 -0
  72. package/packages/core/src/canvas/canvas.test.ts +8 -4
  73. package/packages/core/src/canvas/emitter.ts +131 -80
  74. package/packages/core/src/canvas/index.ts +1 -3
  75. package/packages/core/src/channels/base.ts +9 -1
  76. package/packages/core/src/channels/discord.ts +5 -4
  77. package/packages/core/src/channels/manager.ts +122 -30
  78. package/packages/core/src/channels/slack.ts +5 -4
  79. package/packages/core/src/channels/telegram.ts +36 -6
  80. package/packages/core/src/channels/webchat.ts +11 -10
  81. package/packages/core/src/channels/whatsapp.ts +23 -7
  82. package/packages/core/src/config/index.ts +13 -2
  83. package/packages/core/src/config/loader.ts +71 -29
  84. package/packages/core/src/ethics/EthicsGuard.test.ts +90 -36
  85. package/packages/core/src/ethics/EthicsGuard.ts +51 -47
  86. package/packages/core/src/events/agent-bus.ts +44 -68
  87. package/packages/core/src/events/channel-narration.ts +150 -0
  88. package/packages/core/src/events/narration.ts +82 -0
  89. package/packages/core/src/events/tool-narration.ts +62 -0
  90. package/packages/core/src/gateway/delegation-groups.ts +258 -0
  91. package/packages/core/src/{harness → gateway}/durable-queue.ts +102 -42
  92. package/packages/core/src/{harness → gateway}/job-store.ts +85 -48
  93. package/packages/core/src/gateway/lane-queue.ts +173 -0
  94. package/packages/core/src/gateway/notification-inbox.ts +57 -0
  95. package/packages/core/src/gateway/server.ts +1 -1
  96. package/packages/core/src/harness/index.ts +46 -27
  97. package/packages/core/src/index.ts +33 -27
  98. package/packages/core/src/mcp/hot-reload.ts +32 -23
  99. package/packages/core/src/mcp/index.ts +6 -3
  100. package/packages/core/src/mcp/singleton.ts +1 -4
  101. package/packages/core/src/mcp/tool-sync.ts +138 -0
  102. package/packages/core/src/memory/Scratchpad.test.ts +39 -20
  103. package/packages/core/src/memory/Scratchpad.ts +27 -34
  104. package/packages/core/src/multimodal/vision-service.ts +44 -38
  105. package/packages/core/src/resilience/retry.ts +95 -0
  106. package/packages/core/src/scheduler/CronScheduler.ts +334 -287
  107. package/packages/core/src/scheduler/index.ts +9 -7
  108. package/packages/core/src/scheduler/integration.ts +46 -26
  109. package/packages/core/src/scheduler/scheduler.test.ts +9 -13
  110. package/packages/core/src/scheduler/types.ts +7 -2
  111. package/packages/core/src/security/Pairing.ts +1 -1
  112. package/packages/core/src/skills/bundled/a2ui/a2ui_dashboard/SKILL.md +176 -0
  113. package/packages/core/src/skills/bundled/a2ui/a2ui_form/SKILL.md +202 -0
  114. package/packages/core/src/skills/bundled/a2ui/a2ui_interactive/SKILL.md +206 -0
  115. package/packages/core/src/skills/bundled/agents/agent_spawner/SKILL.md +173 -0
  116. package/packages/core/src/skills/bundled/agents/memory_manager/SKILL.md +143 -0
  117. package/packages/core/src/skills/bundled/agents/research_and_remember/SKILL.md +139 -0
  118. package/packages/core/src/skills/bundled/agents/task_orchestrator/SKILL.md +98 -0
  119. package/packages/core/src/skills/bundled/api/api_client/SKILL.md +132 -0
  120. package/packages/core/src/skills/bundled/cli/cli_pipeline/SKILL.md +135 -0
  121. package/packages/core/src/skills/bundled/cli/cli_safe_exec/SKILL.md +125 -0
  122. package/packages/core/src/skills/bundled/cli/software_engineering/SKILL.md +23 -0
  123. package/packages/core/src/skills/bundled/cron_manager/SKILL.md +188 -0
  124. package/packages/core/src/skills/bundled/cron_reminder/SKILL.md +112 -0
  125. package/packages/core/src/skills/bundled/filesystem/file_manager/SKILL.md +118 -0
  126. package/packages/core/src/skills/bundled/filesystem/file_read_and_summarize/SKILL.md +109 -0
  127. package/packages/core/src/skills/bundled/filesystem/file_writer/SKILL.md +129 -0
  128. package/packages/core/src/skills/bundled/filesystem/workspace_file_operator/SKILL.md +22 -0
  129. package/packages/core/src/skills/bundled/office/office_document_manager/SKILL.md +262 -0
  130. package/packages/core/src/skills/bundled/search_knowledge/capability_discovery/SKILL.md +75 -0
  131. package/packages/core/src/skills/bundled/web/browser_automate/SKILL.md +120 -0
  132. package/packages/core/src/skills/bundled/web/browser_scrape/SKILL.md +109 -0
  133. package/packages/core/src/skills/bundled/web/web_monitor/SKILL.md +127 -0
  134. package/packages/core/src/skills/bundled/web/web_research/SKILL.md +119 -0
  135. package/packages/core/src/skills/bundled-data.generated.ts +731 -2678
  136. package/packages/core/src/skills/skills.test.ts +52 -11
  137. package/packages/core/src/{harness → storage}/boot-id.ts +5 -2
  138. package/packages/core/src/storage/bootstrap.ts +151 -0
  139. package/packages/core/src/storage/causal-events.ts +84 -0
  140. package/packages/core/src/storage/collections.ts +680 -0
  141. package/packages/core/src/storage/crypto.ts +205 -74
  142. package/packages/core/src/{harness/db-helpers.ts → storage/hive.ts} +63 -7
  143. package/packages/core/src/storage/hivedb.ts +61 -0
  144. package/packages/core/src/storage/index.ts +111 -18
  145. package/packages/core/src/storage/model-id.ts +53 -0
  146. package/packages/core/src/storage/onboarding.ts +540 -972
  147. package/packages/core/src/storage/reconcile.ts +238 -0
  148. package/packages/core/src/storage/seed.ts +572 -406
  149. package/packages/core/src/storage/usage.ts +285 -225
  150. package/packages/core/src/storage/user-email.ts +11 -0
  151. package/packages/core/src/swarm/AgentExecutor.ts +1 -1
  152. package/packages/core/src/swarm/EventBridge.ts +1 -1
  153. package/packages/core/src/swarm/index.ts +12 -9
  154. package/packages/core/src/tool-runtime/index.ts +146 -23
  155. package/packages/core/src/tool-runtime/tool-worker.ts +2 -2
  156. package/packages/core/src/tool-runtime/worker-tools.ts +27 -0
  157. package/packages/core/src/{canvas/a2ui-tools.ts → tools/a2ui/index.ts} +17 -8
  158. package/packages/core/src/tools/agents/get-available-models.ts +36 -54
  159. package/packages/core/src/tools/agents/index.ts +784 -292
  160. package/packages/core/src/tools/api/api-request.test.ts +164 -0
  161. package/packages/core/src/tools/api/api-request.ts +174 -0
  162. package/packages/core/src/tools/api/index.ts +16 -0
  163. package/packages/core/src/tools/cli/index.ts +4 -0
  164. package/packages/core/src/tools/core/index.ts +281 -112
  165. package/packages/core/src/tools/cron/index.ts +121 -124
  166. package/packages/core/src/tools/index.ts +63 -78
  167. package/packages/core/src/tools/office/office-escribir-xlsx.ts +3 -1
  168. package/packages/core/src/tools/types.ts +3 -1
  169. package/packages/core/src/tools/web/artifact-inspect.ts +23 -0
  170. package/packages/core/src/tools/web/browser-screenshot.ts +26 -5
  171. package/packages/core/src/tools/web/browser-service.ts +5 -0
  172. package/packages/core/src/tools/web/browser-type.ts +3 -8
  173. package/packages/core/src/tools/web/index.ts +4 -4
  174. package/packages/core/src/voice/index.ts +89 -63
  175. package/packages/core/src/workers/agent.worker.ts +2 -2
  176. package/packages/core/src/workers/workers.test.ts +3 -10
  177. package/scripts/bump-version.ts +248 -0
  178. package/scripts/generate-skill-bundle.ts +108 -0
  179. package/test/agent-loop-terminal-synthesis.test.ts +32 -0
  180. package/test/catalog-agents-stay-enabled.test.ts +117 -0
  181. package/test/causal-events.test.ts +117 -0
  182. package/test/compaction.test.ts +105 -0
  183. package/test/context-compiler.test.ts +269 -0
  184. package/test/curator.test.ts +130 -0
  185. package/test/durable-queue.test.ts +114 -0
  186. package/test/harness-barrel.test.ts +64 -0
  187. package/test/hive-helpers.test.ts +130 -0
  188. package/test/hivedb-search.test.ts +189 -0
  189. package/test/internal-turns.test.ts +166 -0
  190. package/test/job-idempotency.test.ts +68 -0
  191. package/test/job-retry-backoff.test.ts +184 -0
  192. package/test/job-store.test.ts +381 -0
  193. package/test/llm-retry.test.ts +97 -0
  194. package/test/memory-perf.test.ts +774 -0
  195. package/test/minimal-loadout.test.ts +78 -0
  196. package/test/model-catalog.test.ts +105 -0
  197. package/test/preload.ts +12 -0
  198. package/test/reflector.test.ts +320 -0
  199. package/test/retention-cap.test.ts +91 -0
  200. package/test/retired-capabilities-pruned.test.ts +192 -0
  201. package/test/run-store.test.ts +355 -0
  202. package/test/scratchpad.test.ts +74 -0
  203. package/test/secrets-durability.test.ts +119 -0
  204. package/test/seed-model-reseed.test.ts +155 -0
  205. package/test/setup-agent-seed.test.ts +264 -0
  206. package/test/tool-inventory.test.ts +65 -0
  207. package/test/tool-runtime.test.ts +258 -0
  208. package/test/toon.test.ts +429 -0
  209. package/tsconfig.json +2 -0
  210. package/packages/core/src/ace/Curator.ts +0 -158
  211. package/packages/core/src/ace/Reflector.ts +0 -200
  212. package/packages/core/src/ace/index.ts +0 -4
  213. package/packages/core/src/agent/AgentRunner.ts +0 -711
  214. package/packages/core/src/agent/ContextCompiler.ts +0 -567
  215. package/packages/core/src/agent/ContextGuard.ts +0 -91
  216. package/packages/core/src/agent/ConversationStore.ts +0 -254
  217. package/packages/core/src/agent/Hooks.ts +0 -166
  218. package/packages/core/src/agent/StuckLoop.ts +0 -133
  219. package/packages/core/src/agent/providers/LLMClient.ts +0 -149
  220. package/packages/core/src/agent/providers/anthropic.ts +0 -212
  221. package/packages/core/src/agent/providers/openai-compat.ts +0 -231
  222. package/packages/core/src/agent/selectors/PlaybookSelector.ts +0 -121
  223. package/packages/core/src/agent/selectors/SkillSelector.ts +0 -322
  224. package/packages/core/src/agent/selectors/index.ts +0 -6
  225. package/packages/core/src/auth/auth.ts +0 -121
  226. package/packages/core/src/auth/index.ts +0 -1
  227. package/packages/core/src/canvas/CanvasManager.ts +0 -390
  228. package/packages/core/src/canvas/canvas-tools.ts +0 -448
  229. package/packages/core/src/harness/collections.ts +0 -98
  230. package/packages/core/src/harness/goal-verifier.ts +0 -141
  231. package/packages/core/src/harness/harness.test.ts +0 -236
  232. package/packages/core/src/harness/reconcile.ts +0 -149
  233. package/packages/core/src/mcp/MCPToolAdapter.ts +0 -176
  234. package/packages/core/src/multimodal/VisionService.ts +0 -293
  235. package/packages/core/src/scheduler/dag/AgentExecutor.ts +0 -53
  236. package/packages/core/src/scheduler/dag/DAGScheduler.ts +0 -250
  237. package/packages/core/src/scheduler/dag/EventBridge.ts +0 -122
  238. package/packages/core/src/scheduler/dag/TaskGraph.ts +0 -192
  239. package/packages/core/src/scheduler/dag/TaskNode.ts +0 -97
  240. package/packages/core/src/scheduler/dag/TaskResult.ts +0 -22
  241. package/packages/core/src/scheduler/dag/errors.ts +0 -37
  242. package/packages/core/src/scheduler/dag/index.ts +0 -26
  243. package/packages/core/src/scheduler/dag/presets/ResearchPreset.ts +0 -97
  244. package/packages/core/src/scheduler/dag/strategies/ParallelStrategy.ts +0 -21
  245. package/packages/core/src/scheduler/dag/strategies/PriorityStrategy.ts +0 -46
  246. package/packages/core/src/storage/HiveDBStorage.ts +0 -64
  247. package/packages/core/src/storage/SQLiteStorage.ts +0 -414
  248. package/packages/core/src/storage/hiveSeed.ts +0 -308
  249. package/packages/core/src/storage/hiveStorage.test.ts +0 -38
  250. package/packages/core/src/storage/schema.ts +0 -689
  251. package/packages/core/src/storage/storage.test.ts +0 -37
  252. package/packages/core/src/swarm/AgentBus.ts +0 -460
  253. package/packages/core/src/swarm/EventBus.ts +0 -169
  254. package/packages/core/src/swarm/WorkerPool.ts +0 -236
  255. package/packages/core/src/tools/bridge-events.ts +0 -26
  256. package/packages/core/src/tools/canvas/index.ts +0 -375
  257. package/packages/core/src/tools/codebridge/index.ts +0 -342
  258. package/packages/core/src/tools/meeting/index.ts +0 -353
  259. package/packages/core/src/tools/projects/index.ts +0 -37
  260. package/packages/core/src/tools/projects/project-create.ts +0 -94
  261. package/packages/core/src/tools/projects/project-done.ts +0 -66
  262. package/packages/core/src/tools/projects/project-fail.ts +0 -66
  263. package/packages/core/src/tools/projects/project-list.ts +0 -96
  264. package/packages/core/src/tools/projects/project-update.ts +0 -72
  265. package/packages/core/src/tools/projects/task-create.ts +0 -68
  266. package/packages/core/src/tools/projects/task-evaluate.ts +0 -93
  267. package/packages/core/src/tools/projects/task-update.ts +0 -93
  268. package/packages/core/src/tools/voice/index.ts +0 -104
  269. package/packages/core/src/tools/web/api-request.test.ts +0 -170
  270. package/packages/core/src/tools/web/api-request.ts +0 -239
  271. package/test/setup-db.ts +0 -216
  272. /package/packages/core/src/agent/{NativeTools.ts → native-tools.ts} +0 -0
@@ -0,0 +1,258 @@
1
+ import { afterEach, describe, expect, it } from "bun:test"
2
+ import { executeToolBatch, shutdownToolRuntime, type RuntimeTool, type ToolCallLike } from "../packages/core/src/tool-runtime/index.ts"
3
+ import { loadConfig } from "../packages/core/src/config/loader.ts"
4
+
5
+ const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
6
+
7
+ function toolCall(id: string, name: string, args: unknown = {}): ToolCallLike {
8
+ return {
9
+ id,
10
+ function: {
11
+ name,
12
+ arguments: JSON.stringify(args),
13
+ },
14
+ }
15
+ }
16
+
17
+ describe("tool runtime worker pool", () => {
18
+ afterEach(() => {
19
+ shutdownToolRuntime()
20
+ })
21
+
22
+ it("runs multiple tools in parallel through worker scheduling", async () => {
23
+ const tools: RuntimeTool[] = ["slow_a", "slow_b", "slow_c"].map((name) => ({
24
+ name,
25
+ execute: async () => {
26
+ await delay(120)
27
+ return { name }
28
+ },
29
+ }))
30
+
31
+ const startedAt = performance.now()
32
+ const results = await executeToolBatch({
33
+ toolCalls: [
34
+ toolCall("1", "slow_a"),
35
+ toolCall("2", "slow_b"),
36
+ toolCall("3", "slow_c"),
37
+ ],
38
+ allTools: tools,
39
+ toolConfig: {},
40
+ hiveConfig: loadConfig(),
41
+ workerPool: { enabled: true, maxWorkers: 3, toolTimeoutMs: 1000, parallelToolCalls: true },
42
+ })
43
+ const elapsed = performance.now() - startedAt
44
+
45
+ expect(results.map((result) => (result.result as any).name)).toEqual(["slow_a", "slow_b", "slow_c"])
46
+ expect(elapsed).toBeLessThan(260)
47
+ })
48
+
49
+ it("preserves input order when tools complete out of order", async () => {
50
+ const tools: RuntimeTool[] = [
51
+ { name: "first", execute: async () => { await delay(120); return { value: 1 } } },
52
+ { name: "second", execute: async () => { await delay(20); return { value: 2 } } },
53
+ { name: "third", execute: async () => { await delay(60); return { value: 3 } } },
54
+ ]
55
+
56
+ const results = await executeToolBatch({
57
+ toolCalls: [toolCall("1", "first"), toolCall("2", "second"), toolCall("3", "third")],
58
+ allTools: tools,
59
+ toolConfig: {},
60
+ hiveConfig: loadConfig(),
61
+ workerPool: { enabled: true, maxWorkers: 3, toolTimeoutMs: 1000, parallelToolCalls: true },
62
+ })
63
+
64
+ expect(results.map((result) => result.toolName)).toEqual(["first", "second", "third"])
65
+ expect(results.map((result) => (result.result as any).value)).toEqual([1, 2, 3])
66
+ })
67
+
68
+ it("isolates tool errors without cancelling sibling tools", async () => {
69
+ const tools: RuntimeTool[] = [
70
+ { name: "ok", execute: async () => ({ ok: true }) },
71
+ { name: "fail", execute: async () => { throw new Error("boom") } },
72
+ { name: "also_ok", execute: async () => ({ ok: "also" }) },
73
+ ]
74
+
75
+ const results = await executeToolBatch({
76
+ toolCalls: [toolCall("1", "ok"), toolCall("2", "fail"), toolCall("3", "also_ok")],
77
+ allTools: tools,
78
+ toolConfig: {},
79
+ hiveConfig: loadConfig(),
80
+ workerPool: { enabled: true, maxWorkers: 3, toolTimeoutMs: 1000, parallelToolCalls: true },
81
+ })
82
+
83
+ expect(results[0].ok).toBe(true)
84
+ expect(results[1].ok).toBe(false)
85
+ expect((results[1].result as any).error).toBe(true)
86
+ expect((results[1].result as any).message).toContain("boom")
87
+ expect(results[2].ok).toBe(true)
88
+ })
89
+
90
+ it("routes non-reconstructible tools through main-thread RPC", async () => {
91
+ let executedInMainThread = false
92
+ const tools: RuntimeTool[] = [
93
+ {
94
+ name: "ExampleServer__live_tool",
95
+ execute: async (params) => {
96
+ executedInMainThread = true
97
+ return { echoed: params.value }
98
+ },
99
+ },
100
+ {
101
+ name: "other_rpc_tool",
102
+ execute: async () => ({ ok: true }),
103
+ },
104
+ ]
105
+
106
+ const results = await executeToolBatch({
107
+ toolCalls: [
108
+ toolCall("1", "ExampleServer__live_tool", { value: "ok" }),
109
+ toolCall("2", "other_rpc_tool"),
110
+ ],
111
+ allTools: tools,
112
+ toolConfig: {},
113
+ hiveConfig: loadConfig(),
114
+ workerPool: { enabled: true, maxWorkers: 1, toolTimeoutMs: 1000, parallelToolCalls: true },
115
+ })
116
+
117
+ expect(executedInMainThread).toBe(true)
118
+ expect(results[0].ok).toBe(true)
119
+ expect(results[0].result).toEqual({ echoed: "ok" })
120
+ })
121
+
122
+ it("settles in-flight RPC work when the runtime shuts down", async () => {
123
+ const tools: RuntimeTool[] = [
124
+ {
125
+ name: "ExampleServer__slow_live_tool",
126
+ execute: async () => {
127
+ await delay(100)
128
+ return { late: true }
129
+ },
130
+ },
131
+ { name: "other_rpc_tool", execute: async () => ({ ok: true }) },
132
+ ]
133
+
134
+ const pending = executeToolBatch({
135
+ toolCalls: [
136
+ toolCall("1", "ExampleServer__slow_live_tool"),
137
+ toolCall("2", "other_rpc_tool"),
138
+ ],
139
+ allTools: tools,
140
+ toolConfig: {},
141
+ hiveConfig: loadConfig(),
142
+ workerPool: { enabled: true, maxWorkers: 1, toolTimeoutMs: 1000, parallelToolCalls: true },
143
+ })
144
+
145
+ await delay(20)
146
+ shutdownToolRuntime()
147
+
148
+ const results = await pending
149
+ expect(results.every((result) => result.aborted)).toBe(true)
150
+ })
151
+
152
+ it("routes singleton-backed native tools through main-thread RPC", async () => {
153
+ const executed = new Set<string>()
154
+ const tools: RuntimeTool[] = [
155
+ "search_knowledge",
156
+ "save_note",
157
+ "memory_write",
158
+ "task_status",
159
+ "project_create",
160
+ ].map((name) => ({
161
+ name,
162
+ execute: async () => {
163
+ executed.add(name)
164
+ return { name, mainThread: true }
165
+ },
166
+ }))
167
+
168
+ const results = await executeToolBatch({
169
+ toolCalls: [
170
+ toolCall("1", "search_knowledge", { query: "A2UI" }),
171
+ toolCall("2", "save_note", { key: "k", value: "v" }),
172
+ toolCall("3", "memory_write", { key: "m", value: "v" }),
173
+ toolCall("4", "task_status", { task_id: "t1" }),
174
+ toolCall("5", "project_create", { name: "release" }),
175
+ ],
176
+ allTools: tools,
177
+ toolConfig: {},
178
+ hiveConfig: loadConfig(),
179
+ workerPool: { enabled: true, maxWorkers: 2, toolTimeoutMs: 1000, parallelToolCalls: true },
180
+ })
181
+
182
+ expect(results.every((result) => result.ok)).toBe(true)
183
+ expect(executed).toEqual(new Set(["search_knowledge", "save_note", "memory_write", "task_status", "project_create"]))
184
+ expect(results.map((result) => (result.result as any).mainThread)).toEqual([true, true, true, true, true])
185
+ })
186
+
187
+ it("marks timed out tools and keeps completed tools", async () => {
188
+ const tools: RuntimeTool[] = [
189
+ { name: "fast", execute: async () => ({ done: true }) },
190
+ { name: "timeout", execute: async () => { await delay(400); return { late: true } } },
191
+ ]
192
+
193
+ const results = await executeToolBatch({
194
+ toolCalls: [toolCall("1", "fast"), toolCall("2", "timeout")],
195
+ allTools: tools,
196
+ toolConfig: {},
197
+ hiveConfig: loadConfig(),
198
+ workerPool: { enabled: true, maxWorkers: 2, toolTimeoutMs: 150, parallelToolCalls: true },
199
+ })
200
+
201
+ expect(results[0].ok).toBe(true)
202
+ expect(results[1].ok).toBe(false)
203
+ expect(results[1].timedOut).toBe(true)
204
+ expect((results[1].result as any).error).toBe(true)
205
+ })
206
+
207
+ it("marks queued and running work as aborted", async () => {
208
+ const controller = new AbortController()
209
+ const tools: RuntimeTool[] = [
210
+ { name: "slow_one", execute: async () => { await delay(200); return { done: 1 } } },
211
+ { name: "slow_two", execute: async () => { await delay(200); return { done: 2 } } },
212
+ ]
213
+
214
+ setTimeout(() => controller.abort(), 30)
215
+
216
+ const results = await executeToolBatch({
217
+ toolCalls: [toolCall("1", "slow_one"), toolCall("2", "slow_two")],
218
+ allTools: tools,
219
+ toolConfig: {},
220
+ hiveConfig: loadConfig(),
221
+ workerPool: { enabled: true, maxWorkers: 1, toolTimeoutMs: 1000, parallelToolCalls: true },
222
+ signal: controller.signal,
223
+ })
224
+
225
+ expect(results.every((result) => result.aborted)).toBe(true)
226
+ expect(results.map((result) => result.toolName)).toEqual(["slow_one", "slow_two"])
227
+ })
228
+
229
+ it("passes toolConfig (including agent_id) through to a single tool call's config.configurable", async () => {
230
+ // Single tool call → always the serial/main-thread path (executeToolBatch's
231
+ // `toolCalls.length <= 1` branch) — exactly what task_delegate/agent_create/
232
+ // bus_publish go through. Regression test for a real bug: toolConfig never
233
+ // included agent_id, so config.configurable.agent_id was always undefined
234
+ // inside every tool's execute() (task_delegate's parent-agent lookup always
235
+ // failed as a result — see agent-loop.ts's toolConfig construction).
236
+ let seenConfigurable: Record<string, unknown> | undefined;
237
+ const tools: RuntimeTool[] = [
238
+ {
239
+ name: "whoami",
240
+ execute: async (_params, config) => {
241
+ seenConfigurable = config?.configurable;
242
+ return { ok: true };
243
+ },
244
+ },
245
+ ];
246
+
247
+ await executeToolBatch({
248
+ toolCalls: [toolCall("1", "whoami")],
249
+ allTools: tools,
250
+ toolConfig: { agent_id: "bee-coordinator", user_id: "u1", thread_id: "t1", channel: "webchat", workspace: "/tmp/ws" },
251
+ hiveConfig: loadConfig(),
252
+ });
253
+
254
+ expect(seenConfigurable?.agent_id).toBe("bee-coordinator");
255
+ expect(seenConfigurable?.user_id).toBe("u1");
256
+ expect(seenConfigurable?.workspace).toBe("/tmp/ws");
257
+ })
258
+ })
@@ -0,0 +1,429 @@
1
+ /**
2
+ * TOON Format Utility Tests
3
+ *
4
+ * Tests for TOON (Token-Oriented Object Notation) implementation
5
+ * using toon-format-parser library with native compression analysis
6
+ */
7
+
8
+ import { describe, test, expect } from 'bun:test'
9
+ import {
10
+ stringify,
11
+ formatToolResult,
12
+ formatMCPResponse,
13
+ formatSkillOutput,
14
+ formatContext,
15
+ estimateTokens,
16
+ getCompressionAnalysis,
17
+ withToonFormat,
18
+ type ToonStringifyResult,
19
+ } from '../packages/core/src/utils/toon.ts'
20
+
21
+ describe('TOON Utility', () => {
22
+ describe('stringify()', () => {
23
+ test('convierte objeto simple a TOON', () => {
24
+ const data = { name: 'John', age: 30, city: 'New York' }
25
+ const result = stringify(data)
26
+
27
+ expect(result.content).toBeDefined()
28
+ expect(result.format).toBe('toon')
29
+ expect(result.originalSize).toBeGreaterThan(0)
30
+ expect(result.toonSize).toBeGreaterThan(0)
31
+ expect(typeof result.tokensSaved).toBe('number')
32
+ expect(typeof result.savingsPercent).toBe('number')
33
+ expect(typeof result.costSaved).toBe('number')
34
+ // New complete metrics
35
+ expect(typeof result.jsonBytes).toBe('number')
36
+ expect(typeof result.toonBytes).toBe('number')
37
+ expect(typeof result.savedBytes).toBe('number')
38
+ expect(typeof result.savedPercent).toBe('number')
39
+ expect(typeof result.jsonTokens).toBe('number')
40
+ expect(typeof result.toonTokens).toBe('number')
41
+ expect(typeof result.savedTokensPercent).toBe('number')
42
+ })
43
+
44
+ test('convierte objeto anidado a TOON', () => {
45
+ const data = {
46
+ user: {
47
+ name: 'Alice',
48
+ email: 'alice@example.com',
49
+ preferences: {
50
+ theme: 'dark',
51
+ notifications: true,
52
+ },
53
+ },
54
+ projects: [
55
+ { id: 1, name: 'Project A', status: 'active' },
56
+ { id: 2, name: 'Project B', status: 'completed' },
57
+ ],
58
+ }
59
+ const result = stringify(data)
60
+
61
+ expect(result.content).toBeDefined()
62
+ expect(result.format).toBe('toon')
63
+ expect(result.toonSize).toBeGreaterThan(0)
64
+ })
65
+
66
+ test('calcula tokens ahorrados correctamente', () => {
67
+ const data = {
68
+ success: true,
69
+ data: {
70
+ emails: [
71
+ { id: '1', from: 'alice@example.com', subject: 'Hello' },
72
+ { id: '2', from: 'bob@example.com', subject: 'World' },
73
+ ],
74
+ },
75
+ }
76
+ const result = stringify(data)
77
+
78
+ // savingsPercent debería ser >= 0 (usamos Math.max)
79
+ expect(result.savingsPercent).toBeGreaterThanOrEqual(0)
80
+ expect(result.tokensSaved).toBeGreaterThanOrEqual(0)
81
+ })
82
+
83
+ test('calcula costo USD con modelo', () => {
84
+ const data = { message: 'Hello World', count: 42 }
85
+ const result = stringify(data, 'gemini-2.0-flash-exp')
86
+
87
+ expect(result.costSaved).toBeGreaterThanOrEqual(0)
88
+
89
+ // Si hay ahorro de tokens, debería haber ahorro de costo
90
+ if (result.tokensSaved > 0) {
91
+ expect(result.costSaved).toBeGreaterThan(0)
92
+ }
93
+ })
94
+
95
+ test('maneja arrays grandes', () => {
96
+ const data = Array.from({ length: 100 }, (_, i) => ({
97
+ id: i,
98
+ name: `Item ${i}`,
99
+ value: Math.random(),
100
+ }))
101
+ const result = stringify(data)
102
+
103
+ expect(result.content).toBeDefined()
104
+ expect(result.format).toBe('toon')
105
+ })
106
+
107
+ test('maneja objetos vacíos', () => {
108
+ const result = stringify({})
109
+
110
+ expect(result.content).toBeDefined()
111
+ expect(result.format).toBe('toon')
112
+ })
113
+
114
+ test('maneja valores null y undefined', () => {
115
+ const data = {
116
+ nullValue: null,
117
+ undefinedValue: undefined,
118
+ stringValue: 'test',
119
+ }
120
+ const result = stringify(data)
121
+
122
+ expect(result.content).toBeDefined()
123
+ expect(result.format).toBe('toon')
124
+ })
125
+
126
+ test('maneja primitivos', () => {
127
+ expect(stringify('hello').content).toBeDefined()
128
+ expect(stringify(42).content).toBeDefined()
129
+ expect(stringify(true).content).toBeDefined()
130
+ expect(stringify(null).content).toBeDefined()
131
+ })
132
+ })
133
+
134
+ describe('formatToolResult()', () => {
135
+ test('formatea resultado de herramienta', () => {
136
+ const toolResult = {
137
+ success: true,
138
+ data: { items: [1, 2, 3] },
139
+ timestamp: new Date().toISOString(),
140
+ }
141
+ const result = formatToolResult(toolResult, 'gpt-4o-mini')
142
+
143
+ expect(typeof result).toBe('string')
144
+ expect(result.length).toBeGreaterThan(0)
145
+ })
146
+
147
+ test('formatea error de herramienta', () => {
148
+ const errorResult = {
149
+ error: true,
150
+ tool: 'test_tool',
151
+ message: 'Something went wrong',
152
+ timestamp: new Date().toISOString(),
153
+ }
154
+ const result = formatToolResult(errorResult)
155
+
156
+ expect(typeof result).toBe('string')
157
+ expect(result).toContain('error')
158
+ })
159
+
160
+ test('maneja resultado sin modelo', () => {
161
+ const result = formatToolResult({ data: 'test' })
162
+
163
+ expect(typeof result).toBe('string')
164
+ })
165
+ })
166
+
167
+ describe('formatMCPResponse()', () => {
168
+ test('formatea respuesta MCP', () => {
169
+ const mcpResponse = {
170
+ content: [
171
+ { type: 'text', text: 'Hello from MCP' },
172
+ ],
173
+ isError: false,
174
+ }
175
+ const result = formatMCPResponse(mcpResponse, 'gemini-2.0-flash-exp')
176
+
177
+ expect(typeof result).toBe('string')
178
+ expect(result.length).toBeGreaterThan(0)
179
+ })
180
+
181
+ test('formatea lista de herramientas MCP', () => {
182
+ const toolsResponse = {
183
+ tools: [
184
+ {
185
+ name: 'search_web',
186
+ description: 'Search the web',
187
+ inputSchema: { type: 'object', properties: { query: { type: 'string' } } },
188
+ },
189
+ {
190
+ name: 'send_email',
191
+ description: 'Send an email',
192
+ inputSchema: { type: 'object', properties: { to: { type: 'string' } } },
193
+ },
194
+ ],
195
+ }
196
+ const result = formatMCPResponse(toolsResponse)
197
+
198
+ expect(typeof result).toBe('string')
199
+ })
200
+ })
201
+
202
+ describe('formatSkillOutput()', () => {
203
+ test('formatea output de skill', () => {
204
+ const skillOutput = {
205
+ skill: 'pdf',
206
+ result: { pages: 10, text: 'Extracted text...' },
207
+ }
208
+ const result = formatSkillOutput(skillOutput, 'claude-3-5-sonnet')
209
+
210
+ expect(typeof result).toBe('string')
211
+ })
212
+ })
213
+
214
+ describe('formatContext()', () => {
215
+ test('formatea contexto del agente', () => {
216
+ const contextData = {
217
+ ethics: ['Be helpful', 'Be honest'],
218
+ notes: { key1: 'value1', key2: 'value2' },
219
+ projects: { active: 3, completed: 10 },
220
+ }
221
+ const result = formatContext(contextData)
222
+
223
+ expect(typeof result).toBe('string')
224
+ expect(result.length).toBeGreaterThan(0)
225
+ })
226
+
227
+ test('formatea scratchpad data', () => {
228
+ const scratchpadData = {
229
+ currentTask: 'Research hotels',
230
+ lastStep: 'Searched Google',
231
+ nextStep: 'Compare prices',
232
+ }
233
+ const result = formatContext(scratchpadData)
234
+
235
+ expect(typeof result).toBe('string')
236
+ })
237
+ })
238
+
239
+ describe('estimateTokens()', () => {
240
+ test('estima tokens para texto vacío', () => {
241
+ expect(estimateTokens('')).toBe(0)
242
+ })
243
+
244
+ test('estima tokens para texto corto', () => {
245
+ expect(estimateTokens('Hello World')).toBe(3) // ceil(11/4) = 3
246
+ })
247
+
248
+ test('estima tokens para texto largo', () => {
249
+ const text = 'A'.repeat(1000)
250
+ expect(estimateTokens(text)).toBe(250) // 1000/4 = 250
251
+ })
252
+
253
+ test('estima tokens para JSON', () => {
254
+ const json = JSON.stringify({ name: 'test', value: 42 })
255
+ expect(estimateTokens(json)).toBeGreaterThan(0)
256
+ })
257
+ })
258
+
259
+ describe('getCompressionAnalysis()', () => {
260
+ test('retorna análisis de compresión completo', () => {
261
+ const data = {
262
+ users: [
263
+ { id: 1, name: 'Alice', email: 'alice@example.com' },
264
+ { id: 2, name: 'Bob', email: 'bob@example.com' },
265
+ ],
266
+ metadata: {
267
+ total: 2,
268
+ page: 1,
269
+ },
270
+ }
271
+ const analysis = getCompressionAnalysis(data)
272
+
273
+ expect(analysis).toBeDefined()
274
+ expect(analysis.jsonBytes).toBeGreaterThan(0)
275
+ expect(analysis.toonBytes).toBeGreaterThan(0)
276
+ expect(analysis.jsonTokens).toBeGreaterThan(0)
277
+ expect(analysis.toonTokens).toBeGreaterThan(0)
278
+ expect(typeof analysis.savedTokens).toBe('number')
279
+ expect(typeof analysis.savedPercent).toBe('number')
280
+ expect(typeof analysis.savedTokensPercent).toBe('number')
281
+ expect(typeof analysis.savedBytes).toBe('number')
282
+ })
283
+
284
+ test('muestra ahorro significativo en datos estructurados', () => {
285
+ const data = Array.from({ length: 50 }, (_, i) => ({
286
+ id: i,
287
+ name: `Item ${i}`,
288
+ description: `Description for item ${i}`,
289
+ }))
290
+ const analysis = getCompressionAnalysis(data)
291
+
292
+ // En datos estructurados grandes, TOON debería ahorrar
293
+ expect(analysis.savedTokens).toBeGreaterThan(0)
294
+ expect(analysis.savedPercent).toBeGreaterThan(0)
295
+ })
296
+ })
297
+
298
+ describe('withToonFormat()', () => {
299
+ test('formatea resultado de función asíncrona', async () => {
300
+ const mockFn = async () => ({ result: 'success', data: [1, 2, 3] })
301
+ const result = await withToonFormat('test_tool', mockFn, 'gpt-4o')
302
+
303
+ expect(typeof result).toBe('string')
304
+ expect(result.length).toBeGreaterThan(0)
305
+ })
306
+
307
+ test('maneja error de función asíncrona', async () => {
308
+ const mockFn = async () => {
309
+ throw new Error('Test error')
310
+ }
311
+ const result = await withToonFormat('failing_tool', mockFn)
312
+
313
+ expect(typeof result).toBe('string')
314
+ expect(result).toContain('error')
315
+ expect(result).toContain('Test error')
316
+ })
317
+
318
+ test('registra duración de ejecución', async () => {
319
+ const mockFn = async () => {
320
+ await new Promise(resolve => setTimeout(resolve, 10))
321
+ return { done: true }
322
+ }
323
+ const result = await withToonFormat('slow_tool', mockFn)
324
+
325
+ expect(typeof result).toBe('string')
326
+ })
327
+ })
328
+
329
+ describe('Integration: Real-world scenarios', () => {
330
+ test('simula flujo real: tool result → LLM message', () => {
331
+ // 1. Tool ejecuta y retorna dato
332
+ const toolResult = {
333
+ success: true,
334
+ url: 'https://example.com',
335
+ content: 'Page content here',
336
+ statusCode: 200,
337
+ }
338
+
339
+ // 2. Formatear para LLM
340
+ const formattedForLLM = formatToolResult(toolResult, 'gpt-4o-mini')
341
+
342
+ // 3. Verificar formato
343
+ expect(typeof formattedForLLM).toBe('string')
344
+ expect(formattedForLLM.length).toBeGreaterThan(0)
345
+ })
346
+
347
+ test('simula respuesta MCP compleja', () => {
348
+ const mcpResponse = {
349
+ content: [
350
+ {
351
+ type: 'text',
352
+ text: 'Found 5 emails',
353
+ },
354
+ {
355
+ type: 'json',
356
+ data: {
357
+ emails: [
358
+ { id: '1', from: 'a@b.com', subject: 'Test 1' },
359
+ { id: '2', from: 'c@d.com', subject: 'Test 2' },
360
+ ],
361
+ },
362
+ },
363
+ ],
364
+ metadata: {
365
+ server: 'gmail',
366
+ timestamp: new Date().toISOString(),
367
+ },
368
+ }
369
+
370
+ const formatted = formatMCPResponse(mcpResponse)
371
+
372
+ expect(typeof formatted).toBe('string')
373
+ expect(formatted.length).toBeGreaterThan(0)
374
+ })
375
+
376
+ test('comparación JSON vs TOON en datos típicos', () => {
377
+ const typicalData = {
378
+ agent: {
379
+ id: 'coordinator',
380
+ name: 'Main Agent',
381
+ status: 'thinking',
382
+ },
383
+ conversation: {
384
+ threadId: 'thread-123',
385
+ messages: [
386
+ { role: 'user', content: 'Hello' },
387
+ { role: 'assistant', content: 'Hi there!' },
388
+ ],
389
+ },
390
+ tools: [
391
+ { name: 'search', status: 'available' },
392
+ { name: 'browser', status: 'busy' },
393
+ ],
394
+ }
395
+
396
+ const jsonSize = JSON.stringify(typicalData).length
397
+ const toonResult = stringify(typicalData)
398
+
399
+ expect(toonResult.originalSize).toBe(jsonSize)
400
+ expect(toonResult.toonSize).toBeGreaterThan(0)
401
+ expect(toonResult.savingsPercent).toBeGreaterThanOrEqual(0)
402
+ })
403
+ })
404
+
405
+ describe('Performance', () => {
406
+ test('stringify es rápido para objetos pequeños', () => {
407
+ const data = { key: 'value', num: 42 }
408
+ const start = performance.now()
409
+ stringify(data)
410
+ const duration = performance.now() - start
411
+
412
+ expect(duration).toBeLessThan(10) // < 10ms
413
+ })
414
+
415
+ test('stringify es razonable para objetos grandes', () => {
416
+ const data = Array.from({ length: 500 }, (_, i) => ({
417
+ id: i,
418
+ name: `Item ${i}`,
419
+ description: `Description for item ${i}`,
420
+ }))
421
+
422
+ const start = performance.now()
423
+ stringify(data)
424
+ const duration = performance.now() - start
425
+
426
+ expect(duration).toBeLessThan(100) // < 100ms
427
+ })
428
+ })
429
+ })
package/tsconfig.json CHANGED
@@ -25,6 +25,8 @@
25
25
  "declarationMap": true,
26
26
 
27
27
  "paths": {
28
+ "@johpaz/hive-sdk": ["./packages/core/src/index.ts"],
29
+ "@johpaz/hive-sdk/*": ["./packages/core/src/*"],
28
30
  "@hive/core": ["./packages/core/src/index.ts"],
29
31
  "@hive/core/*": ["./packages/core/src/*"]
30
32
  }