hive-am 1.1.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 (261) hide show
  1. package/CHANGELOG.md +83 -1
  2. package/README.md +9 -1
  3. package/package.json +13 -2
  4. package/server/dist/api.js +42 -762
  5. package/server/dist/connections/channel-skill.js +13 -1
  6. package/server/dist/connections/files.js +45 -1
  7. package/server/dist/connections/manager.js +41 -3
  8. package/server/dist/connections/outbound.js +2 -1
  9. package/server/dist/connections/prompt.js +4 -0
  10. package/server/dist/connections/router.js +3 -2
  11. package/server/dist/connections/store.js +4 -43
  12. package/server/dist/connections/telegram.js +43 -3
  13. package/server/dist/cron.js +2 -1
  14. package/server/dist/db/json.js +16 -0
  15. package/server/dist/db/migrations.js +199 -0
  16. package/server/dist/db.js +98 -196
  17. package/server/dist/errors.js +11 -0
  18. package/server/dist/git/browse.js +99 -0
  19. package/server/dist/git/ops.js +136 -19
  20. package/server/dist/git/repo.js +2 -1
  21. package/server/dist/history/claude.js +21 -21
  22. package/server/dist/history/index.js +17 -9
  23. package/server/dist/history/kiro.js +17 -12
  24. package/server/dist/history/lines.js +12 -0
  25. package/server/dist/history/opencode.js +18 -1
  26. package/server/dist/http/body.js +46 -0
  27. package/server/dist/http/errors.js +21 -0
  28. package/server/dist/http/guards.js +44 -0
  29. package/server/dist/http/router.js +24 -0
  30. package/server/dist/http/routes/agents.js +103 -0
  31. package/server/dist/http/routes/channel.js +16 -0
  32. package/server/dist/http/routes/colonies.js +9 -0
  33. package/server/dist/http/routes/connections.js +36 -0
  34. package/server/dist/http/routes/fs.js +22 -0
  35. package/server/dist/http/routes/git.js +90 -0
  36. package/server/dist/http/routes/meta.js +11 -0
  37. package/server/dist/http/routes/notebook.js +34 -0
  38. package/server/dist/http/routes/orchestration.js +32 -0
  39. package/server/dist/http/routes/resources.js +68 -0
  40. package/server/dist/http/routes/schedules.js +40 -0
  41. package/server/dist/http/routes/skills.js +47 -0
  42. package/server/dist/http/routes/terminals.js +18 -0
  43. package/server/dist/http/routes/types.js +36 -0
  44. package/server/dist/http/validate.js +65 -0
  45. package/server/dist/index.js +67 -5
  46. package/server/dist/instructions.js +8 -2
  47. package/server/dist/objects/agent.js +49 -0
  48. package/server/dist/objects/database.js +451 -0
  49. package/server/dist/objects/db-history.js +79 -0
  50. package/server/dist/objects/docker-info.js +75 -0
  51. package/server/dist/objects/docker.js +285 -0
  52. package/server/dist/objects/driver-props.js +196 -0
  53. package/server/dist/objects/exec.js +16 -0
  54. package/server/dist/objects/http-history.js +44 -0
  55. package/server/dist/objects/http-script.js +644 -0
  56. package/server/dist/objects/http.js +735 -0
  57. package/server/dist/objects/index.js +445 -0
  58. package/server/dist/objects/model.js +250 -0
  59. package/server/dist/objects/runner.js +211 -0
  60. package/server/dist/objects/stats.js +106 -0
  61. package/server/dist/objects/store.js +115 -0
  62. package/server/dist/objects/supervisor.js +111 -0
  63. package/server/dist/objects/templates.js +67 -0
  64. package/server/dist/providers/claude.js +3 -13
  65. package/server/dist/providers/index.js +7 -1
  66. package/server/dist/providers/kiro.js +2 -5
  67. package/server/dist/providers/opencode.js +2 -5
  68. package/server/dist/providers/preamble.js +2 -0
  69. package/server/dist/providers/spawn.js +31 -3
  70. package/server/dist/runtime.js +8 -6
  71. package/server/dist/schedules.js +2 -21
  72. package/server/dist/seed.js +15 -11
  73. package/server/dist/services/agents.js +90 -0
  74. package/server/dist/services/colonies.js +38 -0
  75. package/server/dist/services/connections.js +76 -0
  76. package/server/dist/services/notebook.js +22 -0
  77. package/server/dist/services/providers.js +27 -0
  78. package/server/dist/services/schedules.js +18 -0
  79. package/server/dist/sessions-summary.js +51 -0
  80. package/server/dist/skills/defaults.js +37 -0
  81. package/server/dist/skills/notebook.js +3 -8
  82. package/server/dist/terminals.js +177 -0
  83. package/server/dist/wake.js +13 -16
  84. package/server/dist/watch.js +2 -8
  85. package/server/mcp/dispatch.mjs +70 -1
  86. package/web/.next/BUILD_ID +1 -1
  87. package/web/.next/app-build-manifest.json +150 -98
  88. package/web/.next/app-path-routes-manifest.json +7 -5
  89. package/web/.next/build-manifest.json +14 -14
  90. package/web/.next/prerender-manifest.json +67 -43
  91. package/web/.next/react-loadable-manifest.json +26 -1
  92. package/web/.next/required-server-files.json +13 -1
  93. package/web/.next/routes-manifest.json +26 -0
  94. package/web/.next/server/app/_not-found/page.js +2 -2
  95. package/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  96. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  97. package/web/.next/server/app/_not-found.html +1 -1
  98. package/web/.next/server/app/_not-found.rsc +12 -11
  99. package/web/.next/server/app/agents/[id]/page.js +4 -4
  100. package/web/.next/server/app/agents/[id]/page.js.nft.json +1 -1
  101. package/web/.next/server/app/agents/[id]/page_client-reference-manifest.js +1 -1
  102. package/web/.next/server/app/agents/page.js +2 -2
  103. package/web/.next/server/app/agents/page.js.nft.json +1 -1
  104. package/web/.next/server/app/agents/page_client-reference-manifest.js +1 -1
  105. package/web/.next/server/app/agents.html +1 -1
  106. package/web/.next/server/app/agents.rsc +14 -13
  107. package/web/.next/server/app/connections/page.js +2 -2
  108. package/web/.next/server/app/connections/page.js.nft.json +1 -1
  109. package/web/.next/server/app/connections/page_client-reference-manifest.js +1 -1
  110. package/web/.next/server/app/connections.html +1 -1
  111. package/web/.next/server/app/connections.rsc +14 -13
  112. package/web/.next/server/app/index.html +1 -1
  113. package/web/.next/server/app/index.rsc +14 -13
  114. package/web/.next/server/app/page.js +2 -2
  115. package/web/.next/server/app/page.js.nft.json +1 -1
  116. package/web/.next/server/app/page_client-reference-manifest.js +1 -1
  117. package/web/.next/server/app/relations/page.js +2 -2
  118. package/web/.next/server/app/relations/page.js.nft.json +1 -1
  119. package/web/.next/server/app/relations/page_client-reference-manifest.js +1 -1
  120. package/web/.next/server/app/relations.html +1 -1
  121. package/web/.next/server/app/relations.rsc +14 -13
  122. package/web/.next/server/app/resources/[id]/page.js +2 -0
  123. package/web/.next/server/app/resources/[id]/page.js.nft.json +1 -0
  124. package/web/.next/server/app/resources/[id]/page_client-reference-manifest.js +1 -0
  125. package/web/.next/server/app/resources/page.js +2 -0
  126. package/web/.next/server/app/resources/page.js.nft.json +1 -0
  127. package/web/.next/server/app/resources/page_client-reference-manifest.js +1 -0
  128. package/web/.next/server/app/resources.html +1 -0
  129. package/web/.next/server/app/resources.meta +15 -0
  130. package/web/.next/server/app/resources.rsc +25 -0
  131. package/web/.next/server/app/schedules/page.js +2 -2
  132. package/web/.next/server/app/schedules/page.js.nft.json +1 -1
  133. package/web/.next/server/app/schedules/page_client-reference-manifest.js +1 -1
  134. package/web/.next/server/app/schedules.html +1 -1
  135. package/web/.next/server/app/schedules.rsc +14 -13
  136. package/web/.next/server/app/sessions/page.js +2 -2
  137. package/web/.next/server/app/sessions/page.js.nft.json +1 -1
  138. package/web/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
  139. package/web/.next/server/app/sessions.html +1 -1
  140. package/web/.next/server/app/sessions.rsc +14 -13
  141. package/web/.next/server/app/skills/page.js +2 -2
  142. package/web/.next/server/app/skills/page.js.nft.json +1 -1
  143. package/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  144. package/web/.next/server/app/skills.html +1 -1
  145. package/web/.next/server/app/skills.rsc +14 -13
  146. package/web/.next/server/app/types/page.js +2 -2
  147. package/web/.next/server/app/types/page.js.nft.json +1 -1
  148. package/web/.next/server/app/types/page_client-reference-manifest.js +1 -1
  149. package/web/.next/server/app/types.html +1 -1
  150. package/web/.next/server/app/types.rsc +14 -13
  151. package/web/.next/server/app-paths-manifest.json +7 -5
  152. package/web/.next/server/chunks/143.js +1 -1
  153. package/web/.next/server/chunks/274.js +1 -0
  154. package/web/.next/server/chunks/306.js +1 -0
  155. package/web/.next/server/chunks/423.js +1 -0
  156. package/web/.next/server/chunks/505.js +6 -0
  157. package/web/.next/server/chunks/555.js +1 -0
  158. package/web/.next/server/chunks/612.js +1 -0
  159. package/web/.next/server/chunks/638.js +1 -0
  160. package/web/.next/server/chunks/692.js +18 -0
  161. package/web/.next/server/chunks/716.js +2 -0
  162. package/web/.next/server/chunks/790.js +11 -0
  163. package/web/.next/server/chunks/802.js +1 -0
  164. package/web/.next/server/chunks/890.js +1 -1
  165. package/web/.next/server/chunks/919.js +1 -0
  166. package/web/.next/server/chunks/950.js +6 -0
  167. package/web/.next/server/middleware-build-manifest.js +1 -1
  168. package/web/.next/server/middleware-react-loadable-manifest.js +1 -1
  169. package/web/.next/server/next-font-manifest.js +1 -1
  170. package/web/.next/server/next-font-manifest.json +1 -1
  171. package/web/.next/server/pages/404.html +1 -1
  172. package/web/.next/server/pages/500.html +1 -1
  173. package/web/.next/server/server-reference-manifest.json +1 -1
  174. package/web/.next/static/chunks/0937d497-114a2a3de515565e.js +1 -0
  175. package/web/.next/static/chunks/176-aeaf907a6ea5627d.js +1 -0
  176. package/web/.next/static/chunks/291-efae53ea5f7ee92d.js +1 -0
  177. package/web/.next/static/chunks/2b9ff8ee-969e65cc354b2150.js +1 -0
  178. package/web/.next/static/chunks/30-9adcbe92b15a6815.js +1 -0
  179. package/web/.next/static/chunks/316-26da9cc233ba7557.js +1 -0
  180. package/web/.next/static/chunks/350-56599dc3bf06066d.js +1 -0
  181. package/web/.next/static/chunks/486-13e08542f3a1cbac.js +1 -0
  182. package/web/.next/static/chunks/491-6b4e6201f5110dff.js +1 -0
  183. package/web/.next/static/chunks/517-ccaf8cc5c733cbd8.js +1 -0
  184. package/web/.next/static/chunks/545.bfbd92b7632c09de.js +1 -0
  185. package/web/.next/static/chunks/{805-d946ebedb7e85f73.js → 562-7c2e33d838e869bf.js} +1 -1
  186. package/web/.next/static/chunks/581-3ad5c4343dad3f50.js +1 -0
  187. package/web/.next/static/chunks/586-f7017d585a84738d.js +1 -0
  188. package/web/.next/static/chunks/657.b4c39e676c3e20dc.js +1 -0
  189. package/web/.next/static/chunks/701.6abcaf5e60463eef.js +6 -0
  190. package/web/.next/static/chunks/705-a16e1fe09be1ac7f.js +1 -0
  191. package/web/.next/static/chunks/706-fa2be0f10ffebe6a.js +1 -0
  192. package/web/.next/static/chunks/837-33c7480524f48577.js +1 -0
  193. package/web/.next/static/chunks/839-23c5508622fae942.js +1 -0
  194. package/web/.next/static/chunks/844-fe55292e0701f436.js +1 -0
  195. package/web/.next/static/chunks/89-b194fcffe5120c44.js +1 -0
  196. package/web/.next/static/chunks/97.594543c2196de524.js +1 -0
  197. package/web/.next/static/chunks/app/_not-found/page-238a7b80bda6b4ae.js +1 -0
  198. package/web/.next/static/chunks/app/agents/[id]/page-4af6c246c4ca7a97.js +1 -0
  199. package/web/.next/static/chunks/app/agents/page-f4c4c80f955f3967.js +1 -0
  200. package/web/.next/static/chunks/app/connections/page-0936beaf02d863c6.js +1 -0
  201. package/web/.next/static/chunks/app/layout-87ebb70bd9cb8443.js +1 -0
  202. package/web/.next/static/chunks/app/page-a7f292117b7d76bf.js +1 -0
  203. package/web/.next/static/chunks/app/relations/page-fba25393c8688714.js +1 -0
  204. package/web/.next/static/chunks/app/resources/[id]/page-d110d89bb8b5fca7.js +1 -0
  205. package/web/.next/static/chunks/app/resources/page-bbd0b450d3f3131d.js +1 -0
  206. package/web/.next/static/chunks/app/schedules/page-a1124397fc760413.js +1 -0
  207. package/web/.next/static/chunks/app/sessions/page-0e322811b0cde724.js +1 -0
  208. package/web/.next/static/chunks/app/skills/page-22ea0ef89d9f7a13.js +1 -0
  209. package/web/.next/static/chunks/app/types/page-18c4e1db2724d41d.js +1 -0
  210. package/web/.next/static/chunks/fcafe1ac.366e4950d3b0d4b7.js +18 -0
  211. package/web/.next/static/chunks/framework-bbac72adbbe2d328.js +1 -0
  212. package/web/.next/static/chunks/main-app-6086068e5fb5eacd.js +1 -0
  213. package/web/.next/static/chunks/main-c15f3fee674e0303.js +1 -0
  214. package/web/.next/static/chunks/pages/_app-d1670a2c6ce392fc.js +1 -0
  215. package/web/.next/static/chunks/pages/_error-95aac014d202fa7a.js +1 -0
  216. package/web/.next/static/chunks/webpack-ef515e41a83a16ac.js +1 -0
  217. package/web/.next/static/css/945795da867f5250.css +1 -0
  218. package/web/.next/static/css/dd4e32c285f75776.css +1 -0
  219. package/web/.next/static/p4S3i9kvO359GW91DQiHu/_buildManifest.js +1 -0
  220. package/web/.next/trace +29 -23
  221. package/web/.next/types/app/resources/[id]/page.ts +84 -0
  222. package/web/.next/types/app/resources/page.ts +84 -0
  223. package/web/.next/types/routes.d.ts +6 -2
  224. package/web/.next/types/validator.ts +18 -0
  225. package/web/next.config.mjs +4 -0
  226. package/web/.next/server/chunks/247.js +0 -1
  227. package/web/.next/server/chunks/484.js +0 -11
  228. package/web/.next/server/chunks/851.js +0 -1
  229. package/web/.next/server/chunks/932.js +0 -2
  230. package/web/.next/static/_z2hBOHhwiR5ytdI_8alh/_buildManifest.js +0 -1
  231. package/web/.next/static/chunks/0937d497-44abb0f9355ace92.js +0 -1
  232. package/web/.next/static/chunks/2b9ff8ee-c3b61ac1e608c57d.js +0 -1
  233. package/web/.next/static/chunks/463-6ae35cdefdc59169.js +0 -1
  234. package/web/.next/static/chunks/493-50d2adf9fd5ec680.js +0 -1
  235. package/web/.next/static/chunks/517-eb26c72f6059aa26.js +0 -1
  236. package/web/.next/static/chunks/589-8bf536f331de4269.js +0 -1
  237. package/web/.next/static/chunks/657.f1e03b7b65eba1d9.js +0 -1
  238. package/web/.next/static/chunks/687-3a4205f51be38602.js +0 -1
  239. package/web/.next/static/chunks/837-fd7623601dc78ed8.js +0 -1
  240. package/web/.next/static/chunks/850-318f32484989dd59.js +0 -1
  241. package/web/.next/static/chunks/89-ace2e1aa63babfdf.js +0 -1
  242. package/web/.next/static/chunks/951-e250b541b67da7ce.js +0 -1
  243. package/web/.next/static/chunks/app/_not-found/page-bbd54d7b9a55d7ae.js +0 -1
  244. package/web/.next/static/chunks/app/agents/[id]/page-a8b7a43d2c18483f.js +0 -1
  245. package/web/.next/static/chunks/app/agents/page-e9b17f1c3439ef98.js +0 -1
  246. package/web/.next/static/chunks/app/connections/page-477f36fcd5b13d78.js +0 -1
  247. package/web/.next/static/chunks/app/layout-a2d38e3de5911b1b.js +0 -1
  248. package/web/.next/static/chunks/app/page-6fc87fc5a128a204.js +0 -1
  249. package/web/.next/static/chunks/app/relations/page-0355a93a8eb67989.js +0 -1
  250. package/web/.next/static/chunks/app/schedules/page-39befd15b3b20586.js +0 -1
  251. package/web/.next/static/chunks/app/sessions/page-beae9a74a07d69e0.js +0 -1
  252. package/web/.next/static/chunks/app/skills/page-4f839ac913e26d32.js +0 -1
  253. package/web/.next/static/chunks/app/types/page-4f6b99af3232f861.js +0 -1
  254. package/web/.next/static/chunks/framework-0e68884e76d9a8d3.js +0 -1
  255. package/web/.next/static/chunks/main-app-d6f73ccd41f325c5.js +0 -1
  256. package/web/.next/static/chunks/main-cd22509db15764e2.js +0 -1
  257. package/web/.next/static/chunks/pages/_app-0f75d689c738a6fc.js +0 -1
  258. package/web/.next/static/chunks/pages/_error-d481f6d49fb4455d.js +0 -1
  259. package/web/.next/static/chunks/webpack-d40de72e52f65f79.js +0 -1
  260. package/web/.next/static/css/da22f0402b1c40ae.css +0 -1
  261. /package/web/.next/static/{_z2hBOHhwiR5ytdI_8alh → p4S3i9kvO359GW91DQiHu}/_ssgManifest.js +0 -0
@@ -0,0 +1,65 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { agents, colonies } from '../db.js';
3
+ import { validVision } from '../connections/vision.js';
4
+ import { isProvider } from '../services/providers.js';
5
+ import { bad } from './errors.js';
6
+ /** Plain value helpers: what a field is when it may be missing or of the wrong type. */
7
+ export const isRecord = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
8
+ /** Text of a field, '' when missing (like `String(v ?? '')`). */
9
+ export const text = (v) => String(v ?? '');
10
+ export const trimmed = (v) => text(v).trim();
11
+ export const optText = (v) => (v === undefined || v === null || v === '' ? undefined : String(v));
12
+ export const strList = (v) => (Array.isArray(v) ? v.map(String) : []);
13
+ export const numList = (v) => (Array.isArray(v) ? v.map(Number) : []);
14
+ export const requireName = (v) => { const n = trimmed(v); if (!n)
15
+ throw bad('Name is required'); return n; };
16
+ /** The `path` query parameter every git file endpoint needs. */
17
+ export const qpath = (url) => { const p = url.searchParams.get('path'); if (!p)
18
+ throw bad('path is required'); return p; };
19
+ /** A folder that must exist when given. */
20
+ const checkFolder = (cwd) => { if (cwd && (typeof cwd !== 'string' || !existsSync(cwd)))
21
+ throw bad(`Folder does not exist: ${String(cwd)}`); };
22
+ export function validateAgentInput(p, partial = false) {
23
+ if (!partial || p.name !== undefined)
24
+ requireName(p.name);
25
+ if (p.provider !== undefined && !isProvider(p.provider))
26
+ throw bad(`Unknown provider "${text(p.provider)}"`);
27
+ if (p.role !== undefined && !['orchestrator', 'worker'].includes(p.role))
28
+ throw bad('Role must be orchestrator or worker');
29
+ checkFolder(p.cwd);
30
+ if (p.colony_id && (typeof p.colony_id !== 'string' || !colonies.get(p.colony_id)))
31
+ throw bad('That colony no longer exists');
32
+ return p;
33
+ }
34
+ export function validateColony(p, partial = false) {
35
+ if (!partial || p.name !== undefined)
36
+ requireName(p.name);
37
+ checkFolder(p.cwd);
38
+ return p;
39
+ }
40
+ export const CONNECTION_KINDS = ['telegram', 'slack'];
41
+ const REQUIRED = { telegram: ['token'], slack: ['bot_token', 'app_token'] };
42
+ export function validateConnection(kind, config, agentId, enabled) {
43
+ const missing = (REQUIRED[kind] ?? []).filter((k) => typeof config[k] !== 'string' || !config[k].trim());
44
+ if (missing.length)
45
+ throw bad(`Missing: ${missing.join(', ')}`);
46
+ if (config.effort !== undefined && !['', 'low', 'medium', 'high'].includes(config.effort))
47
+ throw bad('effort must be empty, low, medium or high');
48
+ if (config.on_silent !== undefined && !['notice', 'send_text', 'ignore'].includes(config.on_silent))
49
+ throw bad('on_silent must be notice, send_text or ignore');
50
+ if (config.group_mode !== undefined && !['mention', 'open'].includes(config.group_mode))
51
+ throw bad('group_mode must be mention or open');
52
+ if (config.vision !== undefined && config.vision !== null && !validVision(config.vision))
53
+ throw bad('vision must be { provider, model } or empty');
54
+ if (config.chats !== undefined && (!Array.isArray(config.chats) || config.chats.length > 50))
55
+ throw bad('chats must be a list of at most 50 groups');
56
+ if (config.aliases !== undefined && (!Array.isArray(config.aliases) || config.aliases.length > 10))
57
+ throw bad('aliases must be a list of at most 10 names');
58
+ if (agentId) {
59
+ const a = agents.get(agentId);
60
+ if (!a)
61
+ throw bad('That agent no longer exists');
62
+ if (enabled && a.effective.permission === 'plan')
63
+ throw bad(`"${a.name}" is in Plan mode and cannot reply. Give it "Edit files" permission first.`);
64
+ }
65
+ }
@@ -2,27 +2,89 @@ import { createServer } from 'node:http';
2
2
  import { WebSocketServer } from 'ws';
3
3
  import { handle } from './api.js';
4
4
  import { bus } from './runtime.js';
5
- import { skills } from './db.js';
5
+ import { db, skills } from './db.js';
6
6
  import { DEFAULT_SKILLS } from './skills/defaults.js';
7
- import { seedIfEmpty } from './seed.js';
7
+ import { dropStarterSkills, seedIfEmpty } from './seed.js';
8
8
  import { API_PORT } from './mcp-config.js';
9
9
  import './connections/index.js';
10
- import { startAll } from './connections/manager.js';
11
- import { migrateChannelSkill } from './connections/channel-skill.js';
10
+ import { startAll, stopAll } from './connections/manager.js';
11
+ import { addReactionsToChannelSkill, migrateChannelSkill } from './connections/channel-skill.js';
12
12
  import { armWakeups } from './wake.js';
13
13
  import { armSchedules } from './schedules.js';
14
14
  import { armWatches } from './watch.js';
15
+ import { armObjects } from './objects/index.js';
16
+ import { attachTerminal, closeAllTerminals, terminalsEnabled } from './terminals.js';
17
+ import { isLocalRequest } from './http/guards.js';
15
18
  skills.seedDefaults(DEFAULT_SKILLS);
16
19
  seedIfEmpty();
20
+ dropStarterSkills();
17
21
  migrateChannelSkill();
22
+ addReactionsToChannelSkill();
18
23
  armWakeups();
19
24
  armSchedules();
20
25
  armWatches();
26
+ armObjects();
21
27
  const server = createServer((req, res) => { void handle(req, res); });
22
- const wss = new WebSocketServer({ server, path: '/ws' });
28
+ // Two sockets share the port: `/ws` (what the interface listens to) and `/ws/terminal?id=` (one terminal). Terminals only for this machine.
29
+ const wss = new WebSocketServer({ noServer: true });
30
+ const termWss = new WebSocketServer({ noServer: true });
31
+ server.on('upgrade', (req, socket, head) => {
32
+ const url = new URL(req.url ?? '/', 'http://localhost');
33
+ if (url.pathname === '/ws')
34
+ wss.handleUpgrade(req, socket, head, (ws) => wss.emit('connection', ws, req));
35
+ else if (url.pathname === '/ws/terminal') {
36
+ if (!isLocalRequest(req) || !terminalsEnabled()) {
37
+ socket.write('HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n');
38
+ socket.destroy();
39
+ return;
40
+ }
41
+ termWss.handleUpgrade(req, socket, head, (ws) => attachTerminal(ws, url.searchParams.get('id') ?? ''));
42
+ }
43
+ else
44
+ socket.destroy();
45
+ });
23
46
  wss.on('connection', (ws) => {
24
47
  const onMsg = (m) => ws.readyState === ws.OPEN && ws.send(JSON.stringify(m));
25
48
  bus.on('msg', onMsg);
26
49
  ws.on('close', () => bus.off('msg', onMsg));
27
50
  });
51
+ server.on('error', (e) => {
52
+ console.error(e.code === 'EADDRINUSE' ? `[server] port ${API_PORT} is already in use: is hive-am already running? (HIVE_AM_PORT changes it)` : `[server] ${e.message}`);
53
+ process.exit(1);
54
+ });
28
55
  server.listen(API_PORT, '127.0.0.1', () => { console.log(`hive-am server → http://127.0.0.1:${API_PORT}`); void startAll(); });
56
+ // ---- shutdown ----
57
+ // Ctrl+C or a service stop: stop taking requests, drop the sockets, end the shells, stop the channel connections and close the database
58
+ // (which merges its write-ahead log). Servers and containers kept as resources are NOT stopped: they outlive hive-am by design.
59
+ // A step that hangs cannot hold the process: after 5 s it leaves anyway.
60
+ let stopping = false;
61
+ async function shutdown(why, code) {
62
+ if (stopping)
63
+ return;
64
+ stopping = true;
65
+ console.log(`[server] ${why}: shutting down`);
66
+ const force = setTimeout(() => { console.error('[server] shutdown took too long: leaving'); process.exit(code || 1); }, 5000);
67
+ force.unref();
68
+ const step = async (name, f) => { try {
69
+ await f();
70
+ }
71
+ catch (e) {
72
+ console.error(`[server] shutdown, ${name}:`, e);
73
+ } };
74
+ await step('http', () => new Promise((done) => { server.close(() => done()); server.closeAllConnections(); }));
75
+ await step('websockets', () => { for (const w of [wss, termWss]) {
76
+ for (const c of w.clients)
77
+ c.terminate();
78
+ w.close();
79
+ } });
80
+ await step('terminals', closeAllTerminals);
81
+ await step('connections', stopAll);
82
+ await step('database', () => db.close());
83
+ process.exit(code);
84
+ }
85
+ process.on('SIGINT', () => void shutdown('SIGINT', 0));
86
+ process.on('SIGTERM', () => void shutdown('SIGTERM', 0));
87
+ // Policy: a stray rejected promise (a provider, a network call) is logged and the server keeps serving, because it holds terminals and
88
+ // running turns that a crash would take down; an uncaught exception leaves the process in an unknown state, so it is logged and the server restarts cleanly.
89
+ process.on('unhandledRejection', (reason) => console.error('[server] unhandled rejection:', reason));
90
+ process.on('uncaughtException', (e) => { console.error('[server] uncaught exception:', e); void shutdown('uncaught exception', 1); });
@@ -1,7 +1,7 @@
1
1
  import { agents, colonies, skills } from './db.js';
2
2
  import { connections } from './connections/store.js';
3
3
  import { platformName } from './connections/prompt.js';
4
- import { NOTEBOOK_SKILL_ID, WAKEUPS_SKILL_ID } from './skills/defaults.js';
4
+ import { NOTEBOOK_SKILL_ID, OBJECTS_SKILL_ID, WAKEUPS_SKILL_ID } from './skills/defaults.js';
5
5
  import { NOTEBOOK_MAX, notebooks } from './skills/notebook.js';
6
6
  /**
7
7
  * What an agent is told and which hive tools it gets: its identity, prompt, skills, team, channels and notebook, and the
@@ -27,6 +27,8 @@ export function mcpCaps(a) {
27
27
  caps.push('memory');
28
28
  if (a.skill_ids.includes(WAKEUPS_SKILL_ID))
29
29
  caps.push('wake');
30
+ if (a.skill_ids.includes(OBJECTS_SKILL_ID))
31
+ caps.push('objects');
30
32
  if (lazySkills(a).length > 0)
31
33
  caps.push('skills');
32
34
  return caps;
@@ -83,12 +85,16 @@ ${lazy.map((s) => `- **${s.name}**${s.description ? `: ${s.description}` : ''}`)
83
85
  if (aliases.length)
84
86
  parts.push(`## Your names in chats\nBesides "${a.name}", people also call you ${aliases.map((x) => `"${x}"`).join(', ')}. These are your aliases: a message that uses one of them is aimed at you. If someone asks what your aliases or nicknames are, list them.`);
85
87
  parts.push(`## Messages from ${names}
86
- People can write to you from ${names}. Those messages start with a header line \`[hive:channel] …\` that says the platform, place, thread and sender. **Your normal text is not delivered to them**: to answer, call the \`channel_reply\` tool (${toolName(a.provider, 'channel_reply')}) with the text. Other tools: \`channel_send_file\` (${toolName(a.provider, 'channel_send_file')}) sends a file from your working folder; \`channel_mute\` (${toolName(a.provider, 'channel_mute')}) keeps you quiet in a thread. Follow the "Chat channels" skill for how to behave in chats.
88
+ People can write to you from ${names}. Those messages start with a header line \`[hive:channel] …\` that says the platform, place, thread and sender. **Your normal text is not delivered to them**: to answer, call the \`channel_reply\` tool (${toolName(a.provider, 'channel_reply')}) with the text. Other tools: \`channel_send_file\` (${toolName(a.provider, 'channel_send_file')}) sends a file from your working folder; \`channel_react\` (${toolName(a.provider, 'channel_react')}) puts one emoji on the message you are handling (only the emoji the platform allows); \`channel_mute\` (${toolName(a.provider, 'channel_mute')}) keeps you quiet in a thread. Follow the "Chat channels" skill for how to behave in chats.
87
89
  Whatever your skills say: when you answer a \`[hive:channel]\` message, never write a secret (passwords, tokens, keys, \`.env\` contents) in the reply or in a file, to anyone, not even an admin. This does not apply to the hive-am web chat.`);
88
90
  }
89
91
  if (a.skill_ids.includes(WAKEUPS_SKILL_ID)) {
90
92
  parts.push(`## Wake-ups
91
93
  You cannot write on your own between messages. To tell the person something later, call \`wake_me\` (${toolName(a.provider, 'wake_me')}) with \`minutes\` and a \`note\`; you are woken then, in the same place, and answer. When you start a long command in the background and want to know the moment it ends, use \`wake_when_done\` (${toolName(a.provider, 'wake_when_done')}) with its pid. For something that repeats (every weekday at 9, every 30 minutes…) use \`schedule_create\` (${toolName(a.provider, 'schedule_create')}); \`schedule_list\` and \`schedule_cancel\` (${toolName(a.provider, 'schedule_list')}, ${toolName(a.provider, 'schedule_cancel')}) show or stop them. Follow the "Wake-ups" skill, and never promise a later notice without calling it.`);
94
+ }
95
+ if (a.skill_ids.includes(OBJECTS_SKILL_ID)) {
96
+ parts.push(`## Colony resources
97
+ You look after the servers and containers of your colony. Tools: \`object_list\` (${toolName(a.provider, 'object_list')}) shows them with their state; \`object_logs\` (${toolName(a.provider, 'object_logs')}) reads the latest output of one; \`object_action\` (${toolName(a.provider, 'object_action')}) starts, stops or restarts one${a.permission === 'plan' ? ' (not available to you: you are read-only)' : ''}. Follow the "Colony resources" skill. You cannot create or change resources.`);
92
98
  }
93
99
  if (a.skill_ids.includes(NOTEBOOK_SKILL_ID)) {
94
100
  const nb = { read: toolName(a.provider, 'notebook_read'), add: toolName(a.provider, 'notebook_add'), rewrite: toolName(a.provider, 'notebook_rewrite') };
@@ -0,0 +1,49 @@
1
+ import { agents, resolved } from '../db.js';
2
+ import { mcpCaps } from '../instructions.js';
3
+ import { objectAction, objectLogs, listObjects } from './index.js';
4
+ import { ObjectError } from './model.js';
5
+ import { noteServer } from './runner.js';
6
+ /**
7
+ * What an agent with the "Colony resources" skill can do with objects: see them, read their logs and start / stop / restart them. Never
8
+ * create, change or delete. The rules are enforced here, not just written in the skill:
9
+ * - only the objects of the agent's own colony (or the ones with no colony, for an agent with none), unless a person chose another agent to look after it (then only that one);
10
+ * - a read-only agent can look but not act;
11
+ * - an unknown or foreign object looks the same as one that does not exist.
12
+ */
13
+ function whoAsks(from) {
14
+ const a = typeof from === 'string' ? agents.get(from) : undefined;
15
+ if (!a)
16
+ throw new ObjectError('Unknown agent');
17
+ if (!mcpCaps(resolved(a)).includes('objects'))
18
+ throw new ObjectError('You do not have the "Colony resources" skill.');
19
+ return a;
20
+ }
21
+ /** A resource with a chosen manager is for that agent only; with none, for the agents of its colony. */
22
+ const visibleTo = (a) => (o) => (o.manager_id ? o.manager_id === a.id : (o.colony_id ?? null) === (a.colony_id ?? null));
23
+ export function agentObjects(from) {
24
+ const a = whoAsks(from);
25
+ return listObjects().filter(visibleTo(a)).map((o) => ({ name: o.name, kind: o.kind, status: o.state.status, detail: o.state.detail ?? null, since: o.state.since ?? null }));
26
+ }
27
+ function pick(from, name) {
28
+ const a = whoAsks(from);
29
+ const o = listObjects().filter(visibleTo(a)).find((x) => x.name.toLowerCase() === String(name ?? '').trim().toLowerCase());
30
+ if (!o)
31
+ throw new ObjectError(`There is no resource named "${String(name ?? '')}" for you. Call object_list for the names.`);
32
+ return { a, o };
33
+ }
34
+ export async function agentObjectAction(from, name, action) {
35
+ const { a, o } = pick(from, name);
36
+ if (action !== 'start' && action !== 'stop' && action !== 'restart')
37
+ throw new ObjectError('The action must be start, stop or restart');
38
+ if (a.permission === 'plan')
39
+ throw new ObjectError('You are read-only: you can look at resources but not start, stop or restart them.');
40
+ if (o.kind === 'server')
41
+ noteServer(o.id, `${a.name} asked to ${action}`);
42
+ const v = await objectAction(o.id, action);
43
+ return { name: v.name, status: v.state.status, detail: v.state.detail ?? null };
44
+ }
45
+ export async function agentObjectLogs(from, name, tail) {
46
+ const { o } = pick(from, name);
47
+ const n = Number(tail);
48
+ return { name: o.name, text: (await objectLogs(o.id, { tail: Number.isFinite(n) && n > 0 ? Math.min(n, 500) : 80 })).text };
49
+ }