hive-am 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. package/CHANGELOG.md +114 -1
  2. package/README.md +34 -4
  3. package/package.json +13 -2
  4. package/server/dist/api.js +42 -677
  5. package/server/dist/connections/addressing.js +87 -0
  6. package/server/dist/connections/channel-skill.js +62 -0
  7. package/server/dist/connections/fake.js +1 -0
  8. package/server/dist/connections/files.js +45 -1
  9. package/server/dist/connections/format.js +9 -1
  10. package/server/dist/connections/manager.js +60 -3
  11. package/server/dist/connections/outbound.js +2 -1
  12. package/server/dist/connections/prompt.js +12 -3
  13. package/server/dist/connections/rate.js +20 -0
  14. package/server/dist/connections/router.js +55 -22
  15. package/server/dist/connections/secrets.js +52 -0
  16. package/server/dist/connections/store.js +6 -43
  17. package/server/dist/connections/telegram.js +105 -14
  18. package/server/dist/cron.js +138 -0
  19. package/server/dist/db/json.js +16 -0
  20. package/server/dist/db/migrations.js +199 -0
  21. package/server/dist/db.js +98 -196
  22. package/server/dist/errors.js +11 -0
  23. package/server/dist/git/browse.js +99 -0
  24. package/server/dist/git/ops.js +136 -19
  25. package/server/dist/git/repo.js +2 -1
  26. package/server/dist/history/claude.js +33 -20
  27. package/server/dist/history/index.js +17 -9
  28. package/server/dist/history/kiro.js +17 -12
  29. package/server/dist/history/lines.js +12 -0
  30. package/server/dist/history/opencode.js +18 -1
  31. package/server/dist/http/body.js +46 -0
  32. package/server/dist/http/errors.js +21 -0
  33. package/server/dist/http/guards.js +44 -0
  34. package/server/dist/http/router.js +24 -0
  35. package/server/dist/http/routes/agents.js +103 -0
  36. package/server/dist/http/routes/channel.js +16 -0
  37. package/server/dist/http/routes/colonies.js +9 -0
  38. package/server/dist/http/routes/connections.js +36 -0
  39. package/server/dist/http/routes/fs.js +22 -0
  40. package/server/dist/http/routes/git.js +90 -0
  41. package/server/dist/http/routes/meta.js +11 -0
  42. package/server/dist/http/routes/notebook.js +34 -0
  43. package/server/dist/http/routes/orchestration.js +32 -0
  44. package/server/dist/http/routes/resources.js +68 -0
  45. package/server/dist/http/routes/schedules.js +40 -0
  46. package/server/dist/http/routes/skills.js +47 -0
  47. package/server/dist/http/routes/terminals.js +18 -0
  48. package/server/dist/http/routes/types.js +36 -0
  49. package/server/dist/http/validate.js +65 -0
  50. package/server/dist/index.js +74 -4
  51. package/server/dist/instructions.js +23 -11
  52. package/server/dist/objects/agent.js +49 -0
  53. package/server/dist/objects/database.js +451 -0
  54. package/server/dist/objects/db-history.js +79 -0
  55. package/server/dist/objects/docker-info.js +75 -0
  56. package/server/dist/objects/docker.js +285 -0
  57. package/server/dist/objects/driver-props.js +196 -0
  58. package/server/dist/objects/exec.js +16 -0
  59. package/server/dist/objects/http-history.js +44 -0
  60. package/server/dist/objects/http-script.js +644 -0
  61. package/server/dist/objects/http.js +735 -0
  62. package/server/dist/objects/index.js +445 -0
  63. package/server/dist/objects/model.js +250 -0
  64. package/server/dist/objects/runner.js +211 -0
  65. package/server/dist/objects/stats.js +106 -0
  66. package/server/dist/objects/store.js +115 -0
  67. package/server/dist/objects/supervisor.js +111 -0
  68. package/server/dist/objects/templates.js +67 -0
  69. package/server/dist/providers/claude.js +5 -13
  70. package/server/dist/providers/index.js +7 -1
  71. package/server/dist/providers/kiro.js +4 -5
  72. package/server/dist/providers/opencode-server.js +164 -0
  73. package/server/dist/providers/opencode.js +60 -37
  74. package/server/dist/providers/preamble.js +2 -0
  75. package/server/dist/providers/spawn.js +31 -3
  76. package/server/dist/runtime.js +10 -7
  77. package/server/dist/schedules.js +222 -0
  78. package/server/dist/seed.js +15 -11
  79. package/server/dist/services/agents.js +90 -0
  80. package/server/dist/services/colonies.js +38 -0
  81. package/server/dist/services/connections.js +76 -0
  82. package/server/dist/services/notebook.js +22 -0
  83. package/server/dist/services/providers.js +27 -0
  84. package/server/dist/services/schedules.js +18 -0
  85. package/server/dist/sessions-summary.js +51 -0
  86. package/server/dist/skills/defaults.js +120 -0
  87. package/server/dist/skills/notebook.js +3 -8
  88. package/server/dist/terminals.js +177 -0
  89. package/server/dist/wake.js +149 -0
  90. package/server/dist/watch.js +201 -0
  91. package/server/mcp/dispatch.mjs +169 -1
  92. package/web/.next/BUILD_ID +1 -1
  93. package/web/.next/app-build-manifest.json +145 -85
  94. package/web/.next/app-path-routes-manifest.json +5 -2
  95. package/web/.next/build-manifest.json +14 -14
  96. package/web/.next/prerender-manifest.json +85 -37
  97. package/web/.next/react-loadable-manifest.json +26 -1
  98. package/web/.next/required-server-files.json +13 -1
  99. package/web/.next/routes-manifest.json +32 -0
  100. package/web/.next/server/app/_not-found/page.js +2 -2
  101. package/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  102. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  103. package/web/.next/server/app/_not-found.html +1 -1
  104. package/web/.next/server/app/_not-found.rsc +12 -11
  105. package/web/.next/server/app/agents/[id]/page.js +4 -4
  106. package/web/.next/server/app/agents/[id]/page.js.nft.json +1 -1
  107. package/web/.next/server/app/agents/[id]/page_client-reference-manifest.js +1 -1
  108. package/web/.next/server/app/agents/page.js +2 -2
  109. package/web/.next/server/app/agents/page.js.nft.json +1 -1
  110. package/web/.next/server/app/agents/page_client-reference-manifest.js +1 -1
  111. package/web/.next/server/app/agents.html +1 -1
  112. package/web/.next/server/app/agents.rsc +14 -13
  113. package/web/.next/server/app/connections/page.js +2 -2
  114. package/web/.next/server/app/connections/page.js.nft.json +1 -1
  115. package/web/.next/server/app/connections/page_client-reference-manifest.js +1 -1
  116. package/web/.next/server/app/connections.html +1 -1
  117. package/web/.next/server/app/connections.rsc +14 -13
  118. package/web/.next/server/app/index.html +1 -1
  119. package/web/.next/server/app/index.rsc +14 -13
  120. package/web/.next/server/app/page.js +2 -2
  121. package/web/.next/server/app/page.js.nft.json +1 -1
  122. package/web/.next/server/app/page_client-reference-manifest.js +1 -1
  123. package/web/.next/server/app/relations/page.js +2 -2
  124. package/web/.next/server/app/relations/page.js.nft.json +1 -1
  125. package/web/.next/server/app/relations/page_client-reference-manifest.js +1 -1
  126. package/web/.next/server/app/relations.html +1 -1
  127. package/web/.next/server/app/relations.rsc +14 -13
  128. package/web/.next/server/app/resources/[id]/page.js +2 -0
  129. package/web/.next/server/app/resources/[id]/page.js.nft.json +1 -0
  130. package/web/.next/server/app/resources/[id]/page_client-reference-manifest.js +1 -0
  131. package/web/.next/server/app/resources/page.js +2 -0
  132. package/web/.next/server/app/resources/page.js.nft.json +1 -0
  133. package/web/.next/server/app/resources/page_client-reference-manifest.js +1 -0
  134. package/web/.next/server/app/resources.html +1 -0
  135. package/web/.next/server/app/resources.meta +15 -0
  136. package/web/.next/server/app/resources.rsc +25 -0
  137. package/web/.next/server/app/schedules/page.js +2 -0
  138. package/web/.next/server/app/schedules/page.js.nft.json +1 -0
  139. package/web/.next/server/app/schedules/page_client-reference-manifest.js +1 -0
  140. package/web/.next/server/app/schedules.html +1 -0
  141. package/web/.next/server/app/schedules.meta +15 -0
  142. package/web/.next/server/app/schedules.rsc +25 -0
  143. package/web/.next/server/app/sessions/page.js +2 -2
  144. package/web/.next/server/app/sessions/page.js.nft.json +1 -1
  145. package/web/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
  146. package/web/.next/server/app/sessions.html +1 -1
  147. package/web/.next/server/app/sessions.rsc +14 -13
  148. package/web/.next/server/app/skills/page.js +2 -2
  149. package/web/.next/server/app/skills/page.js.nft.json +1 -1
  150. package/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  151. package/web/.next/server/app/skills.html +1 -1
  152. package/web/.next/server/app/skills.rsc +14 -13
  153. package/web/.next/server/app/types/page.js +2 -2
  154. package/web/.next/server/app/types/page.js.nft.json +1 -1
  155. package/web/.next/server/app/types/page_client-reference-manifest.js +1 -1
  156. package/web/.next/server/app/types.html +1 -1
  157. package/web/.next/server/app/types.rsc +14 -13
  158. package/web/.next/server/app-paths-manifest.json +5 -2
  159. package/web/.next/server/chunks/143.js +1 -1
  160. package/web/.next/server/chunks/274.js +1 -0
  161. package/web/.next/server/chunks/306.js +1 -0
  162. package/web/.next/server/chunks/423.js +1 -0
  163. package/web/.next/server/chunks/505.js +6 -0
  164. package/web/.next/server/chunks/555.js +1 -0
  165. package/web/.next/server/chunks/612.js +1 -0
  166. package/web/.next/server/chunks/638.js +1 -0
  167. package/web/.next/server/chunks/692.js +18 -0
  168. package/web/.next/server/chunks/716.js +2 -0
  169. package/web/.next/server/chunks/790.js +11 -0
  170. package/web/.next/server/chunks/802.js +1 -0
  171. package/web/.next/server/chunks/890.js +1 -1
  172. package/web/.next/server/chunks/919.js +1 -0
  173. package/web/.next/server/chunks/950.js +6 -0
  174. package/web/.next/server/middleware-build-manifest.js +1 -1
  175. package/web/.next/server/middleware-react-loadable-manifest.js +1 -1
  176. package/web/.next/server/next-font-manifest.js +1 -1
  177. package/web/.next/server/next-font-manifest.json +1 -1
  178. package/web/.next/server/pages/404.html +1 -1
  179. package/web/.next/server/pages/500.html +1 -1
  180. package/web/.next/server/server-reference-manifest.json +1 -1
  181. package/web/.next/static/chunks/0937d497-114a2a3de515565e.js +1 -0
  182. package/web/.next/static/chunks/176-aeaf907a6ea5627d.js +1 -0
  183. package/web/.next/static/chunks/291-efae53ea5f7ee92d.js +1 -0
  184. package/web/.next/static/chunks/2b9ff8ee-969e65cc354b2150.js +1 -0
  185. package/web/.next/static/chunks/30-9adcbe92b15a6815.js +1 -0
  186. package/web/.next/static/chunks/316-26da9cc233ba7557.js +1 -0
  187. package/web/.next/static/chunks/350-56599dc3bf06066d.js +1 -0
  188. package/web/.next/static/chunks/486-13e08542f3a1cbac.js +1 -0
  189. package/web/.next/static/chunks/491-6b4e6201f5110dff.js +1 -0
  190. package/web/.next/static/chunks/517-ccaf8cc5c733cbd8.js +1 -0
  191. package/web/.next/static/chunks/545.bfbd92b7632c09de.js +1 -0
  192. package/web/.next/static/chunks/{805-d946ebedb7e85f73.js → 562-7c2e33d838e869bf.js} +1 -1
  193. package/web/.next/static/chunks/581-3ad5c4343dad3f50.js +1 -0
  194. package/web/.next/static/chunks/586-f7017d585a84738d.js +1 -0
  195. package/web/.next/static/chunks/657.b4c39e676c3e20dc.js +1 -0
  196. package/web/.next/static/chunks/701.6abcaf5e60463eef.js +6 -0
  197. package/web/.next/static/chunks/705-a16e1fe09be1ac7f.js +1 -0
  198. package/web/.next/static/chunks/706-fa2be0f10ffebe6a.js +1 -0
  199. package/web/.next/static/chunks/837-33c7480524f48577.js +1 -0
  200. package/web/.next/static/chunks/839-23c5508622fae942.js +1 -0
  201. package/web/.next/static/chunks/844-fe55292e0701f436.js +1 -0
  202. package/web/.next/static/chunks/89-b194fcffe5120c44.js +1 -0
  203. package/web/.next/static/chunks/97.594543c2196de524.js +1 -0
  204. package/web/.next/static/chunks/app/_not-found/page-238a7b80bda6b4ae.js +1 -0
  205. package/web/.next/static/chunks/app/agents/[id]/page-4af6c246c4ca7a97.js +1 -0
  206. package/web/.next/static/chunks/app/agents/page-f4c4c80f955f3967.js +1 -0
  207. package/web/.next/static/chunks/app/connections/page-0936beaf02d863c6.js +1 -0
  208. package/web/.next/static/chunks/app/layout-87ebb70bd9cb8443.js +1 -0
  209. package/web/.next/static/chunks/app/page-a7f292117b7d76bf.js +1 -0
  210. package/web/.next/static/chunks/app/relations/page-fba25393c8688714.js +1 -0
  211. package/web/.next/static/chunks/app/resources/[id]/page-d110d89bb8b5fca7.js +1 -0
  212. package/web/.next/static/chunks/app/resources/page-bbd0b450d3f3131d.js +1 -0
  213. package/web/.next/static/chunks/app/schedules/page-a1124397fc760413.js +1 -0
  214. package/web/.next/static/chunks/app/sessions/page-0e322811b0cde724.js +1 -0
  215. package/web/.next/static/chunks/app/skills/page-22ea0ef89d9f7a13.js +1 -0
  216. package/web/.next/static/chunks/app/types/page-18c4e1db2724d41d.js +1 -0
  217. package/web/.next/static/chunks/fcafe1ac.366e4950d3b0d4b7.js +18 -0
  218. package/web/.next/static/chunks/framework-bbac72adbbe2d328.js +1 -0
  219. package/web/.next/static/chunks/main-app-6086068e5fb5eacd.js +1 -0
  220. package/web/.next/static/chunks/main-c15f3fee674e0303.js +1 -0
  221. package/web/.next/static/chunks/pages/_app-d1670a2c6ce392fc.js +1 -0
  222. package/web/.next/static/chunks/pages/_error-95aac014d202fa7a.js +1 -0
  223. package/web/.next/static/chunks/webpack-ef515e41a83a16ac.js +1 -0
  224. package/web/.next/static/css/945795da867f5250.css +1 -0
  225. package/web/.next/static/css/dd4e32c285f75776.css +1 -0
  226. package/web/.next/static/p4S3i9kvO359GW91DQiHu/_buildManifest.js +1 -0
  227. package/web/.next/trace +29 -23
  228. package/web/.next/types/app/resources/[id]/page.ts +84 -0
  229. package/web/.next/types/app/resources/page.ts +84 -0
  230. package/web/.next/types/app/schedules/page.ts +84 -0
  231. package/web/.next/types/routes.d.ts +7 -2
  232. package/web/.next/types/validator.ts +27 -0
  233. package/web/next.config.mjs +4 -0
  234. package/web/.next/server/chunks/149.js +0 -11
  235. package/web/.next/server/chunks/247.js +0 -1
  236. package/web/.next/server/chunks/851.js +0 -1
  237. package/web/.next/server/chunks/932.js +0 -2
  238. package/web/.next/static/84BpRSahg2BTwk3mX6AG8/_buildManifest.js +0 -1
  239. package/web/.next/static/chunks/0937d497-44abb0f9355ace92.js +0 -1
  240. package/web/.next/static/chunks/2b9ff8ee-c3b61ac1e608c57d.js +0 -1
  241. package/web/.next/static/chunks/351-65da8c1a6e25f93b.js +0 -1
  242. package/web/.next/static/chunks/463-6ae35cdefdc59169.js +0 -1
  243. package/web/.next/static/chunks/493-92266798be872b8f.js +0 -1
  244. package/web/.next/static/chunks/517-eb26c72f6059aa26.js +0 -1
  245. package/web/.next/static/chunks/657.f1e03b7b65eba1d9.js +0 -1
  246. package/web/.next/static/chunks/687-3a4205f51be38602.js +0 -1
  247. package/web/.next/static/chunks/837-fd7623601dc78ed8.js +0 -1
  248. package/web/.next/static/chunks/850-318f32484989dd59.js +0 -1
  249. package/web/.next/static/chunks/89-ace2e1aa63babfdf.js +0 -1
  250. package/web/.next/static/chunks/951-e250b541b67da7ce.js +0 -1
  251. package/web/.next/static/chunks/app/_not-found/page-bbd54d7b9a55d7ae.js +0 -1
  252. package/web/.next/static/chunks/app/agents/[id]/page-5eaf094fec246f1b.js +0 -1
  253. package/web/.next/static/chunks/app/agents/page-e9b17f1c3439ef98.js +0 -1
  254. package/web/.next/static/chunks/app/connections/page-c6afd90abd82d47a.js +0 -1
  255. package/web/.next/static/chunks/app/layout-c415d5a847cd94b3.js +0 -1
  256. package/web/.next/static/chunks/app/page-6fc87fc5a128a204.js +0 -1
  257. package/web/.next/static/chunks/app/relations/page-0355a93a8eb67989.js +0 -1
  258. package/web/.next/static/chunks/app/sessions/page-beae9a74a07d69e0.js +0 -1
  259. package/web/.next/static/chunks/app/skills/page-4f839ac913e26d32.js +0 -1
  260. package/web/.next/static/chunks/app/types/page-4f6b99af3232f861.js +0 -1
  261. package/web/.next/static/chunks/framework-0e68884e76d9a8d3.js +0 -1
  262. package/web/.next/static/chunks/main-app-d6f73ccd41f325c5.js +0 -1
  263. package/web/.next/static/chunks/main-cd22509db15764e2.js +0 -1
  264. package/web/.next/static/chunks/pages/_app-0f75d689c738a6fc.js +0 -1
  265. package/web/.next/static/chunks/pages/_error-d481f6d49fb4455d.js +0 -1
  266. package/web/.next/static/chunks/webpack-d40de72e52f65f79.js +0 -1
  267. package/web/.next/static/css/abe1a6216491f944.css +0 -1
  268. /package/web/.next/static/{84BpRSahg2BTwk3mX6AG8 → p4S3i9kvO359GW91DQiHu}/_ssgManifest.js +0 -0
@@ -0,0 +1,199 @@
1
+ const columns = (db, table) => db.prepare(`PRAGMA table_info(${table})`).all().map((c) => c.name);
2
+ /** ALTER TABLE ... ADD COLUMN, skipped when the column is already there (`def` is the column name followed by its type). */
3
+ export function addColumn(db, table, def) {
4
+ const name = def.split(/\s+/)[0];
5
+ if (columns(db, table).includes(name))
6
+ return false;
7
+ db.exec(`ALTER TABLE ${table} ADD COLUMN ${def}`);
8
+ return true;
9
+ }
10
+ export const MIGRATIONS = [
11
+ {
12
+ version: 1, name: 'baseline',
13
+ up(db) {
14
+ db.exec(`
15
+ CREATE TABLE IF NOT EXISTS skills (
16
+ id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, description TEXT NOT NULL DEFAULT '',
17
+ content TEXT NOT NULL DEFAULT '', created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL
18
+ );
19
+ CREATE TABLE IF NOT EXISTS agent_types (
20
+ id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, description TEXT NOT NULL DEFAULT '',
21
+ role TEXT NOT NULL, provider TEXT NOT NULL, model TEXT NOT NULL DEFAULT '',
22
+ system_prompt TEXT NOT NULL DEFAULT '', permission TEXT NOT NULL DEFAULT 'acceptEdits',
23
+ color TEXT NOT NULL DEFAULT '', created_at INTEGER NOT NULL
24
+ );
25
+ CREATE TABLE IF NOT EXISTS type_skills (
26
+ type_id TEXT NOT NULL REFERENCES agent_types(id) ON DELETE CASCADE,
27
+ skill_id TEXT NOT NULL REFERENCES skills(id) ON DELETE CASCADE,
28
+ PRIMARY KEY (type_id, skill_id)
29
+ );
30
+ CREATE TABLE IF NOT EXISTS agents (
31
+ id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, description TEXT NOT NULL DEFAULT '',
32
+ role TEXT NOT NULL, type_id TEXT REFERENCES agent_types(id) ON DELETE SET NULL,
33
+ provider TEXT NOT NULL, model TEXT NOT NULL DEFAULT '', system_prompt TEXT NOT NULL DEFAULT '',
34
+ permission TEXT NOT NULL DEFAULT 'acceptEdits', cwd TEXT NOT NULL,
35
+ session_id TEXT, status TEXT NOT NULL DEFAULT 'idle',
36
+ created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL
37
+ );
38
+ CREATE TABLE IF NOT EXISTS agent_skills (
39
+ agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
40
+ skill_id TEXT NOT NULL REFERENCES skills(id) ON DELETE CASCADE,
41
+ PRIMARY KEY (agent_id, skill_id)
42
+ );
43
+ CREATE TABLE IF NOT EXISTS assignments (
44
+ orchestrator_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
45
+ worker_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
46
+ PRIMARY KEY (orchestrator_id, worker_id)
47
+ );
48
+ CREATE TABLE IF NOT EXISTS colonies (
49
+ id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, color TEXT NOT NULL DEFAULT '', cwd TEXT NOT NULL DEFAULT '',
50
+ permission TEXT NOT NULL DEFAULT 'acceptEdits', system_prompt TEXT NOT NULL DEFAULT '',
51
+ inherit TEXT NOT NULL DEFAULT '{}', created_at INTEGER NOT NULL
52
+ );
53
+ CREATE TABLE IF NOT EXISTS colony_skills (
54
+ colony_id TEXT NOT NULL REFERENCES colonies(id) ON DELETE CASCADE,
55
+ skill_id TEXT NOT NULL REFERENCES skills(id) ON DELETE CASCADE,
56
+ PRIMARY KEY (colony_id, skill_id)
57
+ );
58
+ -- Pointer log of every native session an agent has ever used (for the Sessions view).
59
+ CREATE TABLE IF NOT EXISTS agent_sessions (
60
+ agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
61
+ provider TEXT NOT NULL, session_id TEXT NOT NULL, cwd TEXT NOT NULL,
62
+ first_seen INTEGER NOT NULL, last_seen INTEGER NOT NULL,
63
+ PRIMARY KEY (agent_id, session_id)
64
+ );
65
+ -- Dispatch log: who asked whom to do what. Results live in the worker's own session.
66
+ CREATE TABLE IF NOT EXISTS dispatches (
67
+ id TEXT PRIMARY KEY, from_id TEXT NOT NULL, to_id TEXT NOT NULL, task TEXT NOT NULL,
68
+ status TEXT NOT NULL, result TEXT, created_at INTEGER NOT NULL, finished_at INTEGER
69
+ );
70
+ CREATE TABLE IF NOT EXISTS seeded_skills (slug TEXT PRIMARY KEY);
71
+
72
+ -- Channels (Telegram...). Known threads: where to send a reply and when it was last active.
73
+ CREATE TABLE IF NOT EXISTS connections (
74
+ id TEXT PRIMARY KEY, kind TEXT NOT NULL, name TEXT NOT NULL UNIQUE,
75
+ agent_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
76
+ config TEXT NOT NULL DEFAULT '{}', allowed TEXT NOT NULL DEFAULT '[]',
77
+ enabled INTEGER NOT NULL DEFAULT 1, created_at INTEGER NOT NULL
78
+ );
79
+ CREATE TABLE IF NOT EXISTS threads (
80
+ id TEXT PRIMARY KEY, connection_id TEXT NOT NULL REFERENCES connections(id) ON DELETE CASCADE,
81
+ external_key TEXT NOT NULL, title TEXT NOT NULL DEFAULT '', target TEXT NOT NULL, last_user TEXT,
82
+ created_at INTEGER NOT NULL, last_activity INTEGER NOT NULL,
83
+ UNIQUE (connection_id, external_key)
84
+ );
85
+ CREATE TABLE IF NOT EXISTS thread_messages (
86
+ id INTEGER PRIMARY KEY AUTOINCREMENT, thread_id TEXT NOT NULL REFERENCES threads(id) ON DELETE CASCADE,
87
+ direction TEXT NOT NULL, external_id TEXT NOT NULL, user_id TEXT, user_name TEXT,
88
+ text TEXT NOT NULL, ts INTEGER NOT NULL,
89
+ UNIQUE (thread_id, direction, external_id)
90
+ );
91
+ CREATE TABLE IF NOT EXISTS connection_cursor (
92
+ connection_id TEXT PRIMARY KEY REFERENCES connections(id) ON DELETE CASCADE, value TEXT NOT NULL
93
+ );
94
+
95
+ -- Wake-ups, schedules and process watches (what an agent asks hive-am to do later).
96
+ DROP TABLE IF EXISTS wakeups;
97
+ CREATE TABLE IF NOT EXISTS agent_wakeups (
98
+ id TEXT PRIMARY KEY, agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
99
+ connection_id TEXT, thread_id TEXT REFERENCES threads(id) ON DELETE CASCADE,
100
+ due_at INTEGER NOT NULL, note TEXT NOT NULL, created_at INTEGER NOT NULL
101
+ );
102
+ CREATE TABLE IF NOT EXISTS agent_schedules (
103
+ id TEXT PRIMARY KEY, agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
104
+ connection_id TEXT, thread_id TEXT REFERENCES threads(id) ON DELETE CASCADE,
105
+ kind TEXT NOT NULL, expr TEXT NOT NULL, tz TEXT NOT NULL, note TEXT NOT NULL,
106
+ enabled INTEGER NOT NULL DEFAULT 1, next_due INTEGER, created_at INTEGER NOT NULL,
107
+ last_fired_at INTEGER, fire_count INTEGER NOT NULL DEFAULT 0, last_status TEXT, last_detail TEXT
108
+ );
109
+ CREATE TABLE IF NOT EXISTS schedule_runs (
110
+ id INTEGER PRIMARY KEY AUTOINCREMENT, schedule_id TEXT NOT NULL REFERENCES agent_schedules(id) ON DELETE CASCADE,
111
+ fired_at INTEGER NOT NULL, status TEXT NOT NULL, detail TEXT, duration_ms INTEGER
112
+ );
113
+ CREATE INDEX IF NOT EXISTS idx_schedule_runs ON schedule_runs (schedule_id, id);
114
+ CREATE TABLE IF NOT EXISTS agent_watches (
115
+ id TEXT PRIMARY KEY, agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
116
+ connection_id TEXT, thread_id TEXT REFERENCES threads(id) ON DELETE CASCADE,
117
+ pid INTEGER NOT NULL, pid_stamp TEXT, file TEXT, log TEXT, note TEXT NOT NULL,
118
+ started_at INTEGER NOT NULL, deadline INTEGER NOT NULL, req_id TEXT, req_name TEXT
119
+ );
120
+
121
+ -- The agent's own notebook, and the resources of a colony.
122
+ CREATE TABLE IF NOT EXISTS agent_notebooks (
123
+ agent_id TEXT PRIMARY KEY REFERENCES agents(id) ON DELETE CASCADE,
124
+ content TEXT NOT NULL DEFAULT '', version INTEGER NOT NULL DEFAULT 0, seen INTEGER NOT NULL DEFAULT 0,
125
+ updated_at INTEGER NOT NULL DEFAULT 0, updated_by TEXT NOT NULL DEFAULT ''
126
+ );
127
+ CREATE TABLE IF NOT EXISTS colony_objects (
128
+ id TEXT PRIMARY KEY, colony_id TEXT REFERENCES colonies(id) ON DELETE SET NULL,
129
+ kind TEXT NOT NULL, name TEXT NOT NULL UNIQUE COLLATE NOCASE, config TEXT NOT NULL,
130
+ created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL
131
+ );
132
+ -- The agent chosen to look after a resource (one per resource; the table keeps its first version's shape).
133
+ CREATE TABLE IF NOT EXISTS object_agents (
134
+ object_id TEXT NOT NULL REFERENCES colony_objects(id) ON DELETE CASCADE,
135
+ agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
136
+ PRIMARY KEY (object_id, agent_id)
137
+ );`);
138
+ // Columns added by later builds: databases created before them get them here.
139
+ addColumn(db, 'agents', 'colony_id TEXT REFERENCES colonies(id) ON DELETE SET NULL');
140
+ addColumn(db, 'agents', "overrides TEXT NOT NULL DEFAULT '[]'");
141
+ addColumn(db, 'agents', 'session_cwd TEXT');
142
+ addColumn(db, 'agents', 'instr_hash TEXT');
143
+ addColumn(db, 'agent_sessions', "kind TEXT NOT NULL DEFAULT 'direct'");
144
+ addColumn(db, 'agent_sessions', 'from_id TEXT');
145
+ addColumn(db, 'agent_sessions', 'task TEXT');
146
+ addColumn(db, 'dispatches', 'session_id TEXT');
147
+ addColumn(db, 'agent_wakeups', 'req_id TEXT');
148
+ addColumn(db, 'agent_wakeups', 'req_name TEXT');
149
+ addColumn(db, 'agent_schedules', 'req_id TEXT');
150
+ addColumn(db, 'agent_schedules', 'req_name TEXT');
151
+ addColumn(db, 'threads', 'muted INTEGER NOT NULL DEFAULT 0');
152
+ addColumn(db, 'threads', 'seen_id INTEGER NOT NULL DEFAULT 0');
153
+ addColumn(db, 'thread_messages', 'files TEXT');
154
+ // Skills briefly had a "default/personal" split; all skills are equal now.
155
+ for (const col of ['kind', 'slug']) {
156
+ if (columns(db, 'skills').includes(col)) {
157
+ try {
158
+ db.exec(`ALTER TABLE skills DROP COLUMN ${col}`);
159
+ }
160
+ catch { /* an old SQLite that cannot drop it: the column is harmless */ }
161
+ }
162
+ }
163
+ // How a skill reaches the agent: `always` puts all its text in the instructions; `on_demand` lists name + description and the agent reads the text with `skill_read`.
164
+ if (addColumn(db, 'skills', "load TEXT NOT NULL DEFAULT 'always'")) {
165
+ // The skills that came with hive-am are long, situational guides (the notebook one must always be in view).
166
+ db.exec("UPDATE skills SET load='on_demand' WHERE id LIKE 'default-%' AND id<>'default-notebook'");
167
+ }
168
+ // How each assignment loads the skill (Skill.load is only the suggestion offered when assigning).
169
+ for (const table of ['type_skills', 'colony_skills', 'agent_skills']) {
170
+ if (!addColumn(db, table, "load TEXT NOT NULL DEFAULT 'always'"))
171
+ continue;
172
+ db.exec(`UPDATE ${table} SET load=COALESCE((SELECT load FROM skills WHERE skills.id=${table}.skill_id), 'always')`);
173
+ }
174
+ },
175
+ },
176
+ {
177
+ version: 2, name: 'renames',
178
+ up(db) {
179
+ // The first name of a cluster was `boss`.
180
+ db.exec("UPDATE colony_objects SET kind='cluster' WHERE kind='boss'");
181
+ // The section "Objects" became "Resources": the skill that came with hive-am follows, unless the person already renamed it.
182
+ db.exec("UPDATE skills SET name='Colony resources' WHERE id='default-objects' AND name='Colony objects' AND NOT EXISTS (SELECT 1 FROM skills WHERE lower(name)='colony resources')");
183
+ },
184
+ },
185
+ ];
186
+ export const SCHEMA_VERSION = MIGRATIONS[MIGRATIONS.length - 1].version;
187
+ /** Brings a database up to the current schema. Returns the versions it applied (empty when it was already current). */
188
+ export function migrate(db, migrations = MIGRATIONS) {
189
+ const applied = [];
190
+ let current = db.pragma('user_version', { simple: true });
191
+ for (const m of migrations) {
192
+ if (m.version <= current)
193
+ continue;
194
+ db.transaction(() => { m.up(db); db.pragma(`user_version = ${m.version}`); })();
195
+ current = m.version;
196
+ applied.push(m.version);
197
+ }
198
+ return applied;
199
+ }
package/server/dist/db.js CHANGED
@@ -3,118 +3,28 @@ import { mkdirSync } from 'node:fs';
3
3
  import { homedir } from 'node:os';
4
4
  import { join } from 'node:path';
5
5
  import { randomUUID } from 'node:crypto';
6
+ import { migrate } from './db/migrations.js';
7
+ import { parseJson } from './db/json.js';
6
8
  export const DATA_DIR = process.env.HIVE_AM_HOME ?? join(homedir(), '.hive-am');
7
9
  mkdirSync(DATA_DIR, { recursive: true });
8
10
  export const db = new Database(join(DATA_DIR, 'hive-am.db'));
9
11
  db.pragma('journal_mode = WAL');
10
12
  db.pragma('foreign_keys = ON');
11
- db.exec(`
12
- CREATE TABLE IF NOT EXISTS skills (
13
- id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, description TEXT NOT NULL DEFAULT '',
14
- content TEXT NOT NULL DEFAULT '', created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL
15
- );
16
- CREATE TABLE IF NOT EXISTS agent_types (
17
- id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, description TEXT NOT NULL DEFAULT '',
18
- role TEXT NOT NULL, provider TEXT NOT NULL, model TEXT NOT NULL DEFAULT '',
19
- system_prompt TEXT NOT NULL DEFAULT '', permission TEXT NOT NULL DEFAULT 'acceptEdits',
20
- color TEXT NOT NULL DEFAULT '', created_at INTEGER NOT NULL
21
- );
22
- CREATE TABLE IF NOT EXISTS type_skills (
23
- type_id TEXT NOT NULL REFERENCES agent_types(id) ON DELETE CASCADE,
24
- skill_id TEXT NOT NULL REFERENCES skills(id) ON DELETE CASCADE,
25
- PRIMARY KEY (type_id, skill_id)
26
- );
27
- CREATE TABLE IF NOT EXISTS agents (
28
- id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, description TEXT NOT NULL DEFAULT '',
29
- role TEXT NOT NULL, type_id TEXT REFERENCES agent_types(id) ON DELETE SET NULL,
30
- provider TEXT NOT NULL, model TEXT NOT NULL DEFAULT '', system_prompt TEXT NOT NULL DEFAULT '',
31
- permission TEXT NOT NULL DEFAULT 'acceptEdits', cwd TEXT NOT NULL,
32
- session_id TEXT, status TEXT NOT NULL DEFAULT 'idle',
33
- created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL
34
- );
35
- CREATE TABLE IF NOT EXISTS agent_skills (
36
- agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
37
- skill_id TEXT NOT NULL REFERENCES skills(id) ON DELETE CASCADE,
38
- PRIMARY KEY (agent_id, skill_id)
39
- );
40
- CREATE TABLE IF NOT EXISTS assignments (
41
- orchestrator_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
42
- worker_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
43
- PRIMARY KEY (orchestrator_id, worker_id)
44
- );
45
- CREATE TABLE IF NOT EXISTS colonies (
46
- id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, color TEXT NOT NULL DEFAULT '', cwd TEXT NOT NULL DEFAULT '',
47
- permission TEXT NOT NULL DEFAULT 'acceptEdits', system_prompt TEXT NOT NULL DEFAULT '',
48
- inherit TEXT NOT NULL DEFAULT '{}', created_at INTEGER NOT NULL
49
- );
50
- CREATE TABLE IF NOT EXISTS colony_skills (
51
- colony_id TEXT NOT NULL REFERENCES colonies(id) ON DELETE CASCADE,
52
- skill_id TEXT NOT NULL REFERENCES skills(id) ON DELETE CASCADE,
53
- PRIMARY KEY (colony_id, skill_id)
54
- );
55
- -- Pointer log of every native session an agent has ever used (for the Sessions view).
56
- CREATE TABLE IF NOT EXISTS agent_sessions (
57
- agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
58
- provider TEXT NOT NULL, session_id TEXT NOT NULL, cwd TEXT NOT NULL,
59
- first_seen INTEGER NOT NULL, last_seen INTEGER NOT NULL,
60
- PRIMARY KEY (agent_id, session_id)
61
- );
62
- -- Dispatch log: who asked whom to do what. Results live in the worker's own session.
63
- CREATE TABLE IF NOT EXISTS dispatches (
64
- id TEXT PRIMARY KEY, from_id TEXT NOT NULL, to_id TEXT NOT NULL, task TEXT NOT NULL,
65
- status TEXT NOT NULL, result TEXT, created_at INTEGER NOT NULL, finished_at INTEGER
66
- );
67
- `);
68
- // Lightweight migrations for databases created before colonies existed.
69
- const agentCols = db.prepare('PRAGMA table_info(agents)').all().map((c) => c.name);
70
- if (!agentCols.includes('colony_id'))
71
- db.exec('ALTER TABLE agents ADD COLUMN colony_id TEXT REFERENCES colonies(id) ON DELETE SET NULL');
72
- if (!agentCols.includes('overrides'))
73
- db.exec("ALTER TABLE agents ADD COLUMN overrides TEXT NOT NULL DEFAULT '[]'");
74
- if (!agentCols.includes('session_cwd'))
75
- db.exec('ALTER TABLE agents ADD COLUMN session_cwd TEXT');
76
- if (!agentCols.includes('instr_hash'))
77
- db.exec('ALTER TABLE agents ADD COLUMN instr_hash TEXT');
78
- // Skills briefly had a "default/personal" split; all skills are equal now.
79
- for (const col of ['kind', 'slug']) {
80
- try {
81
- db.exec(`ALTER TABLE skills DROP COLUMN ${col}`);
82
- }
83
- catch { /* already gone */ }
84
- }
85
- db.exec('CREATE TABLE IF NOT EXISTS seeded_skills (slug TEXT PRIMARY KEY)');
86
- // How a skill reaches the agent: `always` puts all its text in the instructions; `on_demand` lists name + description and the agent reads the text with `skill_read` when a task needs it.
87
- if (!db.prepare('PRAGMA table_info(skills)').all().some((c) => c.name === 'load')) {
88
- db.exec("ALTER TABLE skills ADD COLUMN load TEXT NOT NULL DEFAULT 'always'");
89
- // The skills that came with hive-am are long, situational guides (the notebook one must always be in view).
90
- db.exec("UPDATE skills SET load='on_demand' WHERE id LIKE 'default-%' AND id<>'default-notebook'");
91
- }
92
- // How each assignment loads the skill (see Skill.load, which is only the suggestion offered when assigning).
93
- for (const [table] of [['type_skills'], ['colony_skills'], ['agent_skills']]) {
94
- if (db.prepare(`PRAGMA table_info(${table})`).all().some((c) => c.name === 'load'))
95
- continue;
96
- db.exec(`ALTER TABLE ${table} ADD COLUMN load TEXT NOT NULL DEFAULT 'always'`);
97
- db.exec(`UPDATE ${table} SET load=COALESCE((SELECT load FROM skills WHERE skills.id=${table}.skill_id), 'always')`);
98
- }
99
- const sessCols = db.prepare('PRAGMA table_info(agent_sessions)').all().map((c) => c.name);
100
- if (!sessCols.includes('kind'))
101
- db.exec("ALTER TABLE agent_sessions ADD COLUMN kind TEXT NOT NULL DEFAULT 'direct'");
102
- if (!sessCols.includes('from_id'))
103
- db.exec('ALTER TABLE agent_sessions ADD COLUMN from_id TEXT');
104
- if (!sessCols.includes('task'))
105
- db.exec('ALTER TABLE agent_sessions ADD COLUMN task TEXT');
106
- const dispCols = db.prepare('PRAGMA table_info(dispatches)').all().map((c) => c.name);
107
- if (!dispCols.includes('session_id'))
108
- db.exec('ALTER TABLE dispatches ADD COLUMN session_id TEXT');
13
+ // Every table and column is created by the numbered migrations (db/migrations.ts); modules that own a table import `db` from here, so it is ready by then.
14
+ migrate(db);
109
15
  const now = () => Date.now();
110
16
  const uid = () => randomUUID();
17
+ // Statements are prepared once and reused (the SQL text is the key; the few dynamic ones only vary by table name).
18
+ const prepared = new Map();
19
+ const q = (sql) => { let st = prepared.get(sql); if (!st)
20
+ prepared.set(sql, (st = db.prepare(sql))); return st; };
111
21
  // ---- skills ----
112
22
  export const skills = {
113
- list: () => db.prepare('SELECT * FROM skills ORDER BY name').all(),
114
- get: (id) => db.prepare('SELECT * FROM skills WHERE id=?').get(id),
23
+ list: () => q('SELECT * FROM skills ORDER BY name').all(),
24
+ get: (id) => q('SELECT * FROM skills WHERE id=?').get(id),
115
25
  create(p) {
116
26
  const id = uid(), t = now();
117
- db.prepare('INSERT INTO skills (id,name,description,content,created_at,updated_at,load) VALUES (?,?,?,?,?,?,?)').run(id, p.name, p.description ?? '', p.content ?? '', t, t, p.load ?? ((p.content ?? '').length > 2000 ? 'on_demand' : 'always'));
27
+ q('INSERT INTO skills (id,name,description,content,created_at,updated_at,load) VALUES (?,?,?,?,?,?,?)').run(id, p.name, p.description ?? '', p.content ?? '', t, t, p.load ?? ((p.content ?? '').length > 2000 ? 'on_demand' : 'always'));
118
28
  return skills.get(id);
119
29
  },
120
30
  update(id, p) {
@@ -122,36 +32,38 @@ export const skills = {
122
32
  if (!cur)
123
33
  return;
124
34
  const n = { ...cur, ...p };
125
- db.prepare('UPDATE skills SET name=?, description=?, content=?, load=?, updated_at=? WHERE id=?').run(n.name, n.description, n.content, n.load, now(), id);
35
+ q('UPDATE skills SET name=?, description=?, content=?, load=?, updated_at=? WHERE id=?').run(n.name, n.description, n.content, n.load, now(), id);
126
36
  return skills.get(id);
127
37
  },
128
- remove: (id) => db.prepare('DELETE FROM skills WHERE id=?').run(id).changes > 0,
38
+ remove: (id) => q('DELETE FROM skills WHERE id=?').run(id).changes > 0,
129
39
  /**
130
40
  * Adds the skills that come with hive-am, once each: they are ordinary skills afterwards (edit them, delete them). A
131
41
  * skill that was seeded before is never added back, and one the user already has under that name is left alone.
132
42
  */
133
43
  seedDefaults(defs) {
134
44
  const t = now();
135
- for (const d of defs) {
136
- const id = `default-${d.slug}`;
137
- if (db.prepare('SELECT 1 FROM seeded_skills WHERE slug=?').get(d.slug))
138
- continue;
139
- db.prepare('INSERT OR IGNORE INTO seeded_skills (slug) VALUES (?)').run(d.slug);
140
- if (db.prepare('SELECT 1 FROM skills WHERE id=?').get(id))
141
- continue; // from an earlier build: keep it as it is
142
- const taken = !!db.prepare('SELECT 1 FROM skills WHERE lower(name)=lower(?)').get(d.name);
143
- db.prepare('INSERT INTO skills (id,name,description,content,created_at,updated_at,load) VALUES (?,?,?,?,?,?,?)').run(id, taken ? `${d.name} (hive-am)` : d.name, d.description, d.content, t, t, d.load ?? 'always');
144
- }
45
+ db.transaction(() => {
46
+ for (const d of defs) {
47
+ const id = `default-${d.slug}`;
48
+ if (q('SELECT 1 FROM seeded_skills WHERE slug=?').get(d.slug))
49
+ continue;
50
+ q('INSERT OR IGNORE INTO seeded_skills (slug) VALUES (?)').run(d.slug);
51
+ if (q('SELECT 1 FROM skills WHERE id=?').get(id))
52
+ continue; // from an earlier build: keep it as it is
53
+ const taken = !!q('SELECT 1 FROM skills WHERE lower(name)=lower(?)').get(d.name);
54
+ q('INSERT INTO skills (id,name,description,content,created_at,updated_at,load) VALUES (?,?,?,?,?,?,?)').run(id, taken ? `${d.name} (hive-am)` : d.name, d.description, d.content, t, t, d.load ?? 'always');
55
+ }
56
+ })();
145
57
  },
146
58
  /** Agents/types using each skill, for the library view. */
147
59
  usage() {
148
60
  const out = {};
149
61
  for (const s of skills.list())
150
62
  out[s.id] = { agents: 0, types: 0 };
151
- for (const r of db.prepare('SELECT skill_id, COUNT(*) c FROM agent_skills GROUP BY skill_id').all())
63
+ for (const r of q('SELECT skill_id, COUNT(*) c FROM agent_skills GROUP BY skill_id').all())
152
64
  if (out[r.skill_id])
153
65
  out[r.skill_id].agents = r.c;
154
- for (const r of db.prepare('SELECT skill_id, COUNT(*) c FROM type_skills GROUP BY skill_id').all())
66
+ for (const r of q('SELECT skill_id, COUNT(*) c FROM type_skills GROUP BY skill_id').all())
155
67
  if (out[r.skill_id])
156
68
  out[r.skill_id].types = r.c;
157
69
  return out;
@@ -166,10 +78,11 @@ const JOINS = {
166
78
  /** How each skill assigned to this type/colony/agent is loaded. */
167
79
  function loadsOf(kind, id) {
168
80
  const j = JOINS[kind], out = {};
169
- for (const r of db.prepare(`SELECT skill_id, load FROM ${j.table} WHERE ${j.owner}=?`).all(id))
81
+ for (const r of q(`SELECT skill_id, load FROM ${j.table} WHERE ${j.owner}=?`).all(id))
170
82
  out[r.skill_id] = r.load === 'on_demand' ? 'on_demand' : 'always';
171
83
  return out;
172
84
  }
85
+ const skillIdsOf = (kind, id) => { const j = JOINS[kind]; return q(`SELECT skill_id FROM ${j.table} WHERE ${j.owner}=?`).all(id).map((x) => x.skill_id); };
173
86
  /**
174
87
  * Replaces the skills of a type/colony/agent. A skill keeps the way it was loaded before; a new one takes what the caller
175
88
  * says, or else the skill's own suggestion.
@@ -177,98 +90,87 @@ function loadsOf(kind, id) {
177
90
  function assignSkills(kind, id, ids, loads) {
178
91
  const j = JOINS[kind], prev = loadsOf(kind, id);
179
92
  db.transaction(() => {
180
- db.prepare(`DELETE FROM ${j.table} WHERE ${j.owner}=?`).run(id);
93
+ q(`DELETE FROM ${j.table} WHERE ${j.owner}=?`).run(id);
181
94
  for (const s of ids) {
182
95
  const load = loads?.[s] ?? prev[s] ?? skills.get(s)?.load ?? 'always';
183
- db.prepare(`INSERT OR IGNORE INTO ${j.table} (${j.owner}, skill_id, load) VALUES (?,?,?)`).run(id, s, load === 'on_demand' ? 'on_demand' : 'always');
96
+ q(`INSERT OR IGNORE INTO ${j.table} (${j.owner}, skill_id, load) VALUES (?,?,?)`).run(id, s, load === 'on_demand' ? 'on_demand' : 'always');
184
97
  }
185
98
  })();
186
99
  }
187
100
  // ---- agent types ----
188
- function typeRow(r) {
189
- const skill_ids = db.prepare('SELECT skill_id FROM type_skills WHERE type_id=?').all(r.id).map((x) => x.skill_id);
190
- return { ...r, skill_ids, skill_loads: loadsOf('type', r.id) };
101
+ function typeOf(r) {
102
+ return { ...r, skill_ids: skillIdsOf('type', r.id), skill_loads: loadsOf('type', r.id) };
191
103
  }
192
104
  export const types = {
193
- list: () => db.prepare('SELECT * FROM agent_types ORDER BY role DESC, name').all().map(typeRow),
194
- get(id) { const r = db.prepare('SELECT * FROM agent_types WHERE id=?').get(id); return r ? typeRow(r) : undefined; },
195
- create(p) {
105
+ list: () => q('SELECT * FROM agent_types ORDER BY role DESC, name').all().map(typeOf),
106
+ get(id) { const r = q('SELECT * FROM agent_types WHERE id=?').get(id); return r ? typeOf(r) : undefined; },
107
+ create: db.transaction((p) => {
196
108
  const id = uid();
197
- db.prepare('INSERT INTO agent_types VALUES (?,?,?,?,?,?,?,?,?,?)').run(id, p.name, p.description ?? '', p.role, p.provider, p.model ?? '', p.system_prompt ?? '', (p.permission ?? 'acceptEdits'), p.color ?? '', now());
109
+ q('INSERT INTO agent_types (id,name,description,role,provider,model,system_prompt,permission,color,created_at) VALUES (?,?,?,?,?,?,?,?,?,?)').run(id, p.name, p.description ?? '', p.role, p.provider, p.model ?? '', p.system_prompt ?? '', (p.permission ?? 'acceptEdits'), p.color ?? '', now());
198
110
  types.setSkills(id, p.skill_ids ?? [], p.skill_loads);
199
111
  return types.get(id);
200
- },
201
- update(id, p) {
112
+ }),
113
+ update: db.transaction((id, p) => {
202
114
  const cur = types.get(id);
203
115
  if (!cur)
204
116
  return;
205
117
  const n = { ...cur, ...p };
206
- db.prepare(`UPDATE agent_types SET name=?, description=?, role=?, provider=?, model=?, system_prompt=?, permission=?, color=? WHERE id=?`)
118
+ q('UPDATE agent_types SET name=?, description=?, role=?, provider=?, model=?, system_prompt=?, permission=?, color=? WHERE id=?')
207
119
  .run(n.name, n.description, n.role, n.provider, n.model, n.system_prompt, n.permission, n.color, id);
208
120
  if (p.skill_ids || p.skill_loads)
209
121
  types.setSkills(id, p.skill_ids ?? cur.skill_ids, p.skill_loads);
210
122
  return types.get(id);
211
- },
123
+ }),
212
124
  setSkills: (id, ids, loads) => assignSkills('type', id, ids, loads),
213
- remove: (id) => db.prepare('DELETE FROM agent_types WHERE id=?').run(id).changes > 0,
125
+ remove: (id) => q('DELETE FROM agent_types WHERE id=?').run(id).changes > 0,
214
126
  };
215
127
  // ---- colonies ----
216
128
  const DEFAULT_INHERIT = { cwd: true, permission: true, skills: true, prompt: true };
217
- function colonyRow(r) {
218
- const skill_ids = db.prepare('SELECT skill_id FROM colony_skills WHERE colony_id=?').all(r.id).map((x) => x.skill_id);
219
- const agent_ids = db.prepare('SELECT id FROM agents WHERE colony_id=? ORDER BY name').all(r.id).map((x) => x.id);
220
- let inherit = DEFAULT_INHERIT;
221
- try {
222
- inherit = { ...DEFAULT_INHERIT, ...JSON.parse(r.inherit) };
223
- }
224
- catch { /* keep defaults */ }
225
- return { ...r, inherit, skill_ids, skill_loads: loadsOf('colony', r.id), agent_ids };
129
+ function colonyOf(r) {
130
+ const agent_ids = q('SELECT id FROM agents WHERE colony_id=? ORDER BY name').all(r.id).map((x) => x.id);
131
+ const inherit = { ...DEFAULT_INHERIT, ...parseJson(r.inherit, {}, `colony ${r.id} inherit`) };
132
+ return { ...r, inherit, skill_ids: skillIdsOf('colony', r.id), skill_loads: loadsOf('colony', r.id), agent_ids };
226
133
  }
227
134
  export const colonies = {
228
- list: () => db.prepare('SELECT * FROM colonies ORDER BY name').all().map(colonyRow),
229
- get(id) { const r = db.prepare('SELECT * FROM colonies WHERE id=?').get(id); return r ? colonyRow(r) : undefined; },
230
- create(p) {
135
+ list: () => q('SELECT * FROM colonies ORDER BY name').all().map(colonyOf),
136
+ get(id) { const r = q('SELECT * FROM colonies WHERE id=?').get(id); return r ? colonyOf(r) : undefined; },
137
+ create: db.transaction((p) => {
231
138
  const id = uid();
232
- db.prepare('INSERT INTO colonies VALUES (?,?,?,?,?,?,?,?)').run(id, p.name, p.color ?? '', p.cwd ?? '', (p.permission ?? 'acceptEdits'), p.system_prompt ?? '', JSON.stringify({ ...DEFAULT_INHERIT, ...(p.inherit ?? {}) }), now());
139
+ q('INSERT INTO colonies (id,name,color,cwd,permission,system_prompt,inherit,created_at) VALUES (?,?,?,?,?,?,?,?)').run(id, p.name, p.color ?? '', p.cwd ?? '', (p.permission ?? 'acceptEdits'), p.system_prompt ?? '', JSON.stringify({ ...DEFAULT_INHERIT, ...(p.inherit ?? {}) }), now());
233
140
  colonies.setSkills(id, p.skill_ids ?? [], p.skill_loads);
234
141
  if (p.agent_ids)
235
142
  colonies.setMembers(id, p.agent_ids);
236
143
  return colonies.get(id);
237
- },
238
- update(id, p) {
144
+ }),
145
+ update: db.transaction((id, p) => {
239
146
  const cur = colonies.get(id);
240
147
  if (!cur)
241
148
  return;
242
149
  const n = { ...cur, ...p, inherit: { ...cur.inherit, ...(p.inherit ?? {}) } };
243
- db.prepare('UPDATE colonies SET name=?, color=?, cwd=?, permission=?, system_prompt=?, inherit=? WHERE id=?')
150
+ q('UPDATE colonies SET name=?, color=?, cwd=?, permission=?, system_prompt=?, inherit=? WHERE id=?')
244
151
  .run(n.name, n.color, n.cwd, n.permission, n.system_prompt, JSON.stringify(n.inherit), id);
245
152
  if (p.skill_ids || p.skill_loads)
246
153
  colonies.setSkills(id, p.skill_ids ?? cur.skill_ids, p.skill_loads);
247
154
  if (p.agent_ids)
248
155
  colonies.setMembers(id, p.agent_ids);
249
156
  return colonies.get(id);
250
- },
157
+ }),
251
158
  setSkills: (id, ids, loads) => assignSkills('colony', id, ids, loads),
252
159
  /** Replaces membership; an agent belongs to at most one colony. */
253
- setMembers(id, agentIds) {
254
- db.transaction(() => {
255
- db.prepare('UPDATE agents SET colony_id=NULL WHERE colony_id=?').run(id);
256
- for (const a of agentIds)
257
- db.prepare('UPDATE agents SET colony_id=? WHERE id=?').run(id, a);
258
- })();
259
- },
260
- remove: (id) => db.prepare('DELETE FROM colonies WHERE id=?').run(id).changes > 0,
160
+ setMembers: db.transaction((id, agentIds) => {
161
+ q('UPDATE agents SET colony_id=NULL WHERE colony_id=?').run(id);
162
+ for (const a of agentIds)
163
+ q('UPDATE agents SET colony_id=? WHERE id=?').run(id, a);
164
+ }),
165
+ /** Members stay (their colony becomes none through the foreign key); the colony's own skill assignments go with it. */
166
+ remove: (id) => q('DELETE FROM colonies WHERE id=?').run(id).changes > 0,
261
167
  };
262
168
  // ---- agents ----
263
169
  const FIELDS = ['cwd', 'permission', 'skills', 'prompt'];
264
- function agentRow(r) {
265
- const skill_ids = db.prepare('SELECT skill_id FROM agent_skills WHERE agent_id=?').all(r.id).map((x) => x.skill_id);
266
- const worker_ids = db.prepare('SELECT worker_id FROM assignments WHERE orchestrator_id=?').all(r.id).map((x) => x.worker_id);
267
- let overrides = [];
268
- try {
269
- overrides = JSON.parse(r.overrides ?? '[]').filter((f) => FIELDS.includes(f));
270
- }
271
- catch { /* none */ }
170
+ function agentOf(r) {
171
+ const skill_ids = skillIdsOf('agent', r.id);
172
+ const worker_ids = q('SELECT worker_id FROM assignments WHERE orchestrator_id=?').all(r.id).map((x) => x.worker_id);
173
+ const overrides = parseJson(r.overrides, [], `agent ${r.id} overrides`).filter((f) => FIELDS.includes(f));
272
174
  const col = r.colony_id ? colonies.get(r.colony_id) : undefined;
273
175
  const follows = (f) => !!col && col.inherit[f] && !overrides.includes(f);
274
176
  const inherited = FIELDS.filter(follows);
@@ -289,69 +191,69 @@ export function resolved(a) {
289
191
  return { ...a, cwd: a.effective.cwd, permission: a.effective.permission, system_prompt: a.effective.system_prompt, skill_ids: a.effective.skill_ids, skill_loads: a.effective.skill_loads };
290
192
  }
291
193
  export const agents = {
292
- list: () => db.prepare('SELECT * FROM agents ORDER BY role DESC, name').all().map(agentRow),
293
- get(id) { const r = db.prepare('SELECT * FROM agents WHERE id=?').get(id); return r ? agentRow(r) : undefined; },
294
- byName(name) { const r = db.prepare('SELECT * FROM agents WHERE lower(name)=lower(?)').get(name); return r ? agentRow(r) : undefined; },
295
- create(p) {
194
+ list: () => q('SELECT * FROM agents ORDER BY role DESC, name').all().map(agentOf),
195
+ get(id) { const r = q('SELECT * FROM agents WHERE id=?').get(id); return r ? agentOf(r) : undefined; },
196
+ byName(name) { const r = q('SELECT * FROM agents WHERE lower(name)=lower(?)').get(name); return r ? agentOf(r) : undefined; },
197
+ /** The agent, its skills and its workers are written together: either all of it exists afterwards or none. */
198
+ create: db.transaction((p) => {
296
199
  const id = uid(), t = now();
297
- db.prepare('INSERT INTO agents (id,name,description,role,type_id,provider,model,system_prompt,permission,cwd,session_id,status,created_at,updated_at,colony_id,overrides) VALUES (?,?,?,?,?,?,?,?,?,?,NULL,?,?,?,?,?)').run(id, p.name, p.description ?? '', p.role, p.type_id ?? null, p.provider, p.model ?? '', p.system_prompt ?? '', (p.permission ?? 'acceptEdits'), p.cwd ?? '', 'idle', t, t, p.colony_id ?? null, JSON.stringify(p.overrides ?? []));
200
+ q('INSERT INTO agents (id,name,description,role,type_id,provider,model,system_prompt,permission,cwd,session_id,status,created_at,updated_at,colony_id,overrides) VALUES (?,?,?,?,?,?,?,?,?,?,NULL,?,?,?,?,?)').run(id, p.name, p.description ?? '', p.role, p.type_id ?? null, p.provider, p.model ?? '', p.system_prompt ?? '', (p.permission ?? 'acceptEdits'), p.cwd ?? '', 'idle', t, t, p.colony_id ?? null, JSON.stringify(p.overrides ?? []));
298
201
  agents.setSkills(id, p.skill_ids ?? [], p.skill_loads);
299
202
  agents.setWorkers(id, p.worker_ids ?? []);
300
203
  return agents.get(id);
301
- },
302
- update(id, p) {
204
+ }),
205
+ update: db.transaction((id, p) => {
303
206
  const cur = agents.get(id);
304
207
  if (!cur)
305
208
  return;
306
209
  const n = { ...cur, ...p };
307
- db.prepare(`UPDATE agents SET name=?, description=?, role=?, type_id=?, provider=?, model=?, system_prompt=?, permission=?, cwd=?, colony_id=?, overrides=?, updated_at=? WHERE id=?`)
210
+ q('UPDATE agents SET name=?, description=?, role=?, type_id=?, provider=?, model=?, system_prompt=?, permission=?, cwd=?, colony_id=?, overrides=?, updated_at=? WHERE id=?')
308
211
  .run(n.name, n.description, n.role, n.type_id, n.provider, n.model, n.system_prompt, n.permission, n.cwd, n.colony_id ?? null, JSON.stringify(n.overrides ?? []), now(), id);
309
212
  if (p.skill_ids || p.skill_loads)
310
213
  agents.setSkills(id, p.skill_ids ?? cur.skill_ids, p.skill_loads);
311
214
  if (p.worker_ids)
312
215
  agents.setWorkers(id, p.worker_ids);
313
216
  return agents.get(id);
314
- },
315
- setInstrHash: (id, hash) => db.prepare('UPDATE agents SET instr_hash=? WHERE id=?').run(hash, id),
316
- setStatus: (id, status) => db.prepare('UPDATE agents SET status=?, updated_at=? WHERE id=?').run(status, now(), id),
317
- setSession(a, sessionId) {
318
- db.prepare('UPDATE agents SET session_id=?, session_cwd=?, instr_hash=NULL, updated_at=? WHERE id=?').run(sessionId, sessionId ? a.effective.cwd : null, now(), a.id);
217
+ }),
218
+ setInstrHash: (id, hash) => q('UPDATE agents SET instr_hash=? WHERE id=?').run(hash, id),
219
+ setStatus: (id, status) => q('UPDATE agents SET status=?, updated_at=? WHERE id=?').run(status, now(), id),
220
+ setSession: db.transaction((a, sessionId) => {
221
+ q('UPDATE agents SET session_id=?, session_cwd=?, instr_hash=NULL, updated_at=? WHERE id=?').run(sessionId, sessionId ? a.effective.cwd : null, now(), a.id);
319
222
  if (sessionId) {
320
- db.prepare(`INSERT INTO agent_sessions (agent_id, provider, session_id, cwd, first_seen, last_seen) VALUES (?,?,?,?,?,?)
223
+ q(`INSERT INTO agent_sessions (agent_id, provider, session_id, cwd, first_seen, last_seen) VALUES (?,?,?,?,?,?)
321
224
  ON CONFLICT(agent_id, session_id) DO UPDATE SET last_seen=excluded.last_seen`)
322
225
  .run(a.id, a.provider, sessionId, a.effective.cwd, now(), now());
323
226
  }
324
- },
227
+ }),
325
228
  /** Logs a native session without making it the agent's current conversation (used for delegations). */
326
229
  recordSession(a, sessionId, kind, fromId, task) {
327
- db.prepare(`INSERT INTO agent_sessions (agent_id, provider, session_id, cwd, first_seen, last_seen, kind, from_id, task) VALUES (?,?,?,?,?,?,?,?,?)
230
+ q(`INSERT INTO agent_sessions (agent_id, provider, session_id, cwd, first_seen, last_seen, kind, from_id, task) VALUES (?,?,?,?,?,?,?,?,?)
328
231
  ON CONFLICT(agent_id, session_id) DO UPDATE SET last_seen=excluded.last_seen`)
329
232
  .run(a.id, a.provider, sessionId, a.effective.cwd, now(), now(), kind, fromId ?? null, task ? task.slice(0, 500) : null);
330
233
  },
331
234
  setSkills: (id, ids, loads) => assignSkills('agent', id, ids, loads),
332
- setWorkers(id, ids) {
333
- db.transaction(() => {
334
- db.prepare('DELETE FROM assignments WHERE orchestrator_id=?').run(id);
335
- for (const w of ids)
336
- if (w !== id)
337
- db.prepare('INSERT OR IGNORE INTO assignments VALUES (?,?)').run(id, w);
338
- })();
339
- },
340
- remove: (id) => db.prepare('DELETE FROM agents WHERE id=?').run(id).changes > 0,
341
- sessions: (id) => db.prepare('SELECT s.*, f.name from_name FROM agent_sessions s LEFT JOIN agents f ON f.id=s.from_id WHERE s.agent_id=? ORDER BY s.last_seen DESC').all(id),
342
- allSessions: () => db.prepare(`SELECT s.*, a.name agent_name, a.role agent_role, f.name from_name FROM agent_sessions s JOIN agents a ON a.id=s.agent_id LEFT JOIN agents f ON f.id=s.from_id ORDER BY s.last_seen DESC`).all(),
235
+ /** Replaces the workers an orchestrator may dispatch to (never itself). */
236
+ setWorkers: db.transaction((id, ids) => {
237
+ q('DELETE FROM assignments WHERE orchestrator_id=?').run(id);
238
+ for (const w of ids)
239
+ if (w !== id)
240
+ q('INSERT OR IGNORE INTO assignments (orchestrator_id, worker_id) VALUES (?,?)').run(id, w);
241
+ }),
242
+ remove: (id) => q('DELETE FROM agents WHERE id=?').run(id).changes > 0,
243
+ sessions: (id) => q('SELECT s.*, f.name from_name FROM agent_sessions s LEFT JOIN agents f ON f.id=s.from_id WHERE s.agent_id=? ORDER BY s.last_seen DESC').all(id),
244
+ allSessions: () => q(`SELECT s.*, a.name agent_name, a.role agent_role, f.name from_name FROM agent_sessions s JOIN agents a ON a.id=s.agent_id LEFT JOIN agents f ON f.id=s.from_id ORDER BY s.last_seen DESC`).all(),
343
245
  };
344
246
  // ---- dispatch log ----
345
247
  export const dispatches = {
346
248
  start(from, to, task) {
347
249
  const id = uid();
348
- db.prepare('INSERT INTO dispatches (id, from_id, to_id, task, status, result, created_at, finished_at) VALUES (?,?,?,?,?,NULL,?,NULL)').run(id, from, to, task, 'running', now());
250
+ q('INSERT INTO dispatches (id, from_id, to_id, task, status, result, created_at, finished_at) VALUES (?,?,?,?,?,NULL,?,NULL)').run(id, from, to, task, 'running', now());
349
251
  return id;
350
252
  },
351
- setSession: (id, sessionId) => db.prepare('UPDATE dispatches SET session_id=? WHERE id=?').run(sessionId, id),
352
- finish: (id, status, result) => db.prepare('UPDATE dispatches SET status=?, result=?, finished_at=? WHERE id=?').run(status, result.slice(0, 20000), now(), id),
353
- recent: (limit = 50) => db.prepare('SELECT * FROM dispatches ORDER BY created_at DESC LIMIT ?').all(limit),
253
+ setSession: (id, sessionId) => q('UPDATE dispatches SET session_id=? WHERE id=?').run(sessionId, id),
254
+ finish: (id, status, result) => q('UPDATE dispatches SET status=?, result=?, finished_at=? WHERE id=?').run(status, result.slice(0, 20000), now(), id),
255
+ recent: (limit = 50) => q('SELECT * FROM dispatches ORDER BY created_at DESC LIMIT ?').all(limit),
354
256
  };
355
257
  // Agents left 'running' by a crash/blackout are simply idle again: their session is intact on disk.
356
- db.prepare("UPDATE agents SET status='idle' WHERE status='running'").run();
357
- db.prepare("UPDATE dispatches SET status='interrupted' WHERE status='running'").run();
258
+ q("UPDATE agents SET status='idle' WHERE status='running'").run();
259
+ q("UPDATE dispatches SET status='interrupted' WHERE status='running'").run();