@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,236 +0,0 @@
1
- /**
2
- * Harness module tests — job-store retry/idempotency, durable-queue
3
- * dispatch + retry wiring, run-store checkpoint/acceptance/epoch
4
- * round-trip, proof-packet persistence, goal-verifier (deterministic
5
- * check-tool + acceptance-criteria paths only — no network/LLM calls).
6
- *
7
- * Uses an isolated HIVE_HOME so this never touches a real dev database.
8
- */
9
-
10
- import { mkdtempSync, rmSync } from "node:fs";
11
- import { tmpdir } from "node:os";
12
- import * as path from "node:path";
13
-
14
- import { describe, test, expect, beforeEach, afterEach } from "bun:test";
15
- import { closeHiveDB } from "../storage/HiveDBStorage.ts";
16
- import {
17
- ensureHarnessIndexes,
18
- createJob,
19
- claimJob,
20
- failJobOrRetry,
21
- getJob,
22
- computeBackoffDelay,
23
- findByIdempotencyKey,
24
- DurableLaneQueue,
25
- registerExecutor,
26
- createRun,
27
- deserializeAcceptance,
28
- deserializeEpoch,
29
- buildRunEpoch,
30
- buildProofPacket,
31
- findProofPacketsByRun,
32
- verifyGoal,
33
- getBootId,
34
- resetBootId,
35
- col,
36
- type JobRetryPolicy,
37
- type JobDoc,
38
- } from "./index.ts";
39
-
40
- // Fresh HiveDB directory per test — hive-sdk's HiveDBStorage has no ":memory:"
41
- // mode, so isolation means pointing HIVE_HOME at a new temp dir each time and
42
- // letting the lazy singleton reopen there.
43
- let currentDir: string;
44
-
45
- beforeEach(async () => {
46
- closeHiveDB();
47
- resetBootId();
48
- currentDir = mkdtempSync(path.join(tmpdir(), "hive-sdk-harness-test-"));
49
- process.env.HIVE_HOME = currentDir;
50
- await ensureHarnessIndexes();
51
- });
52
-
53
- afterEach(() => {
54
- closeHiveDB();
55
- rmSync(currentDir, { recursive: true, force: true });
56
- });
57
-
58
- const FAST_POLICY: JobRetryPolicy = {
59
- maxRetries: 2,
60
- initialDelayMs: 10,
61
- backoffMultiplier: 2,
62
- maxDelayMs: 1000,
63
- jitter: 0,
64
- };
65
-
66
- async function waitFor(predicate: () => Promise<boolean>, timeoutMs = 3000): Promise<void> {
67
- const start = Date.now();
68
- while (Date.now() - start < timeoutMs) {
69
- if (await predicate()) return;
70
- await new Promise((r) => setTimeout(r, 10));
71
- }
72
- throw new Error("waitFor timed out");
73
- }
74
-
75
- describe("harness/job-store: idempotency", () => {
76
- test("repeated idempotency_key returns the same job", async () => {
77
- const first = await createJob({ lane: "s1", type: "worker_task", payload: { n: 1 }, run_id: "r1", idempotency_key: "dedupe-1" });
78
- const second = await createJob({ lane: "s1", type: "worker_task", payload: { n: 2 }, run_id: "r1", idempotency_key: "dedupe-1" });
79
- expect(second.id).toBe(first.id);
80
- expect(JSON.parse(second.payload_json).n).toBe(1);
81
- });
82
-
83
- test("findByIdempotencyKey returns null for an unknown key", async () => {
84
- expect(await findByIdempotencyKey("nope")).toBeNull();
85
- });
86
- });
87
-
88
- describe("harness/job-store: retry/backoff", () => {
89
- test("computeBackoffDelay grows exponentially and caps", () => {
90
- const policy: JobRetryPolicy = { maxRetries: 10, initialDelayMs: 100, backoffMultiplier: 2, maxDelayMs: 500, jitter: 0 };
91
- expect(computeBackoffDelay(0, policy)).toBe(100);
92
- expect(computeBackoffDelay(1, policy)).toBe(200);
93
- expect(computeBackoffDelay(3, policy)).toBe(500);
94
- });
95
-
96
- test("failJobOrRetry schedules a retry, then fails terminally after maxRetries", async () => {
97
- const job = await createJob({ lane: "s2", type: "worker_task", payload: {}, run_id: "r2" });
98
- const c = await col<JobDoc>("harness_jobQueue");
99
- // Force not_before into the past after each retry so claimJob doesn't block on the backoff delay.
100
- const forcePastNotBefore = async () => {
101
- const entry = await c.get(job.id);
102
- await c.put(job.id, { ...entry!.doc, not_before: Date.now() - 1 }, { expectedVersion: entry!.version });
103
- };
104
-
105
- await claimJob(job.id);
106
- const r1 = await failJobOrRetry(job.id, "boom", getBootId(), FAST_POLICY);
107
- expect(r1!.status).toBe("pending");
108
- expect(r1!.retry_count).toBe(1);
109
- await forcePastNotBefore();
110
-
111
- await claimJob(job.id);
112
- const r2 = await failJobOrRetry(job.id, "boom again", getBootId(), FAST_POLICY);
113
- expect(r2!.status).toBe("pending");
114
- expect(r2!.retry_count).toBe(2);
115
- await forcePastNotBefore();
116
-
117
- await claimJob(job.id);
118
- const r3 = await failJobOrRetry(job.id, "final", getBootId(), FAST_POLICY);
119
- expect(r3!.status).toBe("failed");
120
- });
121
- });
122
-
123
- describe("harness/durable-queue", () => {
124
- let queue: DurableLaneQueue | null = null;
125
-
126
- afterEach(() => {
127
- queue?.stop();
128
- queue = null;
129
- });
130
-
131
- test("retries a retryable logical failure then completes", async () => {
132
- let calls = 0;
133
- registerExecutor("flaky_task", async () => {
134
- calls++;
135
- if (calls < 2) return { ok: false, error: "flaky", retryable: true };
136
- return { ok: true, result: "recovered" };
137
- });
138
-
139
- queue = new DurableLaneQueue({ maxGlobalConcurrency: 2, jobRetryPolicy: FAST_POLICY });
140
- const job = await queue.enqueue({ lane: "lane-flaky", type: "flaky_task", run_id: "r3", payload: {} });
141
-
142
- await waitFor(async () => (await getJob(job.id))?.retry_count === 1);
143
- await new Promise((r) => setTimeout(r, 30));
144
- queue.start();
145
-
146
- await waitFor(async () => (await getJob(job.id))?.status === "completed");
147
- expect(calls).toBe(2);
148
- });
149
-
150
- test("nonRetryableTypes never auto-retries a logical failure", async () => {
151
- let calls = 0;
152
- registerExecutor("chat_turn", async () => {
153
- calls++;
154
- return { ok: false, error: "user-facing failure" };
155
- });
156
-
157
- queue = new DurableLaneQueue({ maxGlobalConcurrency: 2, jobRetryPolicy: FAST_POLICY });
158
- const job = await queue.enqueue({ lane: "lane-chat", type: "chat_turn", run_id: "r4", payload: {} });
159
-
160
- await waitFor(async () => (await getJob(job.id))?.status === "failed");
161
- expect(calls).toBe(1);
162
- expect((await getJob(job.id))!.retry_count).toBe(0);
163
- });
164
- });
165
-
166
- describe("harness/run-store: acceptance + epoch round-trip", () => {
167
- test("createRun persists and deserializes acceptance criteria and epoch", async () => {
168
- const epoch = buildRunEpoch({ provider: "anthropic", model: "claude-x", appVersion: "1.2.3", toolNames: ["search", "exec"] });
169
- const run = await createRun({
170
- thread_id: "t1",
171
- agent_id: "a1",
172
- user_id: "u1",
173
- channel: null,
174
- kind: "goal",
175
- max_iterations: 20,
176
- acceptance: [
177
- { id: "c1", description: "responds in Spanish" },
178
- { id: "c2", description: "calls the search tool", checkTool: "search" },
179
- ],
180
- epoch,
181
- });
182
-
183
- const acceptance = deserializeAcceptance(run);
184
- expect(acceptance).not.toBeNull();
185
- expect(acceptance!.length).toBe(2);
186
- expect(acceptance![1].checkTool).toBe("search");
187
- expect(deserializeEpoch(run)).toEqual(epoch);
188
- });
189
- });
190
-
191
- describe("harness/proof-packet", () => {
192
- test("persists and retrieves a proof packet", async () => {
193
- const packet = await buildProofPacket({
194
- runId: "run-proof-1",
195
- agentId: "a1",
196
- intendedOutcome: "send the weekly report",
197
- met: true,
198
- checksRun: ["llm_verifier"],
199
- evidence: ["report sent"],
200
- });
201
- expect(packet.met).toBe(true);
202
- const found = await findProofPacketsByRun("run-proof-1");
203
- expect(found.length).toBe(1);
204
- expect(found[0].id).toBe(packet.id);
205
- });
206
- });
207
-
208
- describe("harness/goal-verifier: deterministic check-tool path (no LLM)", () => {
209
- test("a single boolean-returning check tool is interpreted directly", async () => {
210
- const verdict = await verifyGoal({
211
- goal: "workspace exists",
212
- checkTool: "check_workspace",
213
- messages: [],
214
- providerCfg: { provider: "anthropic", model: "x", apiKey: "unused" },
215
- runCheckTool: async () => true,
216
- });
217
- expect(verdict.met).toBe(true);
218
- });
219
-
220
- test("acceptance criteria aggregate as a conjunction across check tools", async () => {
221
- const verdict = await verifyGoal({
222
- goal: "onboard client",
223
- messages: [],
224
- providerCfg: { provider: "anthropic", model: "x", apiKey: "unused" },
225
- runCheckTool: async (tool) => (tool === "check_a" ? true : { met: false, reason: "email not sent" }),
226
- acceptance: [
227
- { id: "c1", description: "workspace created", checkTool: "check_a" },
228
- { id: "c2", description: "welcome email sent", checkTool: "check_b" },
229
- ],
230
- });
231
- expect(verdict.met).toBe(false);
232
- expect(verdict.acceptanceResults).toHaveLength(2);
233
- expect(verdict.acceptanceResults![0].met).toBe(true);
234
- expect(verdict.acceptanceResults![1].met).toBe(false);
235
- });
236
- });
@@ -1,149 +0,0 @@
1
- /**
2
- * reconcileOnBoot — repair harness rows left in inconsistent states by a
3
- * previous crash/kill, plus a retention cap. Ported from `hive`'s
4
- * storage/reconcile.ts, trimmed to only the generic harness collections
5
- * (agentRuns/jobQueue) — `hive`'s version also repairs app-specific rows
6
- * (taskRuns, meetingSessions, project tasks) that don't exist in the SDK.
7
- *
8
- * Call early in a host app's boot sequence, before `initDurableQueue`.
9
- */
10
-
11
- import { col } from "./db-helpers";
12
- import type { HarnessRunDoc } from "./collections";
13
- import type { JobDoc } from "./collections";
14
- import { logger } from "../utils/logger";
15
- import { reclaimOrInterrupt, JOB_QUEUE_COLLECTION } from "./job-store";
16
- import { interruptRun, AGENT_RUNS_COLLECTION } from "./run-store";
17
-
18
- const log = logger.child("harness:reconcile");
19
-
20
- export interface ReconcileResult {
21
- bootId: string;
22
- runsInterrupted: number;
23
- runsReEnqueueable: number;
24
- jobsReclaimed: number;
25
- jobsInterrupted: number;
26
- runsPruned: number;
27
- jobsPruned: number;
28
- }
29
-
30
- export interface ReconcileOptions {
31
- /** Run kinds treated as "interactive" (interrupted outright rather than flagged for re-enqueue). Default: ["chat"]. */
32
- interactiveKinds?: string[];
33
- /** Retention cap per thread/run. Default: 500. */
34
- retentionLimit?: number;
35
- /** Called when an interactive run is interrupted, so the host can notify the user. */
36
- onInteractiveInterrupted?: (run: HarnessRunDoc) => Promise<void> | void;
37
- }
38
-
39
- export async function reconcileOnBoot(bootId: string, opts: ReconcileOptions = {}): Promise<ReconcileResult> {
40
- const interactiveKinds = new Set(opts.interactiveKinds ?? ["chat"]);
41
- const retentionLimit = opts.retentionLimit ?? 500;
42
-
43
- log.info(`[reconcileOnBoot] Starting with boot_id=${bootId}`);
44
- const result: ReconcileResult = {
45
- bootId,
46
- runsInterrupted: 0,
47
- runsReEnqueueable: 0,
48
- jobsReclaimed: 0,
49
- jobsInterrupted: 0,
50
- runsPruned: 0,
51
- jobsPruned: 0,
52
- };
53
-
54
- // 1. harness_agentRuns: any "running" row is orphaned at boot — HiveDB is
55
- // single-process and this process just started, so no run can actually be
56
- // executing.
57
- try {
58
- const runsCol = await col<HarnessRunDoc>(AGENT_RUNS_COLLECTION);
59
- const runningRuns = await runsCol.findBy("status", "running");
60
- for (const entry of runningRuns) {
61
- const run = entry.doc;
62
- if (interactiveKinds.has(run.kind)) {
63
- await interruptRun(run.id, "Process restarted while an interactive run was in flight");
64
- result.runsInterrupted++;
65
- try {
66
- await opts.onInteractiveInterrupted?.(run);
67
- } catch {
68
- // non-critical
69
- }
70
- } else {
71
- await interruptRun(run.id, "Process restarted — job will be re-enqueued if durable");
72
- result.runsReEnqueueable++;
73
- }
74
- }
75
- if (result.runsInterrupted > 0 || result.runsReEnqueueable > 0) {
76
- log.info(`[reconcileOnBoot] ${result.runsInterrupted} interactive runs interrupted, ${result.runsReEnqueueable} durable runs flagged for re-enqueue`);
77
- }
78
- } catch (err) {
79
- log.warn(`[reconcileOnBoot] Failed to repair agentRuns: ${(err as Error).message}`);
80
- }
81
-
82
- // 2. harness_jobQueue: any "running" row is orphaned at boot (same
83
- // single-process argument as above) → reclaim (pending) or interrupt,
84
- // ignoring the lease.
85
- try {
86
- const jobsCol = await col<JobDoc>(JOB_QUEUE_COLLECTION);
87
- const runningJobs = await jobsCol.findBy("status", "running");
88
- for (const entry of runningJobs) {
89
- const doc = await reclaimOrInterrupt(entry.doc.id, { force: true });
90
- if (doc?.status === "pending") result.jobsReclaimed++;
91
- else if (doc?.status === "interrupted") result.jobsInterrupted++;
92
- }
93
- if (result.jobsReclaimed > 0 || result.jobsInterrupted > 0) {
94
- log.info(`[reconcileOnBoot] ${result.jobsReclaimed} jobs reclaimed to pending, ${result.jobsInterrupted} jobs interrupted (attempts exhausted)`);
95
- }
96
- } catch (err) {
97
- log.warn(`[reconcileOnBoot] Failed to repair jobQueue: ${(err as Error).message}`);
98
- }
99
-
100
- // 3. Retention cap: keep only the most recent N agentRuns/jobs per thread/run.
101
- try {
102
- const runsCol = await col<HarnessRunDoc>(AGENT_RUNS_COLLECTION);
103
- const allRuns = await runsCol.scan({});
104
- const runsByThread = new Map<string, typeof allRuns>();
105
- for (const entry of allRuns) {
106
- const key = entry.doc.thread_id ?? entry.doc.agent_id ?? "_default";
107
- const list = runsByThread.get(key) ?? [];
108
- list.push(entry);
109
- runsByThread.set(key, list);
110
- }
111
- for (const [, runs] of runsByThread) {
112
- if (runs.length <= retentionLimit) continue;
113
- const sorted = runs.sort((a, b) => (b.doc.created_at ?? 0) - (a.doc.created_at ?? 0));
114
- for (const entry of sorted.slice(retentionLimit)) {
115
- await runsCol.delete(entry.id);
116
- result.runsPruned++;
117
- }
118
- }
119
- if (result.runsPruned > 0) {
120
- log.info(`[reconcileOnBoot] Retention: pruned ${result.runsPruned} old agentRuns (cap=${retentionLimit}/thread)`);
121
- }
122
-
123
- const jobsCol = await col<JobDoc>(JOB_QUEUE_COLLECTION);
124
- const allJobs = await jobsCol.scan({});
125
- const jobsByRun = new Map<string, typeof allJobs>();
126
- for (const entry of allJobs) {
127
- const key = entry.doc.run_id ?? "_default";
128
- const list = jobsByRun.get(key) ?? [];
129
- list.push(entry);
130
- jobsByRun.set(key, list);
131
- }
132
- for (const [, jobs] of jobsByRun) {
133
- if (jobs.length <= retentionLimit) continue;
134
- const sorted = jobs.sort((a, b) => (b.doc.created_at ?? 0) - (a.doc.created_at ?? 0));
135
- for (const entry of sorted.slice(retentionLimit)) {
136
- await jobsCol.delete(entry.id);
137
- result.jobsPruned++;
138
- }
139
- }
140
- if (result.jobsPruned > 0) {
141
- log.info(`[reconcileOnBoot] Retention: pruned ${result.jobsPruned} old jobs (cap=${retentionLimit}/run)`);
142
- }
143
- } catch (err) {
144
- log.warn(`[reconcileOnBoot] Failed to enforce retention cap: ${(err as Error).message}`);
145
- }
146
-
147
- log.info(`[reconcileOnBoot] Done`, result);
148
- return result;
149
- }
@@ -1,176 +0,0 @@
1
- /**
2
- * MCP Tool Sync — Persist MCP tool definitions to DB and FTS5
3
- *
4
- * When an MCP server connects, its tool definitions are persisted to
5
- * the mcp_tools table and indexed in mcp_tools_fts for search_knowledge.
6
- * When the server disconnects, all its tools are deleted from both.
7
- *
8
- * This enables:
9
- * 1. search_knowledge to find MCP tools via FTS5
10
- * 2. Tool definitions to survive across context compiler invocations
11
- * 3. Offline visibility of what MCP tools were available
12
- */
13
-
14
- import { getDb } from "../storage/SQLiteStorage.ts"
15
- import { logger } from "../utils/logger.ts"
16
-
17
- const log = logger.child("mcp:tool-sync")
18
-
19
- export interface MCPToolDefinition {
20
- name: string
21
- description: string
22
- inputSchema?: Record<string, unknown>
23
- }
24
-
25
- /**
26
- * Generate a stable ID for an MCP tool based on server + tool name.
27
- * Uses the same sanitization as mcpToolFullName for consistency.
28
- */
29
- export function mcpToolId(serverName: string, toolName: string): string {
30
- const safe = (s: string) => s.replace(/\s+/g, '_').replace(/[^a-zA-Z0-9_.\-:]/g, '_')
31
- const full = `${safe(serverName)}__${safe(toolName)}`
32
- const trimmed = full.length > 64 ? full.substring(0, 64) : full
33
- return /^[a-zA-Z_]/.test(trimmed) ? trimmed : `_${trimmed}`.substring(0, 64)
34
- }
35
-
36
- /**
37
- * Persist MCP tool definitions to the mcp_tools table.
38
- * Called when a server connects or reconnects.
39
- * Deletes existing tools for the server first, then inserts fresh data.
40
- */
41
- export function syncMCPToolsToDB(
42
- serverId: string,
43
- serverName: string,
44
- tools: MCPToolDefinition[]
45
- ): void {
46
- const db = getDb()
47
-
48
- try {
49
- const deleteExisting = db.prepare("DELETE FROM mcp_tools WHERE server_id = ?")
50
- deleteExisting.run(serverId)
51
-
52
- if (tools.length === 0) {
53
- log.debug(`[mcp:tool-sync] No tools to persist for server ${serverName}`)
54
- return
55
- }
56
-
57
- const insertTool = db.prepare(`
58
- INSERT INTO mcp_tools(id, server_id, server_name, tool_name, description, category, active, created_at, updated_at)
59
- VALUES (?, ?, ?, ?, ?, 'mcp', 1, (unixepoch()), (unixepoch()))
60
- `)
61
-
62
- let count = 0
63
- for (const tool of tools) {
64
- const id = mcpToolId(serverName, tool.name)
65
- insertTool.run(id, serverId, serverName, tool.name, tool.description || "")
66
- count++
67
- }
68
-
69
- log.info(`[mcp:tool-sync] Persisted ${count} MCP tools for server ${serverName} to mcp_tools`)
70
- } catch (err) {
71
- log.error(`[mcp:tool-sync] Failed to persist MCP tools for server ${serverName}:`, err)
72
- }
73
- }
74
-
75
- /**
76
- * Sync all active MCP tools from mcp_tools table to mcp_tools_fts.
77
- * Called after syncMCPToolsToDB or after startup to ensure FTS5 is in sync.
78
- *
79
- * This does a full clear + re-insert to avoid schema drift.
80
- */
81
- export async function syncMCPToolsToFTS(): Promise<void> {
82
- const db = getDb()
83
-
84
- try {
85
- const syncTransaction = db.transaction(() => {
86
- // Verify table exists
87
- const tableCheck = db.query("SELECT name FROM sqlite_master WHERE type='table' AND name='mcp_tools_fts'").get()
88
- if (!tableCheck) {
89
- throw new Error("mcp_tools_fts table does not exist!")
90
- }
91
-
92
- // Clear existing FTS data
93
- db.run("DELETE FROM mcp_tools_fts")
94
-
95
- // Re-populate from mcp_tools where active
96
- const mcpTools = db.query(`
97
- SELECT id, server_name, tool_name, description, category
98
- FROM mcp_tools
99
- WHERE active = 1
100
- `).all() as Array<{ id: string; server_name: string; tool_name: string; description: string; category: string }>
101
-
102
- if (mcpTools.length === 0) {
103
- log.debug(`[mcp:tool-sync] No MCP tools to sync to FTS5`)
104
- return
105
- }
106
-
107
- const insert = db.prepare(`
108
- INSERT INTO mcp_tools_fts(id, server_name, tool_name, description, category)
109
- VALUES (?, ?, ?, ?, ?)
110
- `)
111
-
112
- for (const tool of mcpTools) {
113
- insert.run(tool.id, tool.server_name, tool.tool_name, tool.description, tool.category)
114
- }
115
-
116
- log.info(`[mcp:tool-sync] Synced ${mcpTools.length} MCP tools to mcp_tools_fts`)
117
- })
118
-
119
- syncTransaction()
120
- } catch (err) {
121
- log.error(`[mcp:tool-sync] Failed to sync MCP tools to FTS5:`, err)
122
- }
123
- }
124
-
125
- /**
126
- * Delete all MCP tool definitions for a server from both mcp_tools and mcp_tools_fts.
127
- * Called when a server disconnects or is removed.
128
- */
129
- export function clearMCPToolsFromDB(serverId: string): void {
130
- const db = getDb()
131
-
132
- try {
133
- // Delete from mcp_tools (CASCADE will handle FTS5 via trigger or manual sync)
134
- const result = db.query("DELETE FROM mcp_tools WHERE server_id = ?").run(serverId)
135
-
136
- log.info(`[mcp:tool-sync] Cleared MCP tools for server_id=${serverId}`)
137
-
138
- // Re-sync FTS5 to remove stale entries
139
- syncMCPToolsToFTSSync()
140
- } catch (err) {
141
- log.error(`[mcp:tool-sync] Failed to clear MCP tools for server_id=${serverId}:`, err)
142
- }
143
- }
144
-
145
- /**
146
- * Synchronous version of syncMCPToolsToFTS for use in clearMCPToolsFromDB.
147
- * Avoids async/await in transaction that was already started.
148
- */
149
- function syncMCPToolsToFTSSync(): void {
150
- const db = getDb()
151
-
152
- try {
153
- db.run("DELETE FROM mcp_tools_fts")
154
-
155
- const mcpTools = db.query(`
156
- SELECT id, server_name, tool_name, description, category
157
- FROM mcp_tools
158
- WHERE active = 1
159
- `).all() as Array<{ id: string; server_name: string; tool_name: string; description: string; category: string }>
160
-
161
- if (mcpTools.length === 0) return
162
-
163
- const insert = db.prepare(`
164
- INSERT INTO mcp_tools_fts(id, server_name, tool_name, description, category)
165
- VALUES (?, ?, ?, ?, ?)
166
- `)
167
-
168
- for (const tool of mcpTools) {
169
- insert.run(tool.id, tool.server_name, tool.tool_name, tool.description, tool.category)
170
- }
171
-
172
- log.debug(`[mcp:tool-sync] Re-synced ${mcpTools.length} MCP tools to FTS5 after deletion`)
173
- } catch (err) {
174
- log.error(`[mcp:tool-sync] Failed to re-sync MCP tools to FTS5:`, err)
175
- }
176
- }