heyio 1.13.0 → 3.0.1

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 (380) hide show
  1. package/README.md +162 -278
  2. package/dist/api/middleware/auth.d.ts +14 -0
  3. package/dist/api/middleware/auth.d.ts.map +1 -0
  4. package/dist/api/middleware/auth.js +66 -0
  5. package/dist/api/middleware/auth.js.map +1 -0
  6. package/dist/api/notifications.d.ts +14 -0
  7. package/dist/api/notifications.d.ts.map +1 -0
  8. package/dist/api/notifications.js +112 -0
  9. package/dist/api/notifications.js.map +1 -0
  10. package/dist/api/routes/activity.d.ts +3 -0
  11. package/dist/api/routes/activity.d.ts.map +1 -0
  12. package/dist/api/routes/activity.js +28 -0
  13. package/dist/api/routes/activity.js.map +1 -0
  14. package/dist/api/routes/attachments.d.ts +3 -0
  15. package/dist/api/routes/attachments.d.ts.map +1 -0
  16. package/dist/api/routes/attachments.js +83 -0
  17. package/dist/api/routes/attachments.js.map +1 -0
  18. package/dist/api/routes/config.d.ts +3 -0
  19. package/dist/api/routes/config.d.ts.map +1 -0
  20. package/dist/api/routes/config.js +106 -0
  21. package/dist/api/routes/config.js.map +1 -0
  22. package/dist/api/routes/conversations.d.ts +3 -0
  23. package/dist/api/routes/conversations.d.ts.map +1 -0
  24. package/dist/api/routes/conversations.js +69 -0
  25. package/dist/api/routes/conversations.js.map +1 -0
  26. package/dist/api/routes/health.d.ts +3 -0
  27. package/dist/api/routes/health.d.ts.map +1 -0
  28. package/dist/api/routes/health.js +16 -0
  29. package/dist/api/routes/health.js.map +1 -0
  30. package/dist/api/routes/inbox.d.ts +3 -0
  31. package/dist/api/routes/inbox.d.ts.map +1 -0
  32. package/dist/api/routes/inbox.js +88 -0
  33. package/dist/api/routes/inbox.js.map +1 -0
  34. package/dist/api/routes/schedules.d.ts +3 -0
  35. package/dist/api/routes/schedules.d.ts.map +1 -0
  36. package/dist/api/routes/schedules.js +96 -0
  37. package/dist/api/routes/schedules.js.map +1 -0
  38. package/dist/api/routes/skills.d.ts +2 -0
  39. package/dist/api/routes/skills.d.ts.map +1 -0
  40. package/dist/api/routes/skills.js +85 -0
  41. package/dist/api/routes/skills.js.map +1 -0
  42. package/dist/api/routes/squads.d.ts +3 -0
  43. package/dist/api/routes/squads.d.ts.map +1 -0
  44. package/dist/api/routes/squads.js +129 -0
  45. package/dist/api/routes/squads.js.map +1 -0
  46. package/dist/api/routes/usage.d.ts +3 -0
  47. package/dist/api/routes/usage.d.ts.map +1 -0
  48. package/dist/api/routes/usage.js +55 -0
  49. package/dist/api/routes/usage.js.map +1 -0
  50. package/dist/api/routes/wiki.d.ts +2 -0
  51. package/dist/api/routes/wiki.d.ts.map +1 -0
  52. package/dist/api/routes/wiki.js +43 -0
  53. package/dist/api/routes/wiki.js.map +1 -0
  54. package/dist/api/server.d.ts +7 -0
  55. package/dist/api/server.d.ts.map +1 -0
  56. package/dist/api/server.js +136 -634
  57. package/dist/api/server.js.map +1 -0
  58. package/dist/config.d.ts +3 -0
  59. package/dist/config.d.ts.map +1 -0
  60. package/dist/config.js +2 -91
  61. package/dist/config.js.map +1 -0
  62. package/dist/copilot/client.d.ts +5 -0
  63. package/dist/copilot/client.d.ts.map +1 -0
  64. package/dist/copilot/client.js +19 -11
  65. package/dist/copilot/client.js.map +1 -0
  66. package/dist/copilot/health-monitor.d.ts +14 -0
  67. package/dist/copilot/health-monitor.d.ts.map +1 -0
  68. package/dist/copilot/health-monitor.js +70 -0
  69. package/dist/copilot/health-monitor.js.map +1 -0
  70. package/dist/copilot/orchestrator.d.ts +5 -0
  71. package/dist/copilot/orchestrator.d.ts.map +1 -0
  72. package/dist/copilot/orchestrator.js +127 -123
  73. package/dist/copilot/orchestrator.js.map +1 -0
  74. package/dist/copilot/tools.d.ts +49 -0
  75. package/dist/copilot/tools.d.ts.map +1 -0
  76. package/dist/copilot/tools.js +545 -321
  77. package/dist/copilot/tools.js.map +1 -0
  78. package/dist/index.d.ts +3 -0
  79. package/dist/index.d.ts.map +1 -0
  80. package/dist/index.js +82 -26
  81. package/dist/index.js.map +1 -0
  82. package/dist/logging/logger.d.ts +6 -0
  83. package/dist/logging/logger.d.ts.map +1 -0
  84. package/dist/logging/logger.js +21 -0
  85. package/dist/logging/logger.js.map +1 -0
  86. package/dist/models/index.d.ts +6 -0
  87. package/dist/models/index.d.ts.map +1 -0
  88. package/dist/models/index.js +4 -0
  89. package/dist/models/index.js.map +1 -0
  90. package/dist/models/pricing.d.ts +25 -0
  91. package/dist/models/pricing.d.ts.map +1 -0
  92. package/dist/models/pricing.js +96 -0
  93. package/dist/models/pricing.js.map +1 -0
  94. package/dist/models/registry.d.ts +34 -0
  95. package/dist/models/registry.d.ts.map +1 -0
  96. package/dist/models/registry.js +109 -0
  97. package/dist/models/registry.js.map +1 -0
  98. package/dist/models/token-tracker.d.ts +40 -0
  99. package/dist/models/token-tracker.d.ts.map +1 -0
  100. package/dist/models/token-tracker.js +102 -0
  101. package/dist/models/token-tracker.js.map +1 -0
  102. package/dist/scheduler/engine.d.ts +9 -0
  103. package/dist/scheduler/engine.d.ts.map +1 -0
  104. package/dist/scheduler/engine.js +127 -0
  105. package/dist/scheduler/engine.js.map +1 -0
  106. package/dist/skills/index.d.ts +3 -0
  107. package/dist/skills/index.d.ts.map +1 -0
  108. package/dist/skills/index.js +2 -0
  109. package/dist/skills/index.js.map +1 -0
  110. package/dist/skills/store.d.ts +52 -0
  111. package/dist/skills/store.d.ts.map +1 -0
  112. package/dist/skills/store.js +148 -0
  113. package/dist/skills/store.js.map +1 -0
  114. package/dist/squad/agent.d.ts +46 -0
  115. package/dist/squad/agent.d.ts.map +1 -0
  116. package/dist/squad/agent.js +261 -0
  117. package/dist/squad/agent.js.map +1 -0
  118. package/dist/squad/autonomy.d.ts +16 -0
  119. package/dist/squad/autonomy.d.ts.map +1 -0
  120. package/dist/squad/autonomy.js +63 -0
  121. package/dist/squad/autonomy.js.map +1 -0
  122. package/dist/squad/event-bus.d.ts +22 -0
  123. package/dist/squad/event-bus.d.ts.map +1 -0
  124. package/dist/squad/event-bus.js +56 -0
  125. package/dist/squad/event-bus.js.map +1 -0
  126. package/dist/squad/execution/index.d.ts +12 -0
  127. package/dist/squad/execution/index.d.ts.map +1 -0
  128. package/dist/squad/execution/index.js +7 -0
  129. package/dist/squad/execution/index.js.map +1 -0
  130. package/dist/squad/execution/instance.d.ts +40 -0
  131. package/dist/squad/execution/instance.d.ts.map +1 -0
  132. package/dist/squad/execution/instance.js +138 -0
  133. package/dist/squad/execution/instance.js.map +1 -0
  134. package/dist/squad/execution/meeting.d.ts +25 -0
  135. package/dist/squad/execution/meeting.d.ts.map +1 -0
  136. package/dist/squad/execution/meeting.js +140 -0
  137. package/dist/squad/execution/meeting.js.map +1 -0
  138. package/dist/squad/execution/pr.d.ts +15 -0
  139. package/dist/squad/execution/pr.d.ts.map +1 -0
  140. package/dist/squad/execution/pr.js +93 -0
  141. package/dist/squad/execution/pr.js.map +1 -0
  142. package/dist/squad/execution/runner.d.ts +22 -0
  143. package/dist/squad/execution/runner.d.ts.map +1 -0
  144. package/dist/squad/execution/runner.js +68 -0
  145. package/dist/squad/execution/runner.js.map +1 -0
  146. package/dist/squad/execution/tasks.d.ts +11 -0
  147. package/dist/squad/execution/tasks.d.ts.map +1 -0
  148. package/dist/squad/execution/tasks.js +85 -0
  149. package/dist/squad/execution/tasks.js.map +1 -0
  150. package/dist/squad/execution/worktree.d.ts +26 -0
  151. package/dist/squad/execution/worktree.d.ts.map +1 -0
  152. package/dist/squad/execution/worktree.js +111 -0
  153. package/dist/squad/execution/worktree.js.map +1 -0
  154. package/dist/squad/hiring.d.ts +32 -0
  155. package/dist/squad/hiring.d.ts.map +1 -0
  156. package/dist/squad/hiring.js +200 -0
  157. package/dist/squad/hiring.js.map +1 -0
  158. package/dist/squad/index.d.ts +8 -0
  159. package/dist/squad/index.d.ts.map +1 -0
  160. package/dist/squad/index.js +6 -0
  161. package/dist/squad/index.js.map +1 -0
  162. package/dist/squad/manager.d.ts +48 -0
  163. package/dist/squad/manager.d.ts.map +1 -0
  164. package/dist/squad/manager.js +274 -0
  165. package/dist/squad/manager.js.map +1 -0
  166. package/dist/squad/name-generator.d.ts +16 -0
  167. package/dist/squad/name-generator.d.ts.map +1 -0
  168. package/dist/squad/name-generator.js +113 -0
  169. package/dist/squad/name-generator.js.map +1 -0
  170. package/dist/squad/roles/templates.d.ts +5 -0
  171. package/dist/squad/roles/templates.d.ts.map +1 -0
  172. package/dist/squad/roles/templates.js +102 -0
  173. package/dist/squad/roles/templates.js.map +1 -0
  174. package/dist/squad/skill-parser.d.ts +36 -0
  175. package/dist/squad/skill-parser.d.ts.map +1 -0
  176. package/dist/squad/skill-parser.js +83 -0
  177. package/dist/squad/skill-parser.js.map +1 -0
  178. package/dist/squad/source-resolver.d.ts +20 -0
  179. package/dist/squad/source-resolver.d.ts.map +1 -0
  180. package/dist/squad/source-resolver.js +52 -0
  181. package/dist/squad/source-resolver.js.map +1 -0
  182. package/dist/store/activity.d.ts +43 -0
  183. package/dist/store/activity.d.ts.map +1 -0
  184. package/dist/store/activity.js +131 -0
  185. package/dist/store/activity.js.map +1 -0
  186. package/dist/store/db.d.ts +5 -0
  187. package/dist/store/db.d.ts.map +1 -0
  188. package/dist/store/db.js +209 -248
  189. package/dist/store/db.js.map +1 -0
  190. package/dist/store/inbox.d.ts +53 -0
  191. package/dist/store/inbox.d.ts.map +1 -0
  192. package/dist/store/inbox.js +151 -0
  193. package/dist/store/inbox.js.map +1 -0
  194. package/dist/store/schedules.d.ts +53 -0
  195. package/dist/store/schedules.d.ts.map +1 -0
  196. package/dist/store/schedules.js +149 -54
  197. package/dist/store/schedules.js.map +1 -0
  198. package/dist/wiki/index.d.ts +3 -0
  199. package/dist/wiki/index.d.ts.map +1 -0
  200. package/dist/wiki/index.js +2 -0
  201. package/dist/wiki/index.js.map +1 -0
  202. package/dist/wiki/store.d.ts +49 -0
  203. package/dist/wiki/store.d.ts.map +1 -0
  204. package/dist/wiki/store.js +115 -0
  205. package/dist/wiki/store.js.map +1 -0
  206. package/node_modules/@io/shared/dist/config.d.ts +25 -0
  207. package/node_modules/@io/shared/dist/config.d.ts.map +1 -0
  208. package/node_modules/@io/shared/dist/config.js +47 -0
  209. package/node_modules/@io/shared/dist/config.js.map +1 -0
  210. package/node_modules/@io/shared/dist/constants.d.ts +13 -0
  211. package/node_modules/@io/shared/dist/constants.d.ts.map +1 -0
  212. package/node_modules/@io/shared/dist/constants.js +34 -0
  213. package/node_modules/@io/shared/dist/constants.js.map +1 -0
  214. package/node_modules/@io/shared/dist/index.d.ts +11 -0
  215. package/node_modules/@io/shared/dist/index.d.ts.map +1 -0
  216. package/node_modules/@io/shared/dist/index.js +3 -0
  217. package/node_modules/@io/shared/dist/index.js.map +1 -0
  218. package/node_modules/@io/shared/dist/types/agents.d.ts +3 -0
  219. package/node_modules/@io/shared/dist/types/agents.d.ts.map +1 -0
  220. package/node_modules/@io/shared/dist/types/agents.js +2 -0
  221. package/node_modules/@io/shared/dist/types/agents.js.map +1 -0
  222. package/node_modules/@io/shared/dist/types/api.d.ts +33 -0
  223. package/node_modules/@io/shared/dist/types/api.d.ts.map +1 -0
  224. package/node_modules/@io/shared/dist/types/api.js +2 -0
  225. package/node_modules/@io/shared/dist/types/api.js.map +1 -0
  226. package/node_modules/@io/shared/dist/types/attachments.d.ts +10 -0
  227. package/node_modules/@io/shared/dist/types/attachments.d.ts.map +1 -0
  228. package/node_modules/@io/shared/dist/types/attachments.js +2 -0
  229. package/node_modules/@io/shared/dist/types/attachments.js.map +1 -0
  230. package/node_modules/@io/shared/dist/types/events.d.ts +44 -0
  231. package/node_modules/@io/shared/dist/types/events.d.ts.map +1 -0
  232. package/node_modules/@io/shared/dist/types/events.js +2 -0
  233. package/node_modules/@io/shared/dist/types/events.js.map +1 -0
  234. package/node_modules/@io/shared/dist/types/messages.d.ts +15 -0
  235. package/node_modules/@io/shared/dist/types/messages.d.ts.map +1 -0
  236. package/node_modules/@io/shared/dist/types/messages.js +2 -0
  237. package/node_modules/@io/shared/dist/types/messages.js.map +1 -0
  238. package/node_modules/@io/shared/dist/types/squads.d.ts +43 -0
  239. package/node_modules/@io/shared/dist/types/squads.d.ts.map +1 -0
  240. package/node_modules/@io/shared/dist/types/squads.js +2 -0
  241. package/node_modules/@io/shared/dist/types/squads.js.map +1 -0
  242. package/node_modules/@io/shared/dist/types/tokens.d.ts +19 -0
  243. package/node_modules/@io/shared/dist/types/tokens.d.ts.map +1 -0
  244. package/node_modules/@io/shared/dist/types/tokens.js +2 -0
  245. package/node_modules/@io/shared/dist/types/tokens.js.map +1 -0
  246. package/node_modules/@io/shared/package.json +18 -0
  247. package/node_modules/@io/shared/src/config.ts +74 -0
  248. package/node_modules/@io/shared/src/constants.ts +36 -0
  249. package/node_modules/@io/shared/src/index.ts +37 -0
  250. package/node_modules/@io/shared/src/types/agents.ts +3 -0
  251. package/node_modules/@io/shared/src/types/api.ts +35 -0
  252. package/node_modules/@io/shared/src/types/attachments.ts +9 -0
  253. package/node_modules/@io/shared/src/types/events.ts +81 -0
  254. package/node_modules/@io/shared/src/types/messages.ts +15 -0
  255. package/node_modules/@io/shared/src/types/squads.ts +53 -0
  256. package/node_modules/@io/shared/src/types/tokens.ts +19 -0
  257. package/node_modules/@io/shared/tsconfig.json +9 -0
  258. package/node_modules/@io/shared/tsconfig.tsbuildinfo +1 -0
  259. package/package.json +56 -59
  260. package/src/api/middleware/auth.ts +76 -0
  261. package/src/api/notifications.ts +122 -0
  262. package/src/api/routes/activity.ts +29 -0
  263. package/src/api/routes/attachments.ts +93 -0
  264. package/src/api/routes/config.ts +115 -0
  265. package/src/api/routes/conversations.ts +87 -0
  266. package/src/api/routes/health.ts +18 -0
  267. package/src/api/routes/inbox.ts +98 -0
  268. package/src/api/routes/schedules.ts +121 -0
  269. package/src/api/routes/skills.ts +105 -0
  270. package/src/api/routes/squads.ts +145 -0
  271. package/src/api/routes/usage.ts +57 -0
  272. package/src/api/routes/wiki.ts +49 -0
  273. package/src/api/server.ts +186 -0
  274. package/src/config.ts +3 -0
  275. package/src/copilot/client.ts +42 -0
  276. package/src/copilot/health-monitor.ts +85 -0
  277. package/src/copilot/orchestrator.ts +222 -0
  278. package/src/copilot/tools.ts +707 -0
  279. package/src/index.ts +112 -0
  280. package/src/logging/logger.ts +26 -0
  281. package/src/models/index.ts +11 -0
  282. package/src/models/pricing.ts +121 -0
  283. package/src/models/registry.ts +131 -0
  284. package/src/models/token-tracker.ts +151 -0
  285. package/src/scheduler/engine.ts +146 -0
  286. package/src/skills/index.ts +13 -0
  287. package/src/skills/store.ts +188 -0
  288. package/src/squad/agent.ts +326 -0
  289. package/src/squad/autonomy.ts +78 -0
  290. package/src/squad/event-bus.ts +71 -0
  291. package/src/squad/execution/index.ts +17 -0
  292. package/src/squad/execution/instance.ts +186 -0
  293. package/src/squad/execution/meeting.ts +191 -0
  294. package/src/squad/execution/pr.ts +127 -0
  295. package/src/squad/execution/runner.ts +97 -0
  296. package/src/squad/execution/tasks.ts +111 -0
  297. package/src/squad/execution/worktree.ts +138 -0
  298. package/src/squad/hiring.ts +222 -0
  299. package/src/squad/index.ts +17 -0
  300. package/src/squad/manager.ts +337 -0
  301. package/src/squad/name-generator.ts +135 -0
  302. package/src/squad/roles/templates.ts +104 -0
  303. package/src/squad/skill-parser.ts +120 -0
  304. package/src/squad/source-resolver.ts +57 -0
  305. package/src/store/activity.ts +176 -0
  306. package/src/store/db.ts +237 -0
  307. package/src/store/inbox.ts +199 -0
  308. package/src/store/schedules.ts +199 -0
  309. package/src/wiki/index.ts +12 -0
  310. package/src/wiki/store.ts +139 -0
  311. package/tsconfig.json +9 -0
  312. package/LICENSE +0 -21
  313. package/dist/api/auth.js +0 -46
  314. package/dist/chat/attachments.js +0 -112
  315. package/dist/copilot/agents.js +0 -309
  316. package/dist/copilot/ceremonies.js +0 -174
  317. package/dist/copilot/gh-token.js +0 -64
  318. package/dist/copilot/io-scheduler.js +0 -79
  319. package/dist/copilot/model-router.js +0 -114
  320. package/dist/copilot/scheduler.js +0 -88
  321. package/dist/copilot/skills.js +0 -252
  322. package/dist/copilot/specialist-runner.js +0 -191
  323. package/dist/copilot/squad-tools.js +0 -258
  324. package/dist/copilot/system-message.js +0 -86
  325. package/dist/copilot/token-tracker.js +0 -98
  326. package/dist/copilot/trigger-schedule.js +0 -33
  327. package/dist/daemon.js +0 -67
  328. package/dist/logging.js +0 -27
  329. package/dist/mcp/config.js +0 -29
  330. package/dist/mcp/index.js +0 -3
  331. package/dist/mcp/registry.js +0 -42
  332. package/dist/notify.js +0 -25
  333. package/dist/paths.js +0 -17
  334. package/dist/setup.js +0 -35
  335. package/dist/store/agent-events.js +0 -19
  336. package/dist/store/audit-log.js +0 -71
  337. package/dist/store/conversations.js +0 -164
  338. package/dist/store/feed.js +0 -44
  339. package/dist/store/instances.js +0 -75
  340. package/dist/store/squad-colors.js +0 -23
  341. package/dist/store/squads.js +0 -60
  342. package/dist/store/tasks.js +0 -78
  343. package/dist/store/token-usage.js +0 -94
  344. package/dist/telegram/bot.js +0 -41
  345. package/dist/telegram/handlers.js +0 -42
  346. package/dist/watchdog.js +0 -37
  347. package/dist/wiki/backlinks.js +0 -51
  348. package/dist/wiki/fs.js +0 -108
  349. package/dist/wiki/search.js +0 -47
  350. package/web-dist/assets/AuditLogView-BzfjNXBT.js +0 -6
  351. package/web-dist/assets/ChatView-BdMukPKG.js +0 -1
  352. package/web-dist/assets/FeedView-BfPIabGr.js +0 -6
  353. package/web-dist/assets/HistoryView-BmEEk3Rs.js +0 -1
  354. package/web-dist/assets/LoginView-D7LrkeX7.js +0 -1
  355. package/web-dist/assets/McpView-BAP_ah3T.js +0 -1
  356. package/web-dist/assets/SchedulesView-CAtsUPCZ.js +0 -6
  357. package/web-dist/assets/SettingsView-BovjWZDa.js +0 -1
  358. package/web-dist/assets/SkillsView-DQSMM5LN.js +0 -16
  359. package/web-dist/assets/SquadDetailView-DBscu0m2.js +0 -26
  360. package/web-dist/assets/SquadHealthView-D686BuQo.js +0 -11
  361. package/web-dist/assets/SquadsView-AzMht2NJ.js +0 -6
  362. package/web-dist/assets/ToggleSwitch.vue_vue_type_script_setup_true_lang-DtShZAjW.js +0 -1
  363. package/web-dist/assets/UsageView-DiVn97aI.js +0 -16
  364. package/web-dist/assets/WikiView-Cn7KipkZ.js +0 -26
  365. package/web-dist/assets/api-D4mHJ3u0.js +0 -1
  366. package/web-dist/assets/arrow-left-D_qUNUWW.js +0 -6
  367. package/web-dist/assets/git-branch-DOM-orcl.js +0 -6
  368. package/web-dist/assets/index-Bo83B1LR.css +0 -1
  369. package/web-dist/assets/index-ELvnkQjd.js +0 -273
  370. package/web-dist/assets/pencil-CFsi7ufI.js +0 -6
  371. package/web-dist/assets/plus-BAzlGFd_.js +0 -6
  372. package/web-dist/assets/save-BmgCYJ1g.js +0 -6
  373. package/web-dist/assets/search-CS9zSIeW.js +0 -6
  374. package/web-dist/assets/squad-colors-B8B_Y-lz.js +0 -1
  375. package/web-dist/assets/trash-2-DLveUEsd.js +0 -6
  376. package/web-dist/assets/triangle-alert-lj4I30rL.js +0 -6
  377. package/web-dist/assets/x-CjXR97Fa.js +0 -6
  378. package/web-dist/favicon.svg +0 -10
  379. package/web-dist/index.html +0 -14
  380. package/web-dist/logo.svg +0 -10
@@ -1,650 +1,152 @@
1
- import express from "express";
2
- import { join } from "node:path";
3
- import { fileURLToPath } from "node:url";
4
- import { dirname, resolve } from "node:path";
5
- import { existsSync } from "node:fs";
6
- import { loadConfig, saveConfig } from "../config.js";
7
- import { createAuthMiddleware } from "./auth.js";
8
- import { sendToOrchestrator } from "../copilot/orchestrator.js";
9
- import { listSquads, getSquad, getAgentsForSquad } from "../store/squads.js";
10
- import { getTasksForSquad, getSquadTaskMetrics } from "../store/tasks.js";
11
- import { getInstancesForSquad, destroyInstance } from "../store/instances.js";
12
- import { getAgentEvents } from "../store/agent-events.js";
13
- import { getAuditLog, countAuditLog } from "../store/audit-log.js";
14
- import { getFeedItems, markFeedItemRead, deleteFeedItem, getUnreadCount, } from "../store/feed.js";
15
- import { listSchedules, createSchedule, updateSchedule, deleteSchedule, getSchedule } from "../store/schedules.js";
16
- import { triggerSchedule } from "../copilot/trigger-schedule.js";
17
- import { listServers, toggleMcpServer, addMcpServer, removeMcpServer } from "../mcp/index.js";
18
- import { listSkills, addSkill, createSkill, removeSkill, getSkillContent, updateSkillContent, discoverSkills, installFromSource, fetchRemoteSkillPreview } from "../copilot/skills.js";
19
- import { readPage, writePage, deletePage, listPages, listTemplates, readTemplate, writeTemplate, deleteTemplate } from "../wiki/fs.js";
20
- import { searchPages } from "../wiki/search.js";
21
- import { getBacklinks } from "../wiki/backlinks.js";
22
- import { saveMessage, getConversation, listConversations, searchConversations, deleteConversation, } from "../store/conversations.js";
23
- import { getTokenUsageSummary, getTokenUsageBySquad, getTokenUsageByAgent, getDailyTokenUsage, } from "../store/token-usage.js";
24
- import { DEFAULT_MODEL_PRICING } from "../copilot/token-tracker.js";
25
- import { randomUUID } from "node:crypto";
26
- import { validateMessageAttachments } from "../chat/attachments.js";
27
- const __filename = fileURLToPath(import.meta.url);
28
- const __dirname = dirname(__filename);
29
- const sseClients = [];
30
- function broadcast(event, data) {
31
- const payload = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
32
- for (const client of sseClients) {
33
- if (!client.res.writableEnded) {
34
- client.res.write(payload);
35
- }
36
- }
37
- }
38
- export async function startApiServer(config) {
1
+ import { existsSync } from 'node:fs';
2
+ import { createServer } from 'node:http';
3
+ import { join, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import express from 'express';
6
+ import { WebSocketServer } from 'ws';
7
+ import { sendMessage } from '../copilot/orchestrator.js';
8
+ import { createChildLogger } from '../logging/logger.js';
9
+ import { authMiddleware, verifyWsToken } from './middleware/auth.js';
10
+ import { initNotifications, subscribeClient, unsubscribeClient } from './notifications.js';
11
+ import { activityRouter } from './routes/activity.js';
12
+ import { attachmentsRouter } from './routes/attachments.js';
13
+ import { configRouter } from './routes/config.js';
14
+ import { conversationsRouter } from './routes/conversations.js';
15
+ import { healthRouter } from './routes/health.js';
16
+ import { inboxRouter } from './routes/inbox.js';
17
+ import { schedulesRouter } from './routes/schedules.js';
18
+ import { skillsRouter } from './routes/skills.js';
19
+ import { squadsRouter } from './routes/squads.js';
20
+ import { usageRouter } from './routes/usage.js';
21
+ import { wikiRouter } from './routes/wiki.js';
22
+ // Connected WebSocket clients keyed by connection ID
23
+ const wsClients = new Map();
24
+ export function createApiServer(config) {
25
+ const logger = createChildLogger('api');
39
26
  const app = express();
40
- app.use(express.json({ limit: "30mb" }));
41
- // Serve static web frontend
42
- const webDistPath = resolve(__dirname, "..", "..", "web-dist");
43
- const webIndexPath = join(webDistPath, "index.html");
44
- if (existsSync(webDistPath)) {
45
- app.use(express.static(webDistPath));
46
- }
47
- else {
48
- console.warn(`[io] Warning: web-dist not found at ${webDistPath}. Web UI will not be available.`);
49
- }
50
- // Auth middleware for all API routes
51
- const auth = createAuthMiddleware(config);
52
- // Public endpoint — serves Supabase config to the frontend (no auth required)
53
- app.get("/api/auth/config", (_req, res) => {
54
- res.json({
55
- supabaseUrl: config.supabaseUrl ?? null,
56
- supabaseAnonKey: config.supabaseAnonKey ?? null,
57
- });
58
- });
59
- app.use("/api", auth);
60
- // --- SSE Stream ---
61
- app.get("/api/stream", (req, res) => {
62
- res.setHeader("Content-Type", "text/event-stream");
63
- res.setHeader("Cache-Control", "no-cache");
64
- res.setHeader("Connection", "keep-alive");
65
- res.flushHeaders();
66
- const client = { id: randomUUID(), res };
67
- sseClients.push(client);
68
- res.write(`event: connected\ndata: ${JSON.stringify({ id: client.id })}\n\n`);
69
- req.on("close", () => {
70
- const idx = sseClients.indexOf(client);
71
- if (idx !== -1)
72
- sseClients.splice(idx, 1);
73
- });
74
- });
75
- // --- Chat (SSE-streamed response) ---
76
- app.post("/api/message", async (req, res) => {
77
- const { prompt, conversationId: clientConvId, attachments: rawAttachments } = req.body;
78
- if (!prompt || typeof prompt !== "string") {
79
- res.status(400).json({ error: "prompt is required" });
80
- return;
81
- }
82
- const validation = validateMessageAttachments(rawAttachments);
83
- if (!validation.ok) {
84
- res.status(400).json({ error: validation.error });
85
- return;
86
- }
87
- const attachments = validation.attachments;
88
- const conversationId = (typeof clientConvId === "string" && clientConvId) ? clientConvId : randomUUID();
89
- // Persist the user message
90
- saveMessage(conversationId, "user", prompt, "web", attachments);
91
- // Switch to SSE streaming to avoid Cloudflare 524 timeouts
92
- res.setHeader("Content-Type", "text/event-stream");
93
- res.setHeader("Cache-Control", "no-cache");
94
- res.setHeader("Connection", "keep-alive");
95
- res.setHeader("X-Accel-Buffering", "no");
96
- res.flushHeaders();
97
- // Keepalive: send a comment every 30s to keep Cloudflare happy
98
- const keepalive = setInterval(() => {
99
- if (!finished)
100
- res.write(": keepalive\n\n");
101
- }, 30_000);
102
- let finished = false;
103
- req.on("close", () => { finished = true; clearInterval(keepalive); });
104
- try {
105
- await sendToOrchestrator(prompt, "web", (content, done) => {
106
- if (finished)
107
- return;
108
- if (done) {
109
- finished = true;
110
- saveMessage(conversationId, "assistant", content, "web");
111
- res.write(`event: done\ndata: ${JSON.stringify({ content, conversationId })}\n\n`);
112
- clearInterval(keepalive);
113
- res.end();
114
- }
115
- else {
116
- res.write(`event: delta\ndata: ${JSON.stringify({ content })}\n\n`);
117
- // Also broadcast to other SSE listeners (e.g. ChatOverlay)
118
- broadcast("message_delta", { content, done: false });
119
- }
120
- }, attachments);
121
- }
122
- catch (err) {
123
- if (!finished) {
124
- finished = true;
125
- const message = err instanceof Error ? err.message : "Unknown error";
126
- res.write(`event: error\ndata: ${JSON.stringify({ error: message })}\n\n`);
127
- clearInterval(keepalive);
128
- res.end();
27
+ app.use(express.json());
28
+ // Auth middleware verifies Supabase JWT if configured
29
+ app.use('/api', authMiddleware(config));
30
+ // Routes
31
+ app.use('/api', healthRouter());
32
+ app.use('/api', usageRouter());
33
+ app.use('/api', squadsRouter());
34
+ app.use('/api', activityRouter());
35
+ app.use('/api', attachmentsRouter(config.dataDir));
36
+ app.use('/api', inboxRouter());
37
+ app.use('/api', schedulesRouter());
38
+ app.use('/api', conversationsRouter());
39
+ app.use('/api', configRouter());
40
+ app.use('/api/wiki', wikiRouter);
41
+ app.use('/api', skillsRouter);
42
+ // POST /api/messages — send a message to the orchestrator
43
+ app.post('/api/messages', async (req, res) => {
44
+ const { content, source, connectionId } = req.body;
45
+ if (!content) {
46
+ res.status(400).json({ error: 'content is required' });
47
+ return;
48
+ }
49
+ const ws = connectionId ? wsClients.get(connectionId) : undefined;
50
+ const onDelta = (accumulated, done) => {
51
+ if (ws && ws.readyState === ws.OPEN) {
52
+ ws.send(JSON.stringify({
53
+ type: done ? 'message' : 'delta',
54
+ content: accumulated,
55
+ }));
129
56
  }
130
- }
131
- });
132
- // --- History ---
133
- app.get("/api/history", (req, res) => {
134
- const q = req.query.q;
135
- const from = req.query.from;
136
- const to = req.query.to;
137
- const limit = parseInt(req.query.limit) || 50;
138
- const offset = parseInt(req.query.offset) || 0;
139
- if (q) {
140
- res.json(searchConversations(q, { limit, offset, from, to }));
141
- }
142
- else {
143
- res.json(listConversations({ limit, offset, from, to }));
144
- }
145
- });
146
- app.get("/api/history/:id", (req, res) => {
147
- const messages = getConversation(req.params.id);
148
- if (messages.length === 0) {
149
- res.status(404).json({ error: "Conversation not found" });
150
- return;
151
- }
152
- res.json(messages);
153
- });
154
- app.delete("/api/history/:id", (req, res) => {
155
- deleteConversation(req.params.id);
156
- res.json({ ok: true });
157
- });
158
- // --- Squads ---
159
- app.get("/api/squads", (_req, res) => {
160
- const data = listSquads();
161
- const instanceCounts = {};
162
- for (const squad of data.squads) {
163
- instanceCounts[squad.id] = getInstancesForSquad(squad.id).length;
164
- }
165
- res.json({ ...data, instanceCounts });
166
- });
167
- // --- Squad Health Dashboard ---
168
- app.get("/api/squads/health", (_req, res) => {
169
- const { squads, agents } = listSquads();
170
- const health = squads.map((squad) => {
171
- const squadAgents = agents.filter((a) => a.squad_id === squad.id);
172
- const instances = getInstancesForSquad(squad.id);
173
- const metrics = getSquadTaskMetrics(squad.id);
174
- return {
175
- id: squad.id,
176
- name: squad.name,
177
- universe: squad.universe,
178
- agentCount: squadAgents.length,
179
- activeInstanceCount: instances.length,
180
- activeInstances: instances.map((inst) => ({
181
- id: inst.id,
182
- branch: inst.branch,
183
- lastActivity: inst.last_activity,
184
- })),
185
- tasksTotal: metrics.tasksTotal,
186
- tasksCompleted: metrics.tasksCompleted,
187
- tasksCompletedRecent: metrics.tasksCompletedRecent,
188
- tasksPending: metrics.tasksPending,
189
- tasksInProgress: metrics.tasksInProgress,
190
- tasksFailed: metrics.tasksFailed,
191
- avgCycleTimeMinutes: metrics.avgCycleTimeMinutes,
192
- isStalled: metrics.isStalled,
193
- recentTasks: metrics.recentTasks.map((t) => ({
194
- id: t.id,
195
- description: t.description,
196
- status: t.status,
197
- updatedAt: t.updated_at,
198
- })),
199
- };
200
- });
201
- res.json({ health });
202
- });
203
- app.get("/api/squads/:id", (req, res) => {
204
- const squad = getSquad(req.params.id);
205
- if (!squad) {
206
- res.status(404).json({ error: "Squad not found" });
207
- return;
208
- }
209
- const agents = getAgentsForSquad(req.params.id);
210
- const tasks = getTasksForSquad(req.params.id);
211
- const instances = getInstancesForSquad(req.params.id);
212
- res.json({ squad, agents, tasks, instances });
213
- });
214
- app.delete("/api/instances/:id", async (req, res) => {
215
- try {
216
- await destroyInstance(req.params.id);
217
- res.json({ ok: true });
218
- }
219
- catch (err) {
220
- const msg = err?.message ?? "Unknown error";
221
- const status = msg.toLowerCase().includes("not found") ? 404 : 500;
222
- res.status(status).json({ error: msg });
223
- }
224
- });
225
- // --- Task Events ---
226
- app.get("/api/tasks/:taskId/events", (req, res) => {
227
- const events = getAgentEvents(req.params.taskId);
228
- res.json(events);
229
- });
230
- // --- Stop Task ---
231
- app.post("/api/tasks/:taskId/stop", async (req, res) => {
57
+ };
232
58
  try {
233
- const { stopTask } = await import("../copilot/agents.js");
234
- await stopTask(req.params.taskId);
235
- res.json({ ok: true });
59
+ const response = await sendMessage(content, source ?? 'web', onDelta);
60
+ res.json({ status: 'ok', content: response });
236
61
  }
237
62
  catch (err) {
238
- const msg = err?.message ?? "Unknown error";
239
- const isNotRunning = msg.toLowerCase().includes("not currently running") || msg.toLowerCase().includes("already completed");
240
- res.status(isNotRunning ? 404 : 500).json({ error: msg });
63
+ logger.error({ err }, 'Error processing message');
64
+ res.status(500).json({ error: 'Failed to process message' });
241
65
  }
242
66
  });
243
- // --- Audit Log ---
244
- app.get("/api/audit-log", (req, res) => {
245
- const squad_id = req.query.squad_id;
246
- const agent_id = req.query.agent_id;
247
- const action_type = req.query.action_type;
248
- const from = req.query.from;
249
- const to = req.query.to;
250
- const limit = parseInt(req.query.limit) || 50;
251
- const offset = parseInt(req.query.offset) || 0;
252
- const filters = { squad_id, agent_id, action_type, from, to, limit, offset };
253
- res.json({
254
- entries: getAuditLog(filters),
255
- total: countAuditLog(filters),
256
- });
257
- });
258
- // --- Feed ---
259
- app.get("/api/feed", (req, res) => {
260
- const unreadOnly = req.query.unread === "true";
261
- const source = req.query.source;
262
- const limit = parseInt(req.query.limit) || 50;
263
- const offset = parseInt(req.query.offset) || 0;
264
- res.json({
265
- items: getFeedItems({ unreadOnly, source, limit, offset }),
266
- unreadCount: getUnreadCount(),
67
+ // Serve web frontend static files (production build)
68
+ const __dirname = fileURLToPath(new URL('.', import.meta.url));
69
+ const webDistPath = resolve(__dirname, '../../../web/dist');
70
+ if (existsSync(webDistPath)) {
71
+ app.use(express.static(webDistPath));
72
+ // SPA fallback: serve index.html for any non-API route
73
+ app.get('*', (_req, res) => {
74
+ res.sendFile(join(webDistPath, 'index.html'));
267
75
  });
268
- });
269
- app.post("/api/feed/:id/read", (req, res) => {
270
- markFeedItemRead(req.params.id);
271
- res.json({ ok: true });
272
- });
273
- app.delete("/api/feed/:id", (req, res) => {
274
- deleteFeedItem(req.params.id);
275
- res.json({ ok: true });
276
- });
277
- // --- MCP Servers ---
278
- app.get("/api/mcp", (_req, res) => {
279
- res.json(listServers());
280
- });
281
- app.post("/api/mcp", (req, res) => {
282
- const server = { id: randomUUID(), ...req.body, enabled: true };
283
- addMcpServer(server);
284
- res.json(server);
285
- });
286
- app.put("/api/mcp/:id", (req, res) => {
287
- const { enabled } = req.body;
288
- if (typeof enabled === "boolean") {
289
- toggleMcpServer(req.params.id, enabled);
290
- }
291
- res.json({ ok: true });
292
- });
293
- app.delete("/api/mcp/:id", (req, res) => {
294
- removeMcpServer(req.params.id);
295
- res.json({ ok: true });
296
- });
297
- // --- Skills ---
298
- app.get("/api/skills", async (_req, res) => {
299
- const skills = await listSkills();
300
- res.json(skills);
301
- });
302
- app.get("/api/skills/discover", async (req, res) => {
303
- const source = req.query.source;
304
- if (source !== "awesome-copilot" && source !== "skillssh") {
305
- res.status(400).json({ error: "source must be 'awesome-copilot' or 'skillssh'" });
306
- return;
307
- }
308
- const q = req.query.q;
309
- try {
310
- const skills = await discoverSkills(source, q);
311
- res.json(skills);
312
- }
313
- catch (err) {
314
- res.status(502).json({ error: err.message });
315
- }
316
- });
317
- app.get("/api/skills/preview", async (req, res) => {
318
- const source = req.query.source;
319
- const slug = req.query.slug;
320
- if (source !== "awesome-copilot" && source !== "skillssh") {
321
- res.status(400).json({ error: "source must be 'awesome-copilot' or 'skillssh'" });
322
- return;
323
- }
324
- if (!slug) {
325
- res.status(400).json({ error: "slug is required" });
326
- return;
327
- }
328
- try {
329
- const sourceRepo = req.query.sourceRepo;
330
- const content = await fetchRemoteSkillPreview(source, slug, sourceRepo);
331
- res.json({ content });
332
- }
333
- catch (err) {
334
- res.status(502).json({ error: err.message });
335
- }
336
- });
337
- app.post("/api/skills", async (req, res) => {
338
- try {
339
- const { url, source, slug, content, sourceRepo } = req.body;
340
- if (source && slug) {
341
- if (source !== "awesome-copilot" && source !== "skillssh") {
342
- res.status(400).json({ error: "source must be 'awesome-copilot' or 'skillssh'" });
343
- return;
76
+ logger.info({ path: webDistPath }, 'Serving web frontend');
77
+ }
78
+ const server = createServer(app);
79
+ // WebSocket server for streaming
80
+ const wss = new WebSocketServer({ server, path: '/ws' });
81
+ wss.on('connection', (ws, req) => {
82
+ // Verify token from query string if auth is configured
83
+ const url = new URL(req.url ?? '', `http://${req.headers.host}`);
84
+ const token = url.searchParams.get('token');
85
+ if (!verifyWsToken(config, token)) {
86
+ ws.close(4001, 'Unauthorized');
87
+ return;
88
+ }
89
+ const connectionId = crypto.randomUUID();
90
+ wsClients.set(connectionId, ws);
91
+ subscribeClient(connectionId, ws);
92
+ logger.info({ connectionId }, 'WebSocket client connected');
93
+ // Send the connection ID to the client
94
+ ws.send(JSON.stringify({ type: 'connected', connectionId }));
95
+ ws.on('message', (data) => {
96
+ try {
97
+ const parsed = JSON.parse(data.toString());
98
+ if (parsed.type === 'message' && parsed.content) {
99
+ const source = parsed.source ?? 'tui';
100
+ const onDelta = (accumulated, done) => {
101
+ if (ws.readyState === ws.OPEN) {
102
+ ws.send(JSON.stringify({
103
+ type: done ? 'message' : 'delta',
104
+ content: accumulated,
105
+ }));
106
+ }
107
+ };
108
+ sendMessage(parsed.content, source, onDelta).catch((err) => {
109
+ logger.error({ err }, 'Error processing WebSocket message');
110
+ if (ws.readyState === ws.OPEN) {
111
+ ws.send(JSON.stringify({ type: 'error', content: 'Failed to process message' }));
112
+ }
113
+ });
344
114
  }
345
- await installFromSource(source, slug, sourceRepo);
346
- }
347
- else if (url && typeof url === "string") {
348
- // Git-clone method
349
- await addSkill(url);
350
- }
351
- else if (slug && typeof slug === "string" && content && typeof content === "string") {
352
- // Direct-creation method
353
- await createSkill(slug, content);
354
115
  }
355
- else {
356
- res.status(400).json({ error: "Provide 'url' (git clone), 'source' + 'slug' (community install), or 'slug' + 'content' (direct create)" });
357
- return;
116
+ catch (err) {
117
+ logger.error({ err }, 'Failed to parse WebSocket message');
358
118
  }
359
- res.status(201).json({ ok: true });
360
- }
361
- catch (err) {
362
- res.status(400).json({ error: err.message });
363
- }
364
- });
365
- app.delete("/api/skills/:slug", async (req, res) => {
366
- try {
367
- await removeSkill(req.params.slug);
368
- res.json({ ok: true });
369
- }
370
- catch (err) {
371
- res.status(404).json({ error: err.message });
372
- }
373
- });
374
- app.get("/api/skills/:slug/content", async (req, res) => {
375
- try {
376
- const content = await getSkillContent(req.params.slug);
377
- res.json({ content });
378
- }
379
- catch (err) {
380
- res.status(404).json({ error: err.message });
381
- }
382
- });
383
- app.put("/api/skills/:slug/content", async (req, res) => {
384
- try {
385
- const { content } = req.body;
386
- await updateSkillContent(req.params.slug, content);
387
- res.json({ ok: true });
388
- }
389
- catch (err) {
390
- res.status(404).json({ error: err.message });
391
- }
392
- });
393
- // --- Wiki ---
394
- app.get("/api/wiki/pages", async (_req, res) => {
395
- const pages = await listPages();
396
- res.json(pages);
397
- });
398
- app.get("/api/wiki/page/*path", async (req, res) => {
399
- try {
400
- const raw = req.params.path;
401
- const pagePath = Array.isArray(raw) ? raw.join("/") : raw;
402
- const content = await readPage(pagePath);
403
- res.json({ path: pagePath, content });
404
- }
405
- catch (err) {
406
- res.status(404).json({ error: err.message });
407
- }
408
- });
409
- app.put("/api/wiki/page/*path", async (req, res) => {
410
- const raw = req.params.path;
411
- const pagePath = Array.isArray(raw) ? raw.join("/") : raw;
412
- const { content } = req.body;
413
- await writePage(pagePath, content);
414
- res.json({ ok: true });
415
- });
416
- app.delete("/api/wiki/page/*path", async (req, res) => {
417
- try {
418
- const raw = req.params.path;
419
- const pagePath = Array.isArray(raw) ? raw.join("/") : raw;
420
- await deletePage(pagePath);
421
- res.json({ ok: true });
422
- }
423
- catch (err) {
424
- res.status(404).json({ error: err.message });
425
- }
426
- });
427
- app.get("/api/wiki/search", async (req, res) => {
428
- const query = req.query.q;
429
- if (!query) {
430
- res.status(400).json({ error: "q is required" });
431
- return;
432
- }
433
- const results = await searchPages(query);
434
- res.json(results);
435
- });
436
- app.get("/api/wiki/backlinks/*path", async (req, res) => {
437
- const raw = req.params.path;
438
- const pagePath = Array.isArray(raw) ? raw.join("/") : raw;
439
- const backlinks = await getBacklinks(pagePath);
440
- res.json(backlinks);
441
- });
442
- // --- Wiki Templates ---
443
- app.get("/api/wiki/templates/squad", async (_req, res) => {
444
- const files = await listTemplates();
445
- res.json(files);
446
- });
447
- app.get("/api/wiki/template/squad/*path", async (req, res) => {
448
- try {
449
- const raw = req.params.path;
450
- const templatePath = Array.isArray(raw) ? raw.join("/") : raw;
451
- const content = await readTemplate(templatePath);
452
- res.json({ path: templatePath, content });
453
- }
454
- catch (err) {
455
- res.status(404).json({ error: err.message });
456
- }
457
- });
458
- app.put("/api/wiki/template/squad/*path", async (req, res) => {
459
- const raw = req.params.path;
460
- const templatePath = Array.isArray(raw) ? raw.join("/") : raw;
461
- const { content } = req.body;
462
- await writeTemplate(templatePath, content);
463
- res.json({ ok: true });
464
- });
465
- app.delete("/api/wiki/template/squad/*path", async (req, res) => {
466
- try {
467
- const raw = req.params.path;
468
- const templatePath = Array.isArray(raw) ? raw.join("/") : raw;
469
- await deleteTemplate(templatePath);
470
- res.json({ ok: true });
471
- }
472
- catch (err) {
473
- res.status(404).json({ error: err.message });
474
- }
475
- });
476
- // --- Schedules ---
477
- app.get("/api/schedules", (_req, res) => {
478
- const type = undefined; // return all
479
- res.json(listSchedules(type));
480
- });
481
- app.post("/api/schedules", (req, res) => {
482
- const { type, cron, squad_id, agenda, prompt } = req.body ?? {};
483
- if (type !== "squad" && type !== "io") {
484
- res.status(400).json({ error: "type must be 'squad' or 'io'" });
485
- return;
486
- }
487
- if (!cron || typeof cron !== "string") {
488
- res.status(400).json({ error: "cron is required" });
489
- return;
490
- }
491
- if (!squad_id || typeof squad_id !== "string" || !squad_id.trim()) {
492
- res.status(400).json({ error: "squad_id is required" });
493
- return;
494
- }
495
- const schedule = createSchedule({ type, cron, squad_id, agenda, prompt });
496
- res.json(schedule);
497
- });
498
- app.put("/api/schedules/:id", (req, res) => {
499
- const schedule = getSchedule(req.params.id);
500
- if (!schedule) {
501
- res.status(404).json({ error: "Schedule not found" });
502
- return;
503
- }
504
- const { enabled, cron, agenda, prompt } = req.body ?? {};
505
- const updated = updateSchedule(req.params.id, {
506
- cron: typeof cron === "string" ? cron : undefined,
507
- agenda: typeof agenda === "string" ? agenda : undefined,
508
- prompt: typeof prompt === "string" ? prompt : undefined,
509
- enabled: typeof enabled === "boolean" ? enabled : undefined,
510
- });
511
- res.json(updated);
512
- });
513
- app.post("/api/schedules/:id/trigger", (req, res) => {
514
- const schedule = triggerSchedule(req.params.id);
515
- if (!schedule) {
516
- res.status(404).json({ error: "Schedule not found" });
517
- return;
518
- }
519
- res.json({ ok: true, schedule });
520
- });
521
- app.delete("/api/schedules/:id", (req, res) => {
522
- deleteSchedule(req.params.id);
523
- res.json({ ok: true });
524
- });
525
- // --- Settings ---
526
- app.get("/api/settings", (_req, res) => {
527
- const current = loadConfig();
528
- // Don't expose full Supabase key — mask it
529
- res.json({
530
- defaultModel: current.defaultModel,
531
- port: current.port,
532
- telegramEnabled: current.telegramEnabled,
533
- telegramBotToken: current.telegramBotToken ? "••••••••" : "",
534
- authorizedUserId: current.authorizedUserId ?? null,
535
- supabaseUrl: current.supabaseUrl ?? "",
536
- supabaseAnonKey: current.supabaseAnonKey ? "••••••••" : "",
537
- authorizedEmail: current.authorizedEmail ?? "",
538
- backgroundNotifyMode: current.backgroundNotifyMode,
539
- backgroundNotifyTelegram: current.backgroundNotifyTelegram,
540
- selfEditEnabled: current.selfEditEnabled,
541
- watchdogEnabled: current.watchdogEnabled,
542
- });
543
- });
544
- app.put("/api/settings", (req, res) => {
545
- const updates = {};
546
- const body = req.body;
547
- // Only apply fields that are explicitly provided and not masked
548
- if (body.defaultModel !== undefined)
549
- updates.defaultModel = body.defaultModel;
550
- if (body.port !== undefined)
551
- updates.port = body.port;
552
- if (body.telegramEnabled !== undefined)
553
- updates.telegramEnabled = body.telegramEnabled;
554
- if (body.telegramBotToken && body.telegramBotToken !== "••••••••")
555
- updates.telegramBotToken = body.telegramBotToken;
556
- if (body.authorizedUserId !== undefined)
557
- updates.authorizedUserId = body.authorizedUserId;
558
- if (body.supabaseUrl !== undefined)
559
- updates.supabaseUrl = body.supabaseUrl;
560
- if (body.supabaseAnonKey && body.supabaseAnonKey !== "••••••••")
561
- updates.supabaseAnonKey = body.supabaseAnonKey;
562
- if (body.authorizedEmail !== undefined)
563
- updates.authorizedEmail = body.authorizedEmail;
564
- if (body.backgroundNotifyMode !== undefined)
565
- updates.backgroundNotifyMode = body.backgroundNotifyMode;
566
- if (body.backgroundNotifyTelegram !== undefined)
567
- updates.backgroundNotifyTelegram = body.backgroundNotifyTelegram;
568
- if (body.selfEditEnabled !== undefined)
569
- updates.selfEditEnabled = body.selfEditEnabled;
570
- if (body.watchdogEnabled !== undefined)
571
- updates.watchdogEnabled = body.watchdogEnabled;
572
- saveConfig(updates);
573
- res.json({ ok: true });
574
- });
575
- // --- Token Usage ---
576
- app.get("/api/token-usage/summary", (req, res) => {
577
- const since = req.query.since;
578
- res.json(getTokenUsageSummary({ since }));
579
- });
580
- app.get("/api/token-usage/by-squad", (req, res) => {
581
- const since = req.query.since;
582
- res.json(getTokenUsageBySquad({ since }));
583
- });
584
- app.get("/api/token-usage/by-agent", (req, res) => {
585
- const since = req.query.since;
586
- const squadId = req.query.squad_id;
587
- res.json(getTokenUsageByAgent({ since, squadId }));
588
- });
589
- app.get("/api/token-usage/daily", (req, res) => {
590
- const days = parseInt(req.query.days) || 30;
591
- res.json(getDailyTokenUsage(days));
592
- });
593
- app.get("/api/token-usage/pricing", (_req, res) => {
594
- const config = loadConfig();
595
- const merged = { ...DEFAULT_MODEL_PRICING, ...(config.modelPricing ?? {}) };
596
- res.json(merged);
597
- });
598
- app.put("/api/token-usage/pricing", (req, res) => {
599
- const pricing = req.body;
600
- if (typeof pricing !== "object" || pricing === null) {
601
- res.status(400).json({ error: "Expected object body" });
602
- return;
603
- }
604
- saveConfig({ modelPricing: pricing });
605
- res.json({ ok: true });
606
- });
607
- app.get("/api/token-usage/alert-threshold", (_req, res) => {
608
- const config = loadConfig();
609
- res.json({ tokenAlertThreshold: config.tokenAlertThreshold ?? null });
610
- });
611
- app.put("/api/token-usage/alert-threshold", (req, res) => {
612
- const { tokenAlertThreshold } = req.body;
613
- if (tokenAlertThreshold !== null && typeof tokenAlertThreshold !== "number") {
614
- res.status(400).json({ error: "tokenAlertThreshold must be a number or null" });
615
- return;
616
- }
617
- saveConfig({ tokenAlertThreshold: tokenAlertThreshold ?? undefined });
618
- res.json({ ok: true });
619
- });
620
- // --- Health (unauthenticated) ---
621
- app.get("/health", (_req, res) => {
622
- res.json({
623
- status: "ok",
624
- version: process.env.npm_package_version ?? "unknown",
625
- webUi: existsSync(webIndexPath),
626
- webDistPath,
627
119
  });
628
- });
629
- // SPA fallback — serve index.html for non-API routes
630
- app.get("*splat", (req, res) => {
631
- if (req.path.startsWith("/api/")) {
632
- res.status(404).json({ error: "Not found" });
633
- return;
634
- }
635
- if (existsSync(webIndexPath)) {
636
- res.sendFile(webIndexPath);
637
- }
638
- else {
639
- res.status(503).send("Web UI not available — web-dist not found.");
640
- }
641
- });
642
- return new Promise((resolvePromise, reject) => {
643
- const server = app.listen(config.port, () => {
644
- resolvePromise();
120
+ ws.on('close', () => {
121
+ wsClients.delete(connectionId);
122
+ unsubscribeClient(connectionId);
123
+ logger.info({ connectionId }, 'WebSocket client disconnected');
645
124
  });
646
- server.on("error", reject);
647
125
  });
126
+ return {
127
+ async start() {
128
+ return new Promise((resolve) => {
129
+ server.listen(config.apiPort, () => {
130
+ logger.info({ port: config.apiPort }, 'API server listening');
131
+ resolve();
132
+ });
133
+ });
134
+ },
135
+ async stop() {
136
+ return new Promise((resolve, reject) => {
137
+ for (const ws of wsClients.values()) {
138
+ ws.close();
139
+ }
140
+ wsClients.clear();
141
+ wss.close();
142
+ server.close((err) => {
143
+ if (err)
144
+ reject(err);
145
+ else
146
+ resolve();
147
+ });
148
+ });
149
+ },
150
+ };
648
151
  }
649
- export { broadcast };
650
152
  //# sourceMappingURL=server.js.map