@johpaz/hive-sdk 0.1.3 → 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 -20
  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 -17
  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
@@ -1,254 +0,0 @@
1
- /**
2
- * Conversation Store — persists message history in the `conversations` table.
3
- * Replaces the LangGraph BunSqliteSaver + lg_checkpoints approach.
4
- *
5
- * Also manages: summaries, scratchpad.
6
- */
7
-
8
- import { getDb } from "../storage/SQLiteStorage.ts"
9
- import { logger } from "../utils/logger.ts"
10
- import type { LLMMessage, ContentPart } from "./providers/LLMClient"
11
- import { estimateTokens } from "../utils/toon.ts"
12
-
13
- const log = logger.child("conv-store")
14
-
15
- // ─── Types ────────────────────────────────────────────────────────────────────
16
-
17
- export interface StoredMessage {
18
- id: number
19
- thread_id: string
20
- channel: string
21
- role: "user" | "assistant" | "tool" | "system"
22
- content: string
23
- tool_calls_json: string | null
24
- tool_call_id: string | null
25
- reasoning_content: string | null // Kimi K2 thinking — must be round-tripped
26
- content_multimodal: string | null // JSON array of ContentPart[]
27
- token_count: number
28
- created_at: number
29
- }
30
-
31
- // ─── Message operations ───────────────────────────────────────────────────────
32
-
33
- export function addMessage(
34
- threadId: string,
35
- role: StoredMessage["role"],
36
- content: string | ContentPart[],
37
- opts?: {
38
- channel?: string
39
- tool_calls?: LLMMessage["tool_calls"]
40
- tool_call_id?: string
41
- reasoning_content?: string
42
- }
43
- ): number {
44
- const db = getDb()
45
- // Handle multimodal content by extracting text for the content column
46
- const textContent = typeof content === "string"
47
- ? content
48
- : Array.isArray(content)
49
- ? content.filter(p => p.type === "text").map(p => (p as any).text).join("\n")
50
- : String(content)
51
-
52
- const content_multimodal = Array.isArray(content) ? JSON.stringify(content) : null
53
- const tool_calls_json = opts?.tool_calls ? JSON.stringify(opts.tool_calls) : null
54
-
55
- const result = db.query(`
56
- INSERT INTO conversations (thread_id, channel, role, content, content_multimodal, tool_calls_json, tool_call_id, reasoning_content, token_count, updated_at)
57
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, unixepoch())
58
- RETURNING id
59
- `).get(
60
- threadId,
61
- opts?.channel ?? "webchat",
62
- role,
63
- textContent,
64
- content_multimodal,
65
- tool_calls_json,
66
- opts?.tool_call_id ?? null,
67
- opts?.reasoning_content ?? null,
68
- // Estimate tokens: content + tool_calls JSON
69
- Math.max(1, estimateTokens(textContent) + estimateTokens(tool_calls_json ?? "")),
70
- ) as { id: number }
71
-
72
- return result.id
73
- }
74
-
75
- /**
76
- * Returns all messages for the thread ordered oldest → newest.
77
- */
78
- export function getHistory(threadId: string, limit = 200): StoredMessage[] {
79
- const db = getDb()
80
- return db.query(`
81
- SELECT * FROM conversations
82
- WHERE thread_id = ?
83
- ORDER BY id ASC
84
- LIMIT ?
85
- `).all(threadId, limit) as StoredMessage[]
86
- }
87
-
88
- /**
89
- * Returns only the last N messages (oldest → newest order),
90
- * with leading orphaned tool messages stripped from the window start.
91
- *
92
- * A tool message is "orphaned" when the assistant message that issued its
93
- * tool_call_id is not present in the loaded window (it was compacted away).
94
- * Sending orphaned tool messages to the LLM causes provider errors.
95
- */
96
- export function getRecentMessages(threadId: string, n: number): StoredMessage[] {
97
- const db = getDb()
98
- const rows = db.query(`
99
- SELECT * FROM conversations
100
- WHERE thread_id = ?
101
- ORDER BY id DESC
102
- LIMIT ?
103
- `).all(threadId, n) as StoredMessage[]
104
- return stripLeadingOrphanedTools(rows.reverse())
105
- }
106
-
107
- function stripLeadingOrphanedTools(rows: StoredMessage[]): StoredMessage[] {
108
- // Collect all tool_call_ids referenced by assistant messages in this window
109
- const knownIds = new Set<string>()
110
- for (const r of rows) {
111
- if (r.role === "assistant" && r.tool_calls_json) {
112
- try {
113
- const tcs = JSON.parse(r.tool_calls_json) as Array<{ id: string }>
114
- for (const tc of tcs) knownIds.add(tc.id)
115
- } catch { /* ignore malformed JSON */ }
116
- }
117
- }
118
-
119
- // Drop tool messages at the start of the window whose assistant is missing
120
- let start = 0
121
- while (
122
- start < rows.length &&
123
- rows[start].role === "tool" &&
124
- rows[start].tool_call_id !== null &&
125
- !knownIds.has(rows[start].tool_call_id!)
126
- ) {
127
- start++
128
- }
129
-
130
- if (start > 0) {
131
- log.warn(`[conv-store] Stripped ${start} leading orphaned tool message(s) from window (tool_call_ids outside window)`)
132
- }
133
- return start > 0 ? rows.slice(start) : rows
134
- }
135
-
136
- export function getMessageCount(threadId: string): number {
137
- const db = getDb()
138
- const row = db.query(
139
- "SELECT COUNT(*) as cnt FROM conversations WHERE thread_id = ?"
140
- ).get(threadId) as { cnt: number }
141
- return row.cnt
142
- }
143
-
144
- export function getTotalTokens(threadId: string): number {
145
- const db = getDb()
146
- const row = db.query(
147
- "SELECT COALESCE(SUM(token_count), 0) as total FROM conversations WHERE thread_id = ?"
148
- ).get(threadId) as { total: number }
149
- return row.total
150
- }
151
-
152
- /**
153
- * Messages after a given message ID (for incremental summary updates).
154
- */
155
- export function getMessagesAfter(threadId: string, afterId: number): StoredMessage[] {
156
- const db = getDb()
157
- return db.query(`
158
- SELECT * FROM conversations
159
- WHERE thread_id = ? AND id > ?
160
- ORDER BY id ASC
161
- `).all(threadId, afterId) as StoredMessage[]
162
- }
163
-
164
- // ─── Convert stored messages → LLMMessage array ───────────────────────────────
165
-
166
- export function toAPIMessages(rows: StoredMessage[]): LLMMessage[] {
167
- return rows.map((r) => {
168
- let content: string | ContentPart[] = r.content
169
- if (r.content_multimodal) {
170
- try { content = JSON.parse(r.content_multimodal) } catch { /* ignore */ }
171
- }
172
- const msg: LLMMessage = { role: r.role, content }
173
- if (r.tool_calls_json) {
174
- try { msg.tool_calls = JSON.parse(r.tool_calls_json) } catch { /* ignore */ }
175
- }
176
- if (r.tool_call_id) msg.tool_call_id = r.tool_call_id
177
- if (r.reasoning_content) msg.reasoning_content = r.reasoning_content
178
- return msg
179
- })
180
- }
181
-
182
- // ─── Summaries ────────────────────────────────────────────────────────────────
183
-
184
- export interface Summary {
185
- summary: string
186
- last_message_id: number
187
- messages_covered: number
188
- }
189
-
190
- export function getSummary(threadId: string): Summary | null {
191
- const db = getDb()
192
- return db.query(
193
- "SELECT summary, last_message_id, messages_covered FROM summaries WHERE thread_id = ?"
194
- ).get(threadId) as Summary | null
195
- }
196
-
197
- export function saveSummary(
198
- threadId: string,
199
- summary: string,
200
- messagesCovered: number,
201
- lastMessageId: number
202
- ): void {
203
- const db = getDb()
204
- db.query(`
205
- INSERT INTO summaries (thread_id, summary, messages_covered, last_message_id)
206
- VALUES (?, ?, ?, ?)
207
- ON CONFLICT(thread_id) DO UPDATE SET
208
- summary = excluded.summary,
209
- messages_covered = excluded.messages_covered,
210
- last_message_id = excluded.last_message_id,
211
- updated_at = unixepoch()
212
- `).run(threadId, summary, messagesCovered, lastMessageId)
213
- }
214
-
215
- import { getHiveDB } from "../storage/HiveDBStorage.ts";
216
-
217
- interface ScratchpadDoc {
218
- threadId: string;
219
- key: string;
220
- value: string;
221
- updatedAt: number;
222
- }
223
-
224
- function scratchpadDocId(threadId: string, key: string): string {
225
- return `${threadId}:${key}`;
226
- }
227
-
228
- // ─── Scratchpad ───────────────────────────────────────────────────────────────
229
-
230
- export async function saveScratchpadNote(
231
- threadId: string,
232
- key: string,
233
- value: string,
234
- _source?: string
235
- ): Promise<void> {
236
- const db = await getHiveDB();
237
- const col = db.collection<ScratchpadDoc>("scratchpad");
238
- await col.put(scratchpadDocId(threadId, key), { threadId, key, value, updatedAt: Date.now() });
239
- }
240
-
241
- export async function getScratchpad(threadId: string): Promise<Array<{ key: string; value: string }>> {
242
- const db = await getHiveDB();
243
- const col = db.collection<ScratchpadDoc>("scratchpad");
244
- const entries = await col.scan();
245
- return entries
246
- .filter(e => e.doc.threadId === threadId)
247
- .map(e => ({ key: e.doc.key, value: e.doc.value }));
248
- }
249
-
250
- export async function deleteScratchpadNote(threadId: string, key: string): Promise<void> {
251
- const db = await getHiveDB();
252
- const col = db.collection<ScratchpadDoc>("scratchpad");
253
- await col.delete(scratchpadDocId(threadId, key));
254
- }
@@ -1,166 +0,0 @@
1
- import type { Config } from "../config/loader.ts";
2
- import { logger } from "../utils/logger.ts";
3
- import * as childProcess from "node:child_process";
4
-
5
- export type HookName =
6
- | "before_model_resolve"
7
- | "before_prompt_build"
8
- | "before_tool_call"
9
- | "after_tool_call"
10
- | "tool_result_persist"
11
- | "before_compaction"
12
- | "after_compaction"
13
- | "message_received"
14
- | "message_sending"
15
- | "message_sent"
16
- | "session_start"
17
- | "session_end"
18
- | "gateway_start"
19
- | "gateway_stop";
20
-
21
- export interface HookContext {
22
- sessionId?: string;
23
- agentId?: string;
24
- data?: Record<string, unknown>;
25
- timestamp: Date;
26
- }
27
-
28
- export type HookHandler = (context: HookContext) => Promise<Record<string, unknown> | void>;
29
-
30
- export class HookPipeline {
31
- private config: Config;
32
- private log = logger.child("hooks");
33
- private handlers: Map<HookName, HookHandler[]> = new Map();
34
- private scriptCache: Map<HookName, string> = new Map();
35
-
36
- constructor(config: Config) {
37
- this.config = config;
38
- this.loadScripts();
39
- }
40
-
41
- private loadScripts(): void {
42
- const scripts = this.config.hooks?.scripts;
43
- if (!scripts) return;
44
-
45
- const hookNames: HookName[] = [
46
- "before_model_resolve",
47
- "before_prompt_build",
48
- "before_tool_call",
49
- "after_tool_call",
50
- "tool_result_persist",
51
- "before_compaction",
52
- "after_compaction",
53
- "message_received",
54
- "message_sending",
55
- "message_sent",
56
- "session_start",
57
- "session_end",
58
- "gateway_start",
59
- "gateway_stop",
60
- ];
61
-
62
- for (const name of hookNames) {
63
- const script = scripts[name];
64
- if (script) {
65
- this.scriptCache.set(name, script);
66
- this.log.debug(`Loaded script for hook: ${name}`);
67
- }
68
- }
69
- }
70
-
71
- registerHandler(name: HookName, handler: HookHandler): void {
72
- const handlers = this.handlers.get(name) ?? [];
73
- handlers.push(handler);
74
- this.handlers.set(name, handlers);
75
- this.log.debug(`Registered handler for hook: ${name}`);
76
- }
77
-
78
- unregisterHandler(name: HookName, handler: HookHandler): boolean {
79
- const handlers = this.handlers.get(name);
80
- if (!handlers) return false;
81
-
82
- const index = handlers.indexOf(handler);
83
- if (index >= 0) {
84
- handlers.splice(index, 1);
85
- return true;
86
- }
87
- return false;
88
- }
89
-
90
- async execute(name: HookName, context: HookContext): Promise<Record<string, unknown> | void> {
91
- this.log.debug(`Executing hook: ${name}`, { sessionId: context.sessionId });
92
-
93
- const handlers = this.handlers.get(name) ?? [];
94
- for (const handler of handlers) {
95
- try {
96
- await handler(context);
97
- } catch (error) {
98
- this.log.error(`Handler failed for ${name}: ${(error as Error).message}`);
99
- }
100
- }
101
-
102
- const script = this.scriptCache.get(name);
103
- if (script) {
104
- try {
105
- const result = await this.executeScript(script, context);
106
- return result;
107
- } catch (error) {
108
- this.log.error(`Script failed for ${name}: ${(error as Error).message}`);
109
- }
110
- }
111
- }
112
-
113
- private async executeScript(
114
- scriptPath: string,
115
- context: HookContext
116
- ): Promise<Record<string, unknown> | void> {
117
- return new Promise((resolve, reject) => {
118
- const payload = JSON.stringify(context);
119
-
120
- const proc = childProcess.spawn(scriptPath, [], {
121
- stdio: ["pipe", "pipe", "pipe"],
122
- shell: true,
123
- }) as any;
124
-
125
- let stdout = "";
126
- let stderr = "";
127
-
128
- proc.stdout?.on("data", (data) => {
129
- stdout += data.toString();
130
- });
131
-
132
- proc.stderr?.on("data", (data) => {
133
- stderr += data.toString();
134
- });
135
-
136
- proc.on("close", (code) => {
137
- if (code === 0 && stdout) {
138
- try {
139
- resolve(JSON.parse(stdout));
140
- } catch {
141
- resolve();
142
- }
143
- } else if (stderr) {
144
- reject(new Error(stderr));
145
- } else {
146
- resolve();
147
- }
148
- });
149
-
150
- proc.on("error", (error) => {
151
- reject(error);
152
- });
153
-
154
- proc.stdin?.write(payload);
155
- proc.stdin?.end();
156
- });
157
- }
158
-
159
- hasHandlers(name: HookName): boolean {
160
- return (this.handlers.get(name)?.length ?? 0) > 0 || this.scriptCache.has(name);
161
- }
162
- }
163
-
164
- export function createHookPipeline(config: Config): HookPipeline {
165
- return new HookPipeline(config);
166
- }
@@ -1,133 +0,0 @@
1
- import type { Config } from "../config/loader.ts";
2
- import { logger } from "../utils/logger.ts";
3
- import { hashObject } from "../utils/crypto.ts";
4
-
5
- interface ToolCallRecord {
6
- toolName: string;
7
- argsHash: string;
8
- errorMessage?: string;
9
- timestamp: number;
10
- }
11
-
12
- interface StuckLoopState {
13
- detected: boolean;
14
- toolName: string;
15
- count: number;
16
- lastError?: string;
17
- }
18
-
19
- export class StuckLoopDetector {
20
- private log = logger.child("stuck-loop");
21
- private history: Map<string, ToolCallRecord[]> = new Map();
22
- private readonly maxHistoryPerSession = 50;
23
- private readonly triggerThreshold = 3;
24
-
25
- constructor(_config: Config) {}
26
-
27
- recordToolCall(
28
- sessionId: string,
29
- toolName: string,
30
- args: Record<string, unknown>,
31
- error?: string
32
- ): void {
33
- let sessionHistory = this.history.get(sessionId);
34
- if (!sessionHistory) {
35
- sessionHistory = [];
36
- this.history.set(sessionId, sessionHistory);
37
- }
38
-
39
- const record: ToolCallRecord = {
40
- toolName,
41
- argsHash: hashObject(args),
42
- errorMessage: error,
43
- timestamp: Date.now(),
44
- };
45
-
46
- sessionHistory.push(record);
47
-
48
- if (sessionHistory.length > this.maxHistoryPerSession) {
49
- sessionHistory.shift();
50
- }
51
-
52
- this.log.debug(`Recorded tool call: ${toolName} for session ${sessionId}`);
53
- }
54
-
55
- check(sessionId: string): StuckLoopState {
56
- const sessionHistory = this.history.get(sessionId) ?? [];
57
-
58
- if (sessionHistory.length < this.triggerThreshold) {
59
- return { detected: false, toolName: "", count: 0 };
60
- }
61
-
62
- const recent = sessionHistory.slice(-10);
63
- const counts = new Map<string, { count: number; error?: string }>();
64
-
65
- for (const record of recent) {
66
- const key = `${record.toolName}:${record.argsHash}`;
67
- const existing = counts.get(key);
68
-
69
- if (existing) {
70
- existing.count++;
71
- if (record.errorMessage) {
72
- existing.error = record.errorMessage;
73
- }
74
- } else {
75
- counts.set(key, { count: 1, error: record.errorMessage });
76
- }
77
- }
78
-
79
- for (const [key, data] of counts) {
80
- if (data.count >= this.triggerThreshold && data.error) {
81
- const toolName = key.split(":")[0] ?? "unknown";
82
-
83
- this.log.warn(`Stuck loop detected: ${toolName} called ${data.count} times with same args and error`);
84
-
85
- return {
86
- detected: true,
87
- toolName,
88
- count: data.count,
89
- lastError: data.error,
90
- };
91
- }
92
- }
93
-
94
- return { detected: false, toolName: "", count: 0 };
95
- }
96
-
97
- getInterventionMessage(state: StuckLoopState): string | null {
98
- if (!state.detected) return null;
99
-
100
- if (state.count >= this.triggerThreshold + 1) {
101
- return `CRITICAL: You have called ${state.toolName} ${state.count} times with the same arguments and it keeps failing with: "${state.lastError}". The user has been notified. You MUST try a completely different approach or ask the user for guidance.`;
102
- }
103
-
104
- return `WARNING: You have called ${state.toolName} ${state.count} times with the same arguments and it keeps failing. You MUST try a completely different approach instead of repeating the same action.`;
105
- }
106
-
107
- clear(sessionId: string): void {
108
- this.history.delete(sessionId);
109
- this.log.debug(`Cleared stuck loop history for session ${sessionId}`);
110
- }
111
-
112
- prune(maxAgeMs: number = 30 * 60 * 1000): number {
113
- const now = Date.now();
114
- let pruned = 0;
115
-
116
- for (const [sessionId, history] of this.history) {
117
- const filtered = history.filter(r => now - r.timestamp < maxAgeMs);
118
-
119
- if (filtered.length === 0) {
120
- this.history.delete(sessionId);
121
- pruned++;
122
- } else if (filtered.length !== history.length) {
123
- this.history.set(sessionId, filtered);
124
- }
125
- }
126
-
127
- return pruned;
128
- }
129
- }
130
-
131
- export function createStuckLoopDetector(config: Config): StuckLoopDetector {
132
- return new StuckLoopDetector(config);
133
- }
@@ -1,149 +0,0 @@
1
- /**
2
- * LLM client — direct official SDKs, no abstraction layers.
3
- *
4
- * gemini / google → native Gemini REST API (v1beta, ?key=)
5
- * anthropic → @anthropic-ai/sdk
6
- * ollama → ollama npm package
7
- * everything else → openai npm package (OpenAI-compatible endpoint)
8
- *
9
- * Public interface (LLMMessage, callLLM, resolveProviderConfig) is stable.
10
- */
11
-
12
- import { logger } from "../../utils/logger.ts"
13
- import { GeminiProvider } from "./gemini.ts"
14
- import { AnthropicProvider } from "./anthropic.ts"
15
- import { OllamaProvider } from "./ollama.ts"
16
- import { OpenAICompatProvider } from "./openai-compat.ts"
17
- import type { LLMProvider } from "./interface.ts"
18
-
19
- const log = logger.child("llm-client")
20
-
21
- // ─── Canonical types ───────────────────────────────────────────────────────────
22
-
23
- export interface LLMToolCall {
24
- id: string
25
- type: "function"
26
- function: { name: string; arguments: string }
27
- /** Gemini 3.x thought signature — must be round-tripped for tool-calling. */
28
- thought_signature?: string
29
- }
30
-
31
- export type ContentPart =
32
- | { type: "text"; text: string }
33
- | { type: "image_url"; image_url: { url: string } }
34
- | { type: "image_base64"; base64: string; mimeType: string }
35
- | { type: "document"; base64: string; mimeType: string; fileName?: string }
36
-
37
- export interface LLMMessage {
38
- role: "system" | "user" | "assistant" | "tool"
39
- content: string | ContentPart[]
40
- tool_calls?: LLMToolCall[]
41
- tool_call_id?: string
42
- name?: string
43
- /** Kimi K2 thinking mode — must be round-tripped when tool calls are present. */
44
- reasoning_content?: string
45
- }
46
-
47
- export interface LLMToolDef {
48
- type: "function"
49
- function: {
50
- name: string
51
- description: string
52
- parameters: Record<string, unknown>
53
- }
54
- }
55
-
56
- export interface LLMCallOptions {
57
- provider: string
58
- model: string
59
- apiKey: string
60
- baseUrl?: string
61
- numCtx?: number
62
- messages: LLMMessage[]
63
- tools?: LLMToolDef[]
64
- temperature?: number
65
- maxTokens?: number
66
- numGpu?: number
67
- onToken?: (token: string) => void
68
- signal?: AbortSignal
69
- /** Enable extended thinking for supported models (Anthropic Claude 3.7+). */
70
- thinking?: { enabled: boolean; budget_tokens?: number }
71
- }
72
-
73
- export interface LLMResponse {
74
- content: string
75
- tool_calls?: LLMToolCall[]
76
- stop_reason: "stop" | "tool_calls" | "max_tokens" | "error"
77
- usage?: { input_tokens: number; output_tokens: number; thinking_tokens?: number }
78
- /** Kimi K2 / DeepSeek thinking mode — must be round-tripped in assistant messages. */
79
- reasoning_content?: string
80
- /** Anthropic extended thinking content (not sent to LLM, for display only). */
81
- thinking_content?: string
82
- }
83
-
84
- // ─── Provider factory ─────────────────────────────────────────────────────────
85
-
86
- const GEMINI_PROVIDERS = new Set(["gemini", "google"])
87
-
88
- const KNOWN_PROVIDERS = new Set(["anthropic", "gemini", "google", "ollama", "openai", "groq", "mistral", "openrouter", "deepseek", "kimi", "local-llama", "nvidia"])
89
-
90
- function getProvider(provider: string): LLMProvider {
91
- if (GEMINI_PROVIDERS.has(provider)) return new GeminiProvider()
92
- if (provider === "anthropic") return new AnthropicProvider()
93
- if (provider === "ollama") return new OllamaProvider()
94
- if (!KNOWN_PROVIDERS.has(provider)) {
95
- log.warn(`[llm-client] Unknown provider "${provider}" — falling back to OpenAI-compatible endpoint`)
96
- }
97
- return new OpenAICompatProvider()
98
- }
99
-
100
- // ─── Public API ────────────────────────────────────────────────────────────────
101
-
102
- /**
103
- * Call any LLM provider. Returns a canonical LLMResponse regardless of provider.
104
- */
105
- export async function callLLM(options: LLMCallOptions): Promise<LLMResponse> {
106
- try {
107
- return await getProvider(options.provider).call(options)
108
- } catch (err) {
109
- const msg = (err as Error).message
110
- const cleanModel = options.model.replace(new RegExp(`^${options.provider}\\/`), "")
111
- log.error(`[llm-client] Error calling ${options.provider}/${cleanModel}: ${msg}`, err)
112
- return { content: `[LLM Error] ${msg}`, stop_reason: "error" }
113
- }
114
- }
115
-
116
- /**
117
- * Resolve provider config from DB (decrypts API key).
118
- */
119
- export async function resolveProviderConfig(
120
- providerId: string,
121
- modelId: string
122
- ): Promise<Pick<LLMCallOptions, "provider" | "model" | "apiKey" | "baseUrl" | "numCtx" | "numGpu">> {
123
- const { getDb } = await import("../../storage/SQLiteStorage.ts")
124
- const { decryptApiKey } = await import("../../storage/crypto.ts")
125
-
126
- const db = getDb()
127
- const providerRow = db
128
- .query<any, [string]>("SELECT * FROM providers WHERE id = ? AND enabled = 1")
129
- .get(providerId)
130
-
131
- let apiKey = ""
132
- if (providerRow?.api_key_encrypted && providerRow?.api_key_iv) {
133
- try {
134
- apiKey = await decryptApiKey(providerRow.api_key_encrypted, providerRow.api_key_iv)
135
- } catch { /* fall through to env var */ }
136
- }
137
- if (!apiKey) {
138
- apiKey = process.env[`${providerId.toUpperCase()}_API_KEY`] || ""
139
- }
140
-
141
- return {
142
- provider: providerId,
143
- model: modelId,
144
- apiKey,
145
- baseUrl: providerRow?.base_url || undefined,
146
- numCtx: providerRow?.num_ctx ?? undefined,
147
- numGpu: providerRow?.num_gpu ?? undefined,
148
- }
149
- }