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
@@ -0,0 +1,337 @@
1
+ import { AUTONOMY_TIERS, type AutonomyConfig, type Squad, type SquadMember } from '@io/shared';
2
+ import type { AutonomyTier } from '@io/shared';
3
+ import { createChildLogger } from '../logging/logger.js';
4
+ import { getDatabase } from '../store/db.js';
5
+ import { Agent, type AgentConfig } from './agent.js';
6
+ import { getEventBus } from './event-bus.js';
7
+ import { type SkillDefinition, parseSkillContent, parseSkillFile } from './skill-parser.js';
8
+
9
+ const logger = () => createChildLogger('squad-manager');
10
+
11
+ // Active squads keyed by squad ID
12
+ const activeSquads = new Map<string, SquadRuntime>();
13
+
14
+ export interface SquadRuntime {
15
+ squad: Squad;
16
+ members: Map<string, Agent>; // keyed by role
17
+ skills: Map<string, SkillDefinition>; // keyed by role
18
+ }
19
+
20
+ /** Create a new squad in the database and return it */
21
+ export async function createSquad(params: {
22
+ name: string;
23
+ projectPath: string;
24
+ repoUrl?: string;
25
+ universe?: string;
26
+ autonomyTier?: AutonomyTier;
27
+ }): Promise<Squad> {
28
+ const db = getDatabase();
29
+ const id = crypto.randomUUID();
30
+ const tier = params.autonomyTier ?? 'medium';
31
+ const autonomyConfig = AUTONOMY_TIERS[tier];
32
+
33
+ await db.execute({
34
+ sql: `INSERT INTO squads (id, name, project_path, repo_url, universe, autonomy_tier, autonomy_config, status)
35
+ VALUES (?, ?, ?, ?, ?, ?, ?, 'active')`,
36
+ args: [
37
+ id,
38
+ params.name,
39
+ params.projectPath,
40
+ params.repoUrl ?? null,
41
+ params.universe ?? null,
42
+ tier,
43
+ JSON.stringify(autonomyConfig),
44
+ ],
45
+ });
46
+
47
+ const squad: Squad = {
48
+ id,
49
+ name: params.name,
50
+ projectPath: params.projectPath,
51
+ repoUrl: params.repoUrl,
52
+ universe: params.universe,
53
+ autonomyTier: tier,
54
+ autonomyConfig,
55
+ status: 'active',
56
+ createdAt: new Date(),
57
+ };
58
+
59
+ await getEventBus().emit({
60
+ id: crypto.randomUUID(),
61
+ timestamp: new Date(),
62
+ type: 'squad:created',
63
+ squadId: id,
64
+ squadName: params.name,
65
+ data: { projectPath: params.projectPath, tier },
66
+ });
67
+
68
+ logger().info({ squadId: id, name: params.name }, 'Squad created');
69
+ return squad;
70
+ }
71
+
72
+ /** Add a member to a squad */
73
+ export async function addMember(params: {
74
+ squadId: string;
75
+ skill: SkillDefinition;
76
+ displayName: string;
77
+ persona?: string;
78
+ isVetoMember?: boolean;
79
+ }): Promise<SquadMember> {
80
+ const db = getDatabase();
81
+ const id = crypto.randomUUID();
82
+
83
+ await db.execute({
84
+ sql: `INSERT INTO squad_members (id, squad_id, display_name, role_name, persona, skill_file_path, tools_allowed, is_veto_member, status)
85
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'active')`,
86
+ args: [
87
+ id,
88
+ params.squadId,
89
+ params.displayName,
90
+ params.skill.role,
91
+ params.persona ?? null,
92
+ params.skill.filePath,
93
+ JSON.stringify(params.skill.tools),
94
+ params.isVetoMember ? 1 : 0,
95
+ ],
96
+ });
97
+
98
+ const member: SquadMember = {
99
+ id,
100
+ squadId: params.squadId,
101
+ displayName: params.displayName,
102
+ roleName: params.skill.role,
103
+ persona: params.persona,
104
+ skillFilePath: params.skill.filePath,
105
+ toolsAllowed: params.skill.tools,
106
+ isVetoMember: params.isVetoMember ?? false,
107
+ status: 'active',
108
+ createdAt: new Date(),
109
+ };
110
+
111
+ return member;
112
+ }
113
+
114
+ /** List all squads */
115
+ export async function listSquads(): Promise<Squad[]> {
116
+ const db = getDatabase();
117
+ const result = await db.execute("SELECT * FROM squads WHERE status = 'active'");
118
+
119
+ return result.rows.map((row) => ({
120
+ id: row.id as string,
121
+ name: row.name as string,
122
+ projectPath: row.project_path as string,
123
+ repoUrl: (row.repo_url as string) || undefined,
124
+ universe: (row.universe as string) || undefined,
125
+ autonomyTier: row.autonomy_tier as AutonomyTier,
126
+ autonomyConfig: JSON.parse((row.autonomy_config as string) || '{}') as AutonomyConfig,
127
+ status: row.status as Squad['status'],
128
+ createdAt: new Date(row.created_at as string),
129
+ }));
130
+ }
131
+
132
+ /** Get a squad by name */
133
+ export async function getSquadByName(name: string): Promise<Squad | null> {
134
+ const db = getDatabase();
135
+ const result = await db.execute({
136
+ sql: "SELECT * FROM squads WHERE name = ? AND status = 'active'",
137
+ args: [name],
138
+ });
139
+
140
+ if (result.rows.length === 0) return null;
141
+ const row = result.rows[0];
142
+
143
+ return {
144
+ id: row.id as string,
145
+ name: row.name as string,
146
+ projectPath: row.project_path as string,
147
+ repoUrl: (row.repo_url as string) || undefined,
148
+ universe: (row.universe as string) || undefined,
149
+ autonomyTier: row.autonomy_tier as AutonomyTier,
150
+ autonomyConfig: JSON.parse((row.autonomy_config as string) || '{}') as AutonomyConfig,
151
+ status: row.status as Squad['status'],
152
+ createdAt: new Date(row.created_at as string),
153
+ };
154
+ }
155
+
156
+ /** Get squad members */
157
+ export async function getSquadMembers(squadId: string): Promise<SquadMember[]> {
158
+ const db = getDatabase();
159
+ const result = await db.execute({
160
+ sql: "SELECT * FROM squad_members WHERE squad_id = ? AND status = 'active'",
161
+ args: [squadId],
162
+ });
163
+
164
+ return result.rows.map((row) => ({
165
+ id: row.id as string,
166
+ squadId: row.squad_id as string,
167
+ displayName: (row.display_name as string) || (row.role_name as string),
168
+ roleName: row.role_name as string,
169
+ persona: (row.persona as string) || undefined,
170
+ skillFilePath: (row.skill_file_path as string) || undefined,
171
+ toolsAllowed: JSON.parse((row.tools_allowed as string) || '[]') as string[],
172
+ isVetoMember: Boolean(row.is_veto_member),
173
+ status: row.status as SquadMember['status'],
174
+ createdAt: new Date(row.created_at as string),
175
+ }));
176
+ }
177
+
178
+ /** Disband a squad */
179
+ export async function disbandSquad(squadId: string): Promise<void> {
180
+ const db = getDatabase();
181
+ await db.execute({
182
+ sql: "UPDATE squads SET status = 'disbanded' WHERE id = ?",
183
+ args: [squadId],
184
+ });
185
+ await db.execute({
186
+ sql: "UPDATE squad_members SET status = 'retired' WHERE squad_id = ?",
187
+ args: [squadId],
188
+ });
189
+
190
+ // Destroy any running agents
191
+ const runtime = activeSquads.get(squadId);
192
+ if (runtime) {
193
+ for (const agent of runtime.members.values()) {
194
+ await agent.destroy().catch(() => {});
195
+ }
196
+ activeSquads.delete(squadId);
197
+ }
198
+
199
+ await getEventBus().emit({
200
+ id: crypto.randomUUID(),
201
+ timestamp: new Date(),
202
+ type: 'squad:disbanded',
203
+ squadId,
204
+ squadName: '',
205
+ });
206
+
207
+ logger().info({ squadId }, 'Squad disbanded');
208
+ }
209
+
210
+ /** Retheme a squad with a new universe — updates display names and personas */
211
+ export async function rethemeSquad(
212
+ squadId: string,
213
+ newUniverse: string,
214
+ assignments: { role: string; displayName: string; persona: string }[],
215
+ ): Promise<void> {
216
+ const db = getDatabase();
217
+
218
+ // Update squad universe
219
+ await db.execute({
220
+ sql: 'UPDATE squads SET universe = ? WHERE id = ?',
221
+ args: [newUniverse, squadId],
222
+ });
223
+
224
+ // Update each member
225
+ for (const assignment of assignments) {
226
+ await db.execute({
227
+ sql: 'UPDATE squad_members SET display_name = ?, persona = ? WHERE squad_id = ? AND role_name = ?',
228
+ args: [assignment.displayName, assignment.persona, squadId, assignment.role],
229
+ });
230
+ }
231
+
232
+ // If squad is running, destroy and reboot agents
233
+ const runtime = activeSquads.get(squadId);
234
+ if (runtime) {
235
+ for (const agent of runtime.members.values()) {
236
+ await agent.destroy().catch(() => {});
237
+ }
238
+ activeSquads.delete(squadId);
239
+
240
+ // Reload squad data and reboot
241
+ const squad = await getSquadById(squadId);
242
+ if (squad) {
243
+ await bootSquad(squad);
244
+ }
245
+ }
246
+
247
+ logger().info({ squadId, universe: newUniverse }, 'Squad rethemed');
248
+ }
249
+
250
+ /** Get squad by ID */
251
+ export async function getSquadById(squadId: string): Promise<Squad | null> {
252
+ const db = getDatabase();
253
+ const result = await db.execute({
254
+ sql: 'SELECT * FROM squads WHERE id = ?',
255
+ args: [squadId],
256
+ });
257
+
258
+ if (result.rows.length === 0) return null;
259
+ const row = result.rows[0];
260
+
261
+ return {
262
+ id: row.id as string,
263
+ name: row.name as string,
264
+ projectPath: row.project_path as string,
265
+ repoUrl: (row.repo_url as string) || undefined,
266
+ universe: (row.universe as string) || undefined,
267
+ autonomyTier: row.autonomy_tier as AutonomyTier,
268
+ autonomyConfig: JSON.parse((row.autonomy_config as string) || '{}') as AutonomyConfig,
269
+ status: row.status as Squad['status'],
270
+ createdAt: new Date(row.created_at as string),
271
+ };
272
+ }
273
+
274
+ /** Boot a squad's agents (creates sessions for each member) */
275
+ export async function bootSquad(squad: Squad): Promise<SquadRuntime> {
276
+ const members = await getSquadMembers(squad.id);
277
+ const runtime: SquadRuntime = {
278
+ squad,
279
+ members: new Map(),
280
+ skills: new Map(),
281
+ };
282
+
283
+ const squadContext = `Project: ${squad.name}\nPath: ${squad.projectPath}${squad.repoUrl ? `\nRepo: ${squad.repoUrl}` : ''}`;
284
+
285
+ for (const member of members) {
286
+ let skill: SkillDefinition;
287
+ if (member.skillFilePath) {
288
+ skill = parseSkillFile(member.skillFilePath);
289
+ } else {
290
+ // Fallback: generate a minimal skill
291
+ skill = parseSkillContent(
292
+ `---\nrole: ${member.roleName}\ntools: []\nveto: false\n---\nYou are the ${member.roleName}.`,
293
+ );
294
+ }
295
+
296
+ runtime.skills.set(member.roleName, skill);
297
+
298
+ const agent = new Agent({
299
+ skill,
300
+ squadId: squad.id,
301
+ squadName: squad.name,
302
+ model: 'claude-opus-4.6',
303
+ identity: {
304
+ displayName: member.displayName,
305
+ persona: member.persona,
306
+ universe: squad.universe,
307
+ },
308
+ });
309
+
310
+ await agent.init(squadContext);
311
+ runtime.members.set(member.roleName, agent);
312
+ }
313
+
314
+ activeSquads.set(squad.id, runtime);
315
+ logger().info({ squadId: squad.id, memberCount: members.length }, 'Squad booted');
316
+ return runtime;
317
+ }
318
+
319
+ /** Get a running squad's runtime */
320
+ export function getSquadRuntime(squadId: string): SquadRuntime | undefined {
321
+ return activeSquads.get(squadId);
322
+ }
323
+
324
+ /** Delegate a message to a squad's team lead */
325
+ export async function delegateToSquad(squadId: string, message: string): Promise<string> {
326
+ const runtime = activeSquads.get(squadId);
327
+ if (!runtime) {
328
+ throw new Error(`Squad ${squadId} is not running`);
329
+ }
330
+
331
+ const teamLead = runtime.members.get('team-lead');
332
+ if (!teamLead) {
333
+ throw new Error(`Squad ${squadId} has no team lead`);
334
+ }
335
+
336
+ return teamLead.send(message);
337
+ }
@@ -0,0 +1,135 @@
1
+ import { getClient } from '../copilot/client.js';
2
+ import { createChildLogger } from '../logging/logger.js';
3
+
4
+ const logger = () => createChildLogger('name-generator');
5
+
6
+ export interface NameAssignment {
7
+ role: string;
8
+ displayName: string;
9
+ persona: string;
10
+ }
11
+
12
+ export interface GeneratedNames {
13
+ universe: string;
14
+ assignments: NameAssignment[];
15
+ }
16
+
17
+ const NAME_GENERATION_PROMPT = `You are a creative naming assistant. Your job is to assign pop-culture character names to a team of AI agents based on their technical roles.
18
+
19
+ Rules:
20
+ - Each character name must be UNIQUE within the team
21
+ - Match character personalities to the technical role's nature (e.g. a methodical character for QA, a creative character for frontend)
22
+ - The persona should be 1-2 sentences describing how this character communicates — their tone, quirks, and style
23
+ - Keep personas fun but professional — they should enhance communication, not distract from technical work
24
+ - Return ONLY valid JSON, no markdown fencing
25
+
26
+ Respond with this exact JSON structure:
27
+ {
28
+ "universe": "<the universe name>",
29
+ "assignments": [
30
+ { "role": "<technical role>", "displayName": "<character name>", "persona": "<1-2 sentence persona description>" }
31
+ ]
32
+ }`;
33
+
34
+ /**
35
+ * Generate character names and persona blurbs for squad members using the LLM.
36
+ * If a universe is provided, names come from that universe.
37
+ * If not, the LLM picks a fun universe on its own.
38
+ */
39
+ export async function generateSquadNames(
40
+ roles: string[],
41
+ universe?: string,
42
+ ): Promise<GeneratedNames> {
43
+ const log = logger();
44
+
45
+ const userPrompt = universe
46
+ ? `Assign character names from the "${universe}" universe to these team roles: ${roles.join(', ')}`
47
+ : `Pick a fun pop-culture universe of your choice and assign character names to these team roles: ${roles.join(', ')}`;
48
+
49
+ try {
50
+ const client = await getClient();
51
+ const session = await client.createSession({
52
+ systemMessage: { mode: 'replace' as const, content: NAME_GENERATION_PROMPT },
53
+ });
54
+
55
+ let accumulated = '';
56
+ const unsubDelta = session.on('assistant.message_delta', (event) => {
57
+ accumulated += event.data.deltaContent;
58
+ });
59
+
60
+ try {
61
+ await session.sendAndWait({ prompt: userPrompt }, 60_000);
62
+ } finally {
63
+ unsubDelta();
64
+ }
65
+
66
+ // Parse the JSON response
67
+ const parsed = extractJson(accumulated);
68
+ if (!parsed || !parsed.universe || !Array.isArray(parsed.assignments)) {
69
+ throw new Error('Invalid response structure from LLM');
70
+ }
71
+
72
+ // Validate all roles are covered
73
+ const result: GeneratedNames = {
74
+ universe: parsed.universe as string,
75
+ assignments: [],
76
+ };
77
+
78
+ for (const role of roles) {
79
+ const match = (parsed.assignments as Record<string, string>[]).find(
80
+ (a) => a.role?.toLowerCase() === role.toLowerCase(),
81
+ );
82
+ if (match) {
83
+ result.assignments.push({
84
+ role,
85
+ displayName: match.displayName || match.display_name || role,
86
+ persona: match.persona || '',
87
+ });
88
+ } else {
89
+ // Role wasn't in LLM response — use role name as fallback
90
+ result.assignments.push({ role, displayName: role, persona: '' });
91
+ }
92
+ }
93
+
94
+ log.info({ universe: result.universe, count: result.assignments.length }, 'Names generated');
95
+ return result;
96
+ } catch (err) {
97
+ log.error({ err, roles, universe }, 'Failed to generate names, falling back to role names');
98
+ return fallback(roles);
99
+ }
100
+ }
101
+
102
+ /** Fallback: use role names as display names with no persona */
103
+ function fallback(roles: string[]): GeneratedNames {
104
+ return {
105
+ universe: 'none',
106
+ assignments: roles.map((role) => ({ role, displayName: role, persona: '' })),
107
+ };
108
+ }
109
+
110
+ /** Extract JSON from LLM response (handles possible markdown fencing) */
111
+ function extractJson(text: string): Record<string, unknown> | null {
112
+ try {
113
+ return JSON.parse(text.trim());
114
+ } catch {
115
+ // Try extracting from markdown code fence
116
+ const match = text.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
117
+ if (match) {
118
+ try {
119
+ return JSON.parse(match[1].trim());
120
+ } catch {
121
+ return null;
122
+ }
123
+ }
124
+ // Try finding JSON object in text
125
+ const braceMatch = text.match(/\{[\s\S]*\}/);
126
+ if (braceMatch) {
127
+ try {
128
+ return JSON.parse(braceMatch[0]);
129
+ } catch {
130
+ return null;
131
+ }
132
+ }
133
+ return null;
134
+ }
135
+ }
@@ -0,0 +1,104 @@
1
+ /** Built-in SKILL.md templates for core squad roles */
2
+
3
+ export const TEAM_LEAD_SKILL = `---
4
+ role: team-lead
5
+ tools:
6
+ - read_file
7
+ - search_code
8
+ veto: true
9
+ ---
10
+
11
+ # Team Lead
12
+
13
+ ## Identity
14
+ You are the Team Lead — a senior engineering manager and project coordinator.
15
+
16
+ ## Responsibilities
17
+ - Analyze incoming tasks and break them into actionable work items
18
+ - Assign tasks to the appropriate specialist agents
19
+ - Coordinate round-table meetings for complex decisions
20
+ - Review completed work before it becomes a PR
21
+ - Maintain project direction and consistency
22
+ - Report progress and blockers back to the orchestrator
23
+
24
+ ## Boundaries
25
+ - You do NOT write or edit code directly
26
+ - You do NOT run commands
27
+ - You delegate all implementation work to specialists
28
+ - You focus on planning, coordination, and quality assurance
29
+
30
+ ## Communication Style
31
+ - Be concise and structured in task descriptions
32
+ - Include acceptance criteria for every task
33
+ - Reference relevant files and code patterns when assigning work
34
+ `;
35
+
36
+ export const SCRIBE_SKILL = `---
37
+ role: scribe
38
+ tools:
39
+ - read_file
40
+ - edit_file
41
+ veto: false
42
+ ---
43
+
44
+ # Scribe
45
+
46
+ ## Identity
47
+ You are the Scribe — responsible for recording decisions, meeting notes, and project documentation.
48
+
49
+ ## Responsibilities
50
+ - Record all key decisions made during meetings with rationale
51
+ - Maintain a decisions log that all team members can reference
52
+ - Document architectural choices and trade-offs
53
+ - Write clear commit messages and PR descriptions
54
+ - Update project README and docs when changes warrant it
55
+
56
+ ## Boundaries
57
+ - You do NOT make technical decisions — you record them
58
+ - You do NOT modify source code — only documentation files
59
+ - You ask clarifying questions when decisions are ambiguous
60
+ - You ensure decisions are searchable and well-categorized
61
+
62
+ ## Output Format
63
+ Decisions should follow this format:
64
+ - **Decision**: What was decided
65
+ - **Context**: Why this was needed
66
+ - **Rationale**: Why this option was chosen over alternatives
67
+ - **Consequences**: What this means going forward
68
+ `;
69
+
70
+ export const QA_TESTER_SKILL = `---
71
+ role: qa-tester
72
+ tools:
73
+ - read_file
74
+ - edit_file
75
+ - run_command
76
+ - search_code
77
+ veto: true
78
+ ---
79
+
80
+ # QA / Test Engineer
81
+
82
+ ## Identity
83
+ You are the QA/Test Engineer — the quality gate for all code changes.
84
+
85
+ ## Responsibilities
86
+ - Write comprehensive tests for new features (unit, integration)
87
+ - Run existing test suites and report failures
88
+ - Review code for edge cases, error handling, and security issues
89
+ - Verify changes don't break existing functionality
90
+ - Block merges that don't meet quality standards
91
+
92
+ ## Boundaries
93
+ - You focus on test code, not feature implementation
94
+ - You veto changes that reduce test coverage or break tests
95
+ - You report issues clearly with reproduction steps
96
+ - You suggest fixes but don't implement production features
97
+
98
+ ## Quality Standards
99
+ - All new code must have corresponding tests
100
+ - No PR should reduce overall test coverage
101
+ - All tests must pass before approval
102
+ - Edge cases (empty inputs, null values, errors) must be covered
103
+ - Async code must have timeout and error handling tests
104
+ `;
@@ -0,0 +1,120 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import matter from 'gray-matter';
3
+ import { getActiveSkillsContent } from '../skills/index.js';
4
+ import { getPageListing, getSquadScopes } from '../wiki/index.js';
5
+
6
+ export interface SkillDefinition {
7
+ role: string;
8
+ tools: string[];
9
+ veto: boolean;
10
+ systemPrompt: string;
11
+ rawMarkdown: string;
12
+ filePath: string;
13
+ }
14
+
15
+ interface SkillFrontmatter {
16
+ role: string;
17
+ tools?: string[];
18
+ veto?: boolean;
19
+ }
20
+
21
+ /**
22
+ * Parse a SKILL.md file into a structured SkillDefinition.
23
+ * Format:
24
+ * ---
25
+ * role: react-developer
26
+ * tools:
27
+ * - edit_file
28
+ * - read_file
29
+ * veto: false
30
+ * ---
31
+ * # Role Name
32
+ * System prompt markdown content...
33
+ */
34
+ export function parseSkillFile(filePath: string): SkillDefinition {
35
+ if (!existsSync(filePath)) {
36
+ throw new Error(`SKILL.md not found: ${filePath}`);
37
+ }
38
+
39
+ const raw = readFileSync(filePath, 'utf-8');
40
+ return parseSkillContent(raw, filePath);
41
+ }
42
+
43
+ /**
44
+ * Parse SKILL.md content string into a SkillDefinition.
45
+ */
46
+ export function parseSkillContent(content: string, filePath = '<inline>'): SkillDefinition {
47
+ const { data, content: body } = matter(content);
48
+ const frontmatter = data as SkillFrontmatter;
49
+
50
+ if (!frontmatter.role) {
51
+ throw new Error(`SKILL.md missing required 'role' in frontmatter: ${filePath}`);
52
+ }
53
+
54
+ return {
55
+ role: frontmatter.role,
56
+ tools: frontmatter.tools ?? [],
57
+ veto: frontmatter.veto ?? false,
58
+ systemPrompt: body.trim(),
59
+ rawMarkdown: content,
60
+ filePath,
61
+ };
62
+ }
63
+
64
+ /**
65
+ * Compile a SkillDefinition into a full system message string for the LLM.
66
+ * Injects role identity, boundaries, and tool context.
67
+ */
68
+ export async function compileSystemPrompt(
69
+ skill: SkillDefinition,
70
+ squadContext?: string,
71
+ squadName?: string,
72
+ squadId?: string,
73
+ identity?: { displayName: string; persona?: string; universe?: string },
74
+ ): Promise<string> {
75
+ const parts: string[] = [];
76
+
77
+ if (identity?.displayName && identity.displayName !== skill.role) {
78
+ const intro = `You are ${identity.displayName}${identity.universe ? ` from ${identity.universe}` : ''}, the ${skill.role} agent in an IO squad.`;
79
+ parts.push(intro);
80
+ if (identity.persona) {
81
+ parts.push(identity.persona);
82
+ }
83
+ } else {
84
+ parts.push(`You are the ${skill.role} agent in an IO squad.`);
85
+ }
86
+
87
+ if (squadContext) {
88
+ parts.push(`\n## Squad Context\n${squadContext}`);
89
+ }
90
+
91
+ parts.push(`\n## Your Role\n${skill.systemPrompt}`);
92
+
93
+ if (skill.tools.length > 0) {
94
+ parts.push(`\n## Allowed Tools\nYou may ONLY use: ${skill.tools.join(', ')}`);
95
+ }
96
+
97
+ if (skill.veto) {
98
+ parts.push(
99
+ '\n## Veto Power\nYou have veto power in meetings. Use it when you identify critical issues.',
100
+ );
101
+ }
102
+
103
+ // Inject wiki page listing so agents know what knowledge is available
104
+ if (squadName) {
105
+ const wikiListing = getPageListing(getSquadScopes(squadName));
106
+ parts.push(
107
+ `\n## Wiki Knowledge\n${wikiListing}\n\nUse read_wiki to access page content. Use write_wiki to record important project knowledge.`,
108
+ );
109
+ }
110
+
111
+ // Inject active skills for this squad
112
+ if (squadId) {
113
+ const skillsContent = await getActiveSkillsContent('squad', squadId);
114
+ if (skillsContent) {
115
+ parts.push(skillsContent);
116
+ }
117
+ }
118
+
119
+ return parts.join('\n');
120
+ }