@jaimevalasek/aioson 1.3.0 → 1.5.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 (330) hide show
  1. package/CHANGELOG.md +31 -1
  2. package/LICENSE +661 -21
  3. package/README.md +22 -3
  4. package/docs/en/squad-dashboard.md +372 -0
  5. package/docs/openclaw-bridge.md +308 -0
  6. package/docs/pt/README.md +62 -2
  7. package/docs/pt/advisor-spec.md +5 -5
  8. package/docs/pt/agentes-customizados.md +670 -0
  9. package/docs/pt/agentes.md +235 -23
  10. package/docs/pt/automacao-squads.md +407 -0
  11. package/docs/pt/cenarios.md +49 -5
  12. package/docs/pt/clientes-ai.md +62 -0
  13. package/docs/pt/comandos-cli.md +226 -17
  14. package/docs/pt/deyvin.md +115 -0
  15. package/docs/pt/genome-3.0-spec.md +11 -11
  16. package/docs/pt/inicio-rapido.md +63 -2
  17. package/docs/pt/memoria-contexto.md +255 -0
  18. package/docs/pt/output-strategy-delivery.md +655 -0
  19. package/docs/pt/profiler-system.md +17 -17
  20. package/docs/pt/runtime-observability.md +5 -1
  21. package/docs/pt/skills.md +175 -0
  22. package/docs/pt/squad-dashboard.md +373 -0
  23. package/docs/pt/{squad-genoma.md → squad-genome.md} +81 -75
  24. package/docs/testing/genome-2.0-matrix.md +5 -5
  25. package/docs/testing/genome-2.0-rollout.md +10 -10
  26. package/package.json +4 -4
  27. package/src/agents.js +21 -5
  28. package/src/backup-local.js +74 -0
  29. package/src/backup-provider.js +303 -0
  30. package/src/cli.js +276 -2
  31. package/src/commands/agents.js +22 -4
  32. package/src/commands/backup-local-cmd.js +25 -0
  33. package/src/commands/backup.js +533 -0
  34. package/src/commands/cloud.js +17 -17
  35. package/src/commands/context-pack.js +45 -0
  36. package/src/commands/implementation-plan.js +340 -0
  37. package/src/commands/learning.js +134 -0
  38. package/src/commands/live.js +1583 -0
  39. package/src/commands/runtime.js +1075 -2
  40. package/src/commands/scan-project.js +288 -24
  41. package/src/commands/setup-context.js +30 -2
  42. package/src/commands/skill.js +558 -0
  43. package/src/commands/squad-agent-create.js +788 -0
  44. package/src/commands/squad-daemon.js +209 -0
  45. package/src/commands/squad-dashboard.js +39 -0
  46. package/src/commands/squad-deploy.js +64 -0
  47. package/src/commands/squad-doctor.js +103 -1
  48. package/src/commands/squad-investigate.js +261 -0
  49. package/src/commands/squad-learning.js +209 -0
  50. package/src/commands/squad-mcp.js +270 -0
  51. package/src/commands/squad-pipeline.js +247 -1
  52. package/src/commands/squad-plan.js +329 -0
  53. package/src/commands/squad-processes.js +56 -0
  54. package/src/commands/squad-recovery.js +42 -0
  55. package/src/commands/squad-roi.js +291 -0
  56. package/src/commands/squad-score.js +250 -0
  57. package/src/commands/squad-status.js +38 -2
  58. package/src/commands/squad-validate.js +118 -1
  59. package/src/commands/squad-webhook.js +160 -0
  60. package/src/commands/squad-worker.js +191 -0
  61. package/src/commands/squad-worktrees.js +75 -0
  62. package/src/commands/test-agents.js +6 -1
  63. package/src/commands/web-map.js +70 -0
  64. package/src/commands/web-scrape.js +71 -0
  65. package/src/commands/workflow-next.js +8 -1
  66. package/src/commands/workflow-status.js +250 -0
  67. package/src/constants.js +88 -16
  68. package/src/context-memory.js +837 -0
  69. package/src/context-writer.js +47 -1
  70. package/src/delivery-runner.js +319 -0
  71. package/src/genome-files.js +1 -1
  72. package/src/genome-format.js +1 -1
  73. package/src/i18n/messages/en.js +333 -8
  74. package/src/i18n/messages/es.js +240 -6
  75. package/src/i18n/messages/fr.js +239 -5
  76. package/src/i18n/messages/pt-BR.js +330 -12
  77. package/src/installer.js +30 -2
  78. package/src/lib/genomes/compat.js +1 -1
  79. package/src/lib/webhook-server.js +328 -0
  80. package/src/mcp-connectors/registry.js +602 -0
  81. package/src/runtime-store.js +1037 -42
  82. package/src/session-handoff.js +77 -0
  83. package/src/squad/external-session.js +180 -0
  84. package/src/squad/inter-squad.js +74 -0
  85. package/src/squad/recovery-context.js +201 -0
  86. package/src/squad/worktree-manager.js +114 -0
  87. package/src/squad-daemon.js +490 -0
  88. package/src/squad-dashboard/api.js +223 -0
  89. package/src/squad-dashboard/attachment-handler.js +93 -0
  90. package/src/squad-dashboard/context-monitor.js +157 -0
  91. package/src/squad-dashboard/execution-logs.js +115 -0
  92. package/src/squad-dashboard/hunk-review.js +209 -0
  93. package/src/squad-dashboard/metrics.js +133 -0
  94. package/src/squad-dashboard/process-monitor.js +125 -0
  95. package/src/squad-dashboard/renderer.js +858 -0
  96. package/src/squad-dashboard/server.js +232 -0
  97. package/src/squad-dashboard/styles.js +525 -0
  98. package/src/squad-dashboard/token-tracker.js +99 -0
  99. package/src/web.js +284 -0
  100. package/src/worker-runner.js +339 -0
  101. package/template/.aioson/agents/analyst.md +40 -9
  102. package/template/.aioson/agents/architect.md +24 -5
  103. package/template/.aioson/agents/dev.md +254 -25
  104. package/template/.aioson/agents/deyvin.md +174 -0
  105. package/template/.aioson/agents/discovery-design-doc.md +25 -1
  106. package/template/.aioson/agents/{genoma.md → genome.md} +20 -20
  107. package/template/.aioson/agents/neo.md +152 -0
  108. package/template/.aioson/agents/orache.md +388 -0
  109. package/template/.aioson/agents/orchestrator.md +63 -2
  110. package/template/.aioson/agents/pair.md +5 -0
  111. package/template/.aioson/agents/pm.md +17 -5
  112. package/template/.aioson/agents/product.md +113 -29
  113. package/template/.aioson/agents/profiler-enricher.md +1 -1
  114. package/template/.aioson/agents/profiler-forge.md +9 -9
  115. package/template/.aioson/agents/profiler-researcher.md +1 -1
  116. package/template/.aioson/agents/qa.md +18 -5
  117. package/template/.aioson/agents/setup.md +138 -18
  118. package/template/.aioson/agents/sheldon.md +603 -0
  119. package/template/.aioson/agents/squad.md +866 -28
  120. package/template/.aioson/agents/tester.md +254 -0
  121. package/template/.aioson/agents/ux-ui.md +289 -34
  122. package/template/.aioson/config.md +181 -0
  123. package/template/.aioson/context/spec.md.template +17 -0
  124. package/template/.aioson/genomes/.gitkeep +0 -0
  125. package/template/.aioson/installed-skills/.gitkeep +0 -0
  126. package/template/.aioson/locales/en/agents/analyst.md +34 -4
  127. package/template/.aioson/locales/en/agents/architect.md +18 -0
  128. package/template/.aioson/locales/en/agents/dev.md +155 -11
  129. package/template/.aioson/locales/en/agents/deyvin.md +137 -0
  130. package/template/.aioson/locales/en/agents/{genoma.md → genome.md} +14 -14
  131. package/template/.aioson/locales/en/agents/neo.md +8 -0
  132. package/template/.aioson/locales/en/agents/orchestrator.md +62 -2
  133. package/template/.aioson/locales/en/agents/pair.md +5 -0
  134. package/template/.aioson/locales/en/agents/pm.md +7 -0
  135. package/template/.aioson/locales/en/agents/product.md +35 -17
  136. package/template/.aioson/locales/en/agents/qa.md +56 -0
  137. package/template/.aioson/locales/en/agents/setup.md +53 -6
  138. package/template/.aioson/locales/en/agents/sheldon.md +340 -0
  139. package/template/.aioson/locales/en/agents/squad.md +203 -15
  140. package/template/.aioson/locales/en/agents/ux-ui.md +383 -35
  141. package/template/.aioson/locales/es/agents/analyst.md +24 -4
  142. package/template/.aioson/locales/es/agents/architect.md +18 -0
  143. package/template/.aioson/locales/es/agents/dev.md +136 -9
  144. package/template/.aioson/locales/es/agents/deyvin.md +97 -0
  145. package/template/.aioson/locales/es/agents/{genoma.md → genome.md} +13 -13
  146. package/template/.aioson/locales/es/agents/neo.md +48 -0
  147. package/template/.aioson/locales/es/agents/orache.md +103 -0
  148. package/template/.aioson/locales/es/agents/orchestrator.md +62 -2
  149. package/template/.aioson/locales/es/agents/pair.md +5 -0
  150. package/template/.aioson/locales/es/agents/pm.md +7 -0
  151. package/template/.aioson/locales/es/agents/product.md +13 -3
  152. package/template/.aioson/locales/es/agents/qa.md +33 -0
  153. package/template/.aioson/locales/es/agents/setup.md +30 -6
  154. package/template/.aioson/locales/es/agents/sheldon.md +192 -0
  155. package/template/.aioson/locales/es/agents/squad.md +284 -15
  156. package/template/.aioson/locales/es/agents/ux-ui.md +34 -25
  157. package/template/.aioson/locales/fr/agents/analyst.md +24 -4
  158. package/template/.aioson/locales/fr/agents/architect.md +18 -0
  159. package/template/.aioson/locales/fr/agents/dev.md +136 -9
  160. package/template/.aioson/locales/fr/agents/deyvin.md +97 -0
  161. package/template/.aioson/locales/fr/agents/{genoma.md → genome.md} +7 -7
  162. package/template/.aioson/locales/fr/agents/neo.md +48 -0
  163. package/template/.aioson/locales/fr/agents/orache.md +104 -0
  164. package/template/.aioson/locales/fr/agents/orchestrator.md +62 -2
  165. package/template/.aioson/locales/fr/agents/pair.md +5 -0
  166. package/template/.aioson/locales/fr/agents/pm.md +7 -0
  167. package/template/.aioson/locales/fr/agents/product.md +13 -3
  168. package/template/.aioson/locales/fr/agents/qa.md +33 -0
  169. package/template/.aioson/locales/fr/agents/setup.md +30 -6
  170. package/template/.aioson/locales/fr/agents/sheldon.md +192 -0
  171. package/template/.aioson/locales/fr/agents/squad.md +279 -10
  172. package/template/.aioson/locales/fr/agents/ux-ui.md +34 -25
  173. package/template/.aioson/locales/pt-BR/agents/analyst.md +45 -4
  174. package/template/.aioson/locales/pt-BR/agents/architect.md +29 -0
  175. package/template/.aioson/locales/pt-BR/agents/dev.md +167 -15
  176. package/template/.aioson/locales/pt-BR/agents/deyvin.md +137 -0
  177. package/template/.aioson/locales/pt-BR/agents/{genoma.md → genome.md} +49 -49
  178. package/template/.aioson/locales/pt-BR/agents/neo.md +147 -0
  179. package/template/.aioson/locales/pt-BR/agents/orache.md +137 -0
  180. package/template/.aioson/locales/pt-BR/agents/orchestrator.md +62 -2
  181. package/template/.aioson/locales/pt-BR/agents/pair.md +5 -0
  182. package/template/.aioson/locales/pt-BR/agents/pm.md +7 -0
  183. package/template/.aioson/locales/pt-BR/agents/product.md +43 -20
  184. package/template/.aioson/locales/pt-BR/agents/qa.md +67 -0
  185. package/template/.aioson/locales/pt-BR/agents/setup.md +53 -6
  186. package/template/.aioson/locales/pt-BR/agents/sheldon.md +192 -0
  187. package/template/.aioson/locales/pt-BR/agents/squad.md +591 -47
  188. package/template/.aioson/locales/pt-BR/agents/ux-ui.md +369 -22
  189. package/template/.aioson/my-agents/.gitkeep +0 -0
  190. package/template/.aioson/rules/.gitkeep +0 -0
  191. package/template/.aioson/rules/squad/.gitkeep +0 -0
  192. package/template/.aioson/rules/squad/README.md +50 -0
  193. package/template/.aioson/schemas/genome-meta.schema.json +1 -1
  194. package/template/.aioson/schemas/genome.schema.json +1 -1
  195. package/template/.aioson/schemas/squad-blueprint.schema.json +32 -0
  196. package/template/.aioson/schemas/squad-manifest.schema.json +434 -1
  197. package/template/.aioson/skills/design/bold-editorial-ui/SKILL.md +205 -0
  198. package/template/.aioson/skills/design/bold-editorial-ui/references/art-direction.md +338 -0
  199. package/template/.aioson/skills/design/bold-editorial-ui/references/components.md +977 -0
  200. package/template/.aioson/skills/design/bold-editorial-ui/references/dashboards.md +218 -0
  201. package/template/.aioson/skills/design/bold-editorial-ui/references/design-tokens.md +326 -0
  202. package/template/.aioson/skills/design/bold-editorial-ui/references/motion.md +461 -0
  203. package/template/.aioson/skills/design/bold-editorial-ui/references/patterns.md +293 -0
  204. package/template/.aioson/skills/design/bold-editorial-ui/references/websites.md +352 -0
  205. package/template/.aioson/skills/design/clean-saas-ui/SKILL.md +210 -0
  206. package/template/.aioson/skills/design/clean-saas-ui/references/art-direction.md +319 -0
  207. package/template/.aioson/skills/design/clean-saas-ui/references/components.md +365 -0
  208. package/template/.aioson/skills/design/clean-saas-ui/references/dashboards.md +196 -0
  209. package/template/.aioson/skills/design/clean-saas-ui/references/design-tokens.md +244 -0
  210. package/template/.aioson/skills/design/clean-saas-ui/references/motion.md +235 -0
  211. package/template/.aioson/skills/design/clean-saas-ui/references/patterns.md +215 -0
  212. package/template/.aioson/skills/design/clean-saas-ui/references/websites.md +295 -0
  213. package/template/.aioson/skills/design/cognitive-core-ui/SKILL.md +203 -0
  214. package/template/.aioson/skills/design/cognitive-core-ui/references/art-direction.md +339 -0
  215. package/template/.aioson/skills/design/cognitive-core-ui/references/components.md +407 -0
  216. package/template/.aioson/skills/design/cognitive-core-ui/references/dashboards.md +272 -0
  217. package/template/.aioson/skills/design/cognitive-core-ui/references/design-tokens.md +524 -0
  218. package/template/.aioson/skills/design/cognitive-core-ui/references/motion.md +277 -0
  219. package/template/.aioson/skills/design/cognitive-core-ui/references/patterns.md +289 -0
  220. package/template/.aioson/skills/design/cognitive-core-ui/references/websites.md +437 -0
  221. package/template/.aioson/skills/design/interface-design/SKILL.md +47 -0
  222. package/template/.aioson/skills/design/interface-design/references/components-and-states.md +105 -0
  223. package/template/.aioson/skills/design/interface-design/references/design-directions.md +101 -0
  224. package/template/.aioson/skills/design/interface-design/references/handoff-and-quality.md +71 -0
  225. package/template/.aioson/skills/design/interface-design/references/intent-and-domain.md +74 -0
  226. package/template/.aioson/skills/design/interface-design/references/tokens-and-depth.md +173 -0
  227. package/template/.aioson/skills/design/premium-command-center-ui/SKILL.md +62 -0
  228. package/template/.aioson/skills/design/premium-command-center-ui/references/operations.md +74 -0
  229. package/template/.aioson/skills/design/premium-command-center-ui/references/patterns.md +116 -0
  230. package/template/.aioson/skills/design/premium-command-center-ui/references/validation.md +47 -0
  231. package/template/.aioson/skills/design/premium-command-center-ui/references/visual-system.md +215 -0
  232. package/template/.aioson/skills/design/warm-craft-ui/SKILL.md +209 -0
  233. package/template/.aioson/skills/design/warm-craft-ui/references/art-direction.md +324 -0
  234. package/template/.aioson/skills/design/warm-craft-ui/references/components.md +508 -0
  235. package/template/.aioson/skills/design/warm-craft-ui/references/dashboards.md +223 -0
  236. package/template/.aioson/skills/design/warm-craft-ui/references/design-tokens.md +374 -0
  237. package/template/.aioson/skills/design/warm-craft-ui/references/motion.md +356 -0
  238. package/template/.aioson/skills/design/warm-craft-ui/references/patterns.md +288 -0
  239. package/template/.aioson/skills/design/warm-craft-ui/references/websites.md +289 -0
  240. package/template/.aioson/skills/design-system/SKILL.md +92 -0
  241. package/template/.aioson/skills/design-system/cognitive-core-ui.skill +0 -0
  242. package/template/.aioson/skills/design-system/components/SKILL.md +274 -0
  243. package/template/.aioson/skills/design-system/components/SKILL.md:Zone.Identifier +0 -0
  244. package/template/.aioson/skills/design-system/dashboards/SKILL.md +184 -0
  245. package/template/.aioson/skills/design-system/dashboards/SKILL.md:Zone.Identifier +0 -0
  246. package/template/.aioson/skills/design-system/foundations/SKILL.md +250 -0
  247. package/template/.aioson/skills/design-system/foundations/SKILL.md:Zone.Identifier +0 -0
  248. package/template/.aioson/skills/design-system/motion/SKILL.md +197 -0
  249. package/template/.aioson/skills/design-system/motion/SKILL.md:Zone.Identifier +0 -0
  250. package/template/.aioson/skills/design-system/patterns/SKILL.md +231 -0
  251. package/template/.aioson/skills/design-system/patterns/SKILL.md:Zone.Identifier +0 -0
  252. package/template/.aioson/skills/premium-visual-design/SKILL.md +83 -0
  253. package/template/.aioson/skills/premium-visual-design/components/agent-badge.md +92 -0
  254. package/template/.aioson/skills/premium-visual-design/components/dependency-node.md +102 -0
  255. package/template/.aioson/skills/premium-visual-design/components/mention-autocomplete.md +136 -0
  256. package/template/.aioson/skills/premium-visual-design/components/notification-center.md +136 -0
  257. package/template/.aioson/skills/premium-visual-design/components/review-action-bar.md +188 -0
  258. package/template/.aioson/skills/premium-visual-design/components/team-switcher.md +131 -0
  259. package/template/.aioson/skills/premium-visual-design/patterns/agent-message-thread.md +198 -0
  260. package/template/.aioson/skills/premium-visual-design/patterns/notification-panel.md +275 -0
  261. package/template/.aioson/skills/premium-visual-design/patterns/review-workflow-ui.md +234 -0
  262. package/template/.aioson/skills/premium-visual-design/patterns/task-dependency-graph.md +147 -0
  263. package/template/.aioson/skills/premium-visual-design/tokens/status-extended.md +142 -0
  264. package/template/.aioson/skills/squad/SKILL.md +58 -0
  265. package/template/.aioson/skills/squad/domains/.gitkeep +0 -0
  266. package/template/.aioson/skills/squad/formats/.gitkeep +0 -0
  267. package/template/.aioson/skills/squad/formats/catalog.json +15 -0
  268. package/template/.aioson/skills/squad/formats/content/blog-post.md +47 -0
  269. package/template/.aioson/skills/squad/formats/content/newsletter.md +47 -0
  270. package/template/.aioson/skills/squad/formats/creative/podcast-script.md +43 -0
  271. package/template/.aioson/skills/squad/formats/creative/video-script.md +41 -0
  272. package/template/.aioson/skills/squad/formats/social/instagram-feed.md +42 -0
  273. package/template/.aioson/skills/squad/formats/social/linkedin-post.md +42 -0
  274. package/template/.aioson/skills/squad/formats/social/tiktok.md +39 -0
  275. package/template/.aioson/skills/squad/formats/social/twitter-thread.md +39 -0
  276. package/template/.aioson/skills/squad/formats/social/youtube-long.md +47 -0
  277. package/template/.aioson/skills/squad/formats/social/youtube-shorts.md +39 -0
  278. package/template/.aioson/skills/squad/patterns/.gitkeep +0 -0
  279. package/template/.aioson/skills/squad/patterns/multi-platform-pattern.md +108 -0
  280. package/template/.aioson/skills/squad/patterns/persona-based-pattern.md +98 -0
  281. package/template/.aioson/skills/squad/patterns/pipeline-pattern.md +106 -0
  282. package/template/.aioson/skills/squad/patterns/review-loop-pattern.md +81 -0
  283. package/template/.aioson/skills/squad/references/.gitkeep +0 -0
  284. package/template/.aioson/skills/squad/references/checklist-templates.md +122 -0
  285. package/template/.aioson/skills/squad/references/executor-archetypes.md +123 -0
  286. package/template/.aioson/skills/squad/references/workflow-templates.md +169 -0
  287. package/template/.aioson/skills/static/debugging-protocol.md +42 -0
  288. package/template/.aioson/skills/static/git-worktrees.md +36 -0
  289. package/template/.aioson/tasks/implementation-plan.md +307 -0
  290. package/template/.aioson/tasks/squad-create.md +1 -1
  291. package/template/.aioson/tasks/squad-design.md +28 -0
  292. package/template/.aioson/tasks/squad-execution-plan.md +279 -0
  293. package/template/.aioson/tasks/squad-export.md +1 -1
  294. package/template/.aioson/tasks/squad-investigate.md +44 -0
  295. package/template/.aioson/tasks/squad-learning-review.md +44 -0
  296. package/template/.aioson/tasks/squad-output-config.md +177 -0
  297. package/template/.aioson/tasks/squad-profile.md +48 -0
  298. package/template/.aioson/tasks/squad-review.md +61 -0
  299. package/template/.aioson/tasks/squad-task-decompose.md +66 -0
  300. package/template/.aioson/tasks/squad-validate.md +1 -1
  301. package/template/.claude/commands/aioson/agent/deyvin.md +5 -0
  302. package/template/.claude/commands/aioson/agent/discovery-design-doc.md +5 -0
  303. package/template/.claude/commands/aioson/agent/genome.md +5 -0
  304. package/template/.claude/commands/aioson/agent/neo.md +5 -0
  305. package/template/.claude/commands/aioson/agent/product.md +5 -0
  306. package/template/.claude/commands/aioson/agent/profiler-enricher.md +5 -0
  307. package/template/.claude/commands/aioson/agent/profiler-forge.md +5 -0
  308. package/template/.claude/commands/aioson/agent/profiler-researcher.md +5 -0
  309. package/template/.claude/commands/aioson/agent/squad.md +5 -0
  310. package/template/.claude/commands/aioson/agent/tester.md +5 -0
  311. package/template/.gemini/GEMINI.md +3 -0
  312. package/template/.gemini/commands/aios-deyvin.toml +6 -0
  313. package/template/.gemini/commands/aios-neo.toml +4 -0
  314. package/template/.gemini/commands/aios-pair.toml +6 -0
  315. package/template/.gemini/commands/aios-tester.toml +6 -0
  316. package/template/AGENTS.md +37 -6
  317. package/template/CLAUDE.md +34 -4
  318. package/template/OPENCODE.md +8 -2
  319. package/template/squad-searches/.gitkeep +0 -0
  320. package/template/.aioson/skills/static/interface-design.md +0 -372
  321. package/template/.aioson/skills/static/premium-command-center-ui.md +0 -190
  322. /package/template/.aioson/{genomas → docs}/.gitkeep +0 -0
  323. /package/template/.claude/commands/aioson/{analyst.md → agent/analyst.md} +0 -0
  324. /package/template/.claude/commands/aioson/{architect.md → agent/architect.md} +0 -0
  325. /package/template/.claude/commands/aioson/{dev.md → agent/dev.md} +0 -0
  326. /package/template/.claude/commands/aioson/{orchestrator.md → agent/orchestrator.md} +0 -0
  327. /package/template/.claude/commands/aioson/{pm.md → agent/pm.md} +0 -0
  328. /package/template/.claude/commands/aioson/{qa.md → agent/qa.md} +0 -0
  329. /package/template/.claude/commands/aioson/{setup.md → agent/setup.md} +0 -0
  330. /package/template/.claude/commands/aioson/{ux-ui.md → agent/ux-ui.md} +0 -0
@@ -0,0 +1,490 @@
1
+ 'use strict';
2
+
3
+ const http = require('node:http');
4
+ const fs = require('node:fs/promises');
5
+ const path = require('node:path');
6
+ const { openRuntimeDb, insertWorkerRun } = require('./runtime-store');
7
+ const { listWorkers, runWorker, loadWorkerConfig } = require('./worker-runner');
8
+
9
+ const SQUADS_DIR = path.join('.aioson', 'squads');
10
+
11
+ // --- Simple Cron Parser (zero dependencies) ---
12
+
13
+ const CRON_PRESETS = {
14
+ '@yearly': '0 0 1 1 *',
15
+ '@monthly': '0 0 1 * *',
16
+ '@weekly': '0 0 * * 0',
17
+ '@daily': '0 0 * * *',
18
+ '@hourly': '0 * * * *',
19
+ '@every5m': '*/5 * * * *',
20
+ '@every10m': '*/10 * * * *',
21
+ '@every15m': '*/15 * * * *',
22
+ '@every30m': '*/30 * * * *'
23
+ };
24
+
25
+ function parseCronField(field, min, max) {
26
+ if (field === '*') return null; // matches all
27
+ const values = new Set();
28
+ for (const part of field.split(',')) {
29
+ const stepMatch = part.match(/^(\*|\d+(?:-\d+)?)\/(\d+)$/);
30
+ if (stepMatch) {
31
+ const step = parseInt(stepMatch[2], 10);
32
+ let start = min;
33
+ let end = max;
34
+ if (stepMatch[1] !== '*') {
35
+ const range = stepMatch[1].split('-');
36
+ start = parseInt(range[0], 10);
37
+ if (range[1]) end = parseInt(range[1], 10);
38
+ }
39
+ for (let i = start; i <= end; i += step) values.add(i);
40
+ } else if (part.includes('-')) {
41
+ const [a, b] = part.split('-').map(Number);
42
+ for (let i = a; i <= b; i++) values.add(i);
43
+ } else {
44
+ values.add(parseInt(part, 10));
45
+ }
46
+ }
47
+ return values;
48
+ }
49
+
50
+ function parseCronExpression(expr) {
51
+ const resolved = CRON_PRESETS[expr] || expr;
52
+ const parts = resolved.trim().split(/\s+/);
53
+ if (parts.length !== 5) return null;
54
+ return {
55
+ minute: parseCronField(parts[0], 0, 59),
56
+ hour: parseCronField(parts[1], 0, 23),
57
+ dayOfMonth: parseCronField(parts[2], 1, 31),
58
+ month: parseCronField(parts[3], 1, 12),
59
+ dayOfWeek: parseCronField(parts[4], 0, 6)
60
+ };
61
+ }
62
+
63
+ function cronMatches(parsed, date) {
64
+ if (!parsed) return false;
65
+ const checks = [
66
+ [parsed.minute, date.getMinutes()],
67
+ [parsed.hour, date.getHours()],
68
+ [parsed.dayOfMonth, date.getDate()],
69
+ [parsed.month, date.getMonth() + 1],
70
+ [parsed.dayOfWeek, date.getDay()]
71
+ ];
72
+ return checks.every(([field, val]) => field === null || field.has(val));
73
+ }
74
+
75
+ // --- Squad Daemon ---
76
+
77
+ class SquadDaemon {
78
+ constructor(projectDir, squadSlug, options = {}) {
79
+ this.projectDir = projectDir;
80
+ this.squadSlug = squadSlug;
81
+ this.webhookPort = options.port || 0;
82
+ this.pollInterval = options.poll || 10000;
83
+ this.config = options.config || {};
84
+ this.running = false;
85
+ this.db = null;
86
+ this.httpServer = null;
87
+ this.cronTimer = null;
88
+ this.pollTimer = null;
89
+ this.cronJobs = [];
90
+ this.lastCronCheck = null;
91
+ this.eventLog = [];
92
+ this.startedAt = null;
93
+ }
94
+
95
+ log(level, message, data) {
96
+ const entry = {
97
+ ts: new Date().toISOString(),
98
+ level,
99
+ squad: this.squadSlug,
100
+ message,
101
+ ...(data || {})
102
+ };
103
+ this.eventLog.push(entry);
104
+ if (this.eventLog.length > 500) this.eventLog.shift();
105
+ return entry;
106
+ }
107
+
108
+ async start() {
109
+ // 1. Open runtime DB
110
+ const handle = await openRuntimeDb(this.projectDir, { mustExist: false });
111
+ if (!handle) {
112
+ throw new Error('Could not open runtime database');
113
+ }
114
+ this.db = handle.db;
115
+
116
+ // 2. Load workers and register cron jobs
117
+ const workers = await listWorkers(this.projectDir, this.squadSlug);
118
+ this.cronJobs = [];
119
+ for (const worker of workers) {
120
+ if (worker.type === 'scheduled' && worker.trigger && worker.trigger.cron) {
121
+ const parsed = parseCronExpression(worker.trigger.cron);
122
+ if (parsed) {
123
+ this.cronJobs.push({ workerSlug: worker.slug, cron: worker.trigger.cron, parsed });
124
+ }
125
+ }
126
+ }
127
+
128
+ // 3. Start webhook HTTP server
129
+ this.httpServer = await this._startWebhookServer();
130
+
131
+ // 4. Start cron check loop (every 60s, checks at minute boundaries)
132
+ this.lastCronCheck = new Date();
133
+ this.cronTimer = setInterval(() => this._checkCron(), 60000);
134
+
135
+ // 5. Start event poll loop
136
+ this.pollTimer = setInterval(() => this._pollEvents(), this.pollInterval);
137
+
138
+ // 6. Register daemon in SQLite
139
+ this._upsertDaemonRecord('running');
140
+
141
+ this.running = true;
142
+ await this._processInbox();
143
+
144
+ this.startedAt = new Date().toISOString();
145
+ this.log('info', 'Daemon started', {
146
+ port: this.webhookPort,
147
+ cronJobs: this.cronJobs.length,
148
+ workers: workers.length
149
+ });
150
+
151
+ return {
152
+ port: this.webhookPort,
153
+ cronJobs: this.cronJobs.length,
154
+ workers: workers.length
155
+ };
156
+ }
157
+
158
+ async stop() {
159
+ this.running = false;
160
+
161
+ if (this.cronTimer) {
162
+ clearInterval(this.cronTimer);
163
+ this.cronTimer = null;
164
+ }
165
+ if (this.pollTimer) {
166
+ clearInterval(this.pollTimer);
167
+ this.pollTimer = null;
168
+ }
169
+
170
+ if (this.httpServer) {
171
+ await new Promise((resolve) => this.httpServer.close(resolve));
172
+ this.httpServer = null;
173
+ }
174
+
175
+ this._upsertDaemonRecord('stopped');
176
+
177
+ if (this.db) {
178
+ this.db.close();
179
+ this.db = null;
180
+ }
181
+
182
+ this.log('info', 'Daemon stopped');
183
+ }
184
+
185
+ getStatus() {
186
+ return {
187
+ squad: this.squadSlug,
188
+ running: this.running,
189
+ port: this.webhookPort,
190
+ startedAt: this.startedAt,
191
+ cronJobs: this.cronJobs.map(j => ({ worker: j.workerSlug, cron: j.cron })),
192
+ recentLogs: this.eventLog.slice(-20)
193
+ };
194
+ }
195
+
196
+ // --- Private methods ---
197
+
198
+ _startWebhookServer() {
199
+ return new Promise((resolve, reject) => {
200
+ const server = http.createServer(async (req, res) => {
201
+ await this._handleWebhook(req, res);
202
+ });
203
+ server.on('error', reject);
204
+ const bindAddr = this.config.webhook?.bind || '127.0.0.1';
205
+ server.listen(this.webhookPort, bindAddr, () => {
206
+ this.webhookPort = server.address().port;
207
+ resolve(server);
208
+ });
209
+ });
210
+ }
211
+
212
+ async _handleWebhook(req, res) {
213
+ const segments = (req.url || '').replace(/\/+$/, '').split('/').filter(Boolean);
214
+
215
+ // Status aceita GET e POST (health check externo)
216
+ if (segments[0] === 'status' && segments.length === 1) {
217
+ res.writeHead(200, { 'Content-Type': 'application/json' });
218
+ res.end(JSON.stringify(this.getStatus()));
219
+ return;
220
+ }
221
+
222
+ if (req.method !== 'POST' && req.method !== 'OPTIONS') {
223
+ res.writeHead(405, { 'Content-Type': 'application/json' });
224
+ res.end(JSON.stringify({ error: 'Method not allowed' }));
225
+ return;
226
+ }
227
+
228
+ // Read raw body (must be before JSON.parse so HMAC validates original bytes)
229
+ let rawBody = '';
230
+ for await (const chunk of req) rawBody += chunk;
231
+
232
+ // HMAC signature validation
233
+ if (this.config.webhook?.validate_signature) {
234
+ const { createHmac, timingSafeEqual } = require('node:crypto');
235
+ const envKey = this.config.webhook.signature_env || 'WEBHOOK_SECRET';
236
+ const headerKey = (this.config.webhook.signature_header || 'x-hub-signature-256').toLowerCase();
237
+ const secret = process.env[envKey];
238
+ const receivedSig = req.headers[headerKey];
239
+
240
+ if (!secret || !receivedSig) {
241
+ res.writeHead(401, { 'Content-Type': 'application/json' });
242
+ res.end(JSON.stringify({ error: 'signature_required' }));
243
+ return;
244
+ }
245
+ const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex');
246
+ try {
247
+ if (!timingSafeEqual(Buffer.from(receivedSig), Buffer.from(expected))) {
248
+ res.writeHead(401, { 'Content-Type': 'application/json' });
249
+ res.end(JSON.stringify({ error: 'invalid_signature' }));
250
+ return;
251
+ }
252
+ } catch {
253
+ res.writeHead(401, { 'Content-Type': 'application/json' });
254
+ res.end(JSON.stringify({ error: 'invalid_signature' }));
255
+ return;
256
+ }
257
+ }
258
+
259
+ let payload;
260
+ try {
261
+ payload = JSON.parse(rawBody || '{}');
262
+ } catch {
263
+ res.writeHead(400, { 'Content-Type': 'application/json' });
264
+ res.end(JSON.stringify({ error: 'Invalid JSON body' }));
265
+ return;
266
+ }
267
+
268
+ // POST /call/:worker (inter-squad)
269
+ if (segments[0] === 'call' && segments[1]) {
270
+ const depth = payload?._inter_squad?.depth ?? 0;
271
+ if (depth > 5) {
272
+ res.writeHead(429, { 'Content-Type': 'application/json' });
273
+ res.end(JSON.stringify({ error: 'cascade_guard' }));
274
+ return;
275
+ }
276
+ const result = await this._executeWorker(segments[1], payload, 'inter-squad');
277
+ res.writeHead(result.ok ? 200 : 500, { 'Content-Type': 'application/json' });
278
+ res.end(JSON.stringify(result));
279
+ return;
280
+ }
281
+
282
+ // /api/:path (public API endpoints)
283
+ if (segments[0] === 'api' && segments[1]) {
284
+ const apiPath = '/' + segments[1];
285
+ const endpoint = (this.config.api_endpoints || []).find(e => e.path === apiPath);
286
+
287
+ if (!endpoint) {
288
+ res.writeHead(404, { 'Content-Type': 'application/json' });
289
+ res.end(JSON.stringify({ error: 'api_endpoint_not_found' }));
290
+ return;
291
+ }
292
+
293
+ const origin = req.headers['origin'];
294
+ const corsOrigins = endpoint.cors_origins || [];
295
+ if (origin && corsOrigins.includes(origin)) {
296
+ res.setHeader('Access-Control-Allow-Origin', origin);
297
+ res.setHeader('Access-Control-Allow-Methods', endpoint.method || 'POST');
298
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
299
+ res.setHeader('Vary', 'Origin');
300
+ }
301
+ if (req.method === 'OPTIONS') {
302
+ res.writeHead(204);
303
+ res.end();
304
+ return;
305
+ }
306
+
307
+ const result = await this._executeWorker(endpoint.worker, payload, 'api');
308
+ res.writeHead(result.ok ? 200 : 500, { 'Content-Type': 'application/json' });
309
+ res.end(JSON.stringify(result));
310
+ return;
311
+ }
312
+
313
+ if (segments[0] !== 'webhook' || !segments[1]) {
314
+ res.writeHead(404, { 'Content-Type': 'application/json' });
315
+ res.end(JSON.stringify({ error: 'Not found. Use POST /webhook/<worker-slug>' }));
316
+ return;
317
+ }
318
+
319
+ const workerSlug = segments[1];
320
+
321
+ this.log('info', `Webhook received for ${workerSlug}`, { payload });
322
+
323
+ // Execute worker
324
+ const result = await this._executeWorker(workerSlug, payload, 'webhook');
325
+
326
+ const statusCode = result.ok ? 200 : 500;
327
+ res.writeHead(statusCode, { 'Content-Type': 'application/json' });
328
+ res.end(JSON.stringify(result));
329
+ }
330
+
331
+ async _checkCron() {
332
+ if (!this.running || this.cronJobs.length === 0) return;
333
+
334
+ const now = new Date();
335
+ for (const job of this.cronJobs) {
336
+ if (cronMatches(job.parsed, now)) {
337
+ this.log('info', `Cron triggered: ${job.workerSlug}`, { cron: job.cron });
338
+ this._executeWorker(job.workerSlug, {}, 'scheduled').catch(err => {
339
+ this.log('error', `Cron execution failed: ${job.workerSlug}`, { error: err.message });
340
+ });
341
+ }
342
+ }
343
+ this.lastCronCheck = now;
344
+
345
+ // Update heartbeat
346
+ this._updateHeartbeat();
347
+ }
348
+
349
+ async _pollEvents() {
350
+ if (!this.running || !this.db) return;
351
+
352
+ // Poll for pending handoffs targeted at this squad
353
+ try {
354
+ const pending = this.db.prepare(
355
+ "SELECT * FROM squad_handoffs WHERE to_squad = ? AND status = 'pending' ORDER BY created_at ASC LIMIT 5"
356
+ ).all(this.squadSlug);
357
+
358
+ for (const handoff of pending) {
359
+ const payload = handoff.payload_json ? JSON.parse(handoff.payload_json) : {};
360
+ this.log('info', `Handoff received from ${handoff.from_squad}`, { handoffId: handoff.id });
361
+
362
+ // Find event-triggered workers
363
+ const workers = await listWorkers(this.projectDir, this.squadSlug);
364
+ for (const w of workers) {
365
+ if (w.type === 'event' && w.trigger && w.trigger.source === 'handoff') {
366
+ await this._executeWorker(w.slug, payload, 'event');
367
+ }
368
+ }
369
+
370
+ // Mark handoff consumed
371
+ this.db.prepare(
372
+ "UPDATE squad_handoffs SET status = 'consumed', consumed_at = datetime('now') WHERE id = ?"
373
+ ).run(handoff.id);
374
+ }
375
+ } catch (err) {
376
+ this.log('error', 'Poll error', { error: err.message });
377
+ }
378
+
379
+ this._updateHeartbeat();
380
+ }
381
+
382
+ async _executeWorker(workerSlug, inputPayload, triggerType) {
383
+ const result = await runWorker(this.projectDir, this.squadSlug, workerSlug, inputPayload, {
384
+ triggerType,
385
+ noRetry: false
386
+ });
387
+
388
+ // Log to runtime store
389
+ if (this.db) {
390
+ try {
391
+ const conversationId = triggerType === 'inter-squad'
392
+ ? inputPayload?._inter_squad?.conversationId
393
+ : undefined;
394
+ insertWorkerRun(this.db, {
395
+ squadSlug: this.squadSlug,
396
+ workerSlug,
397
+ triggerType,
398
+ inputJson: JSON.stringify(inputPayload),
399
+ outputJson: result.ok ? JSON.stringify(result.output) : null,
400
+ status: result.ok ? 'completed' : 'failed',
401
+ errorMessage: result.ok ? null : result.error,
402
+ durationMs: result.durationMs || 0,
403
+ attempt: result.attempt || 1,
404
+ conversationId
405
+ });
406
+ } catch (err) {
407
+ this.log('error', 'Failed to log worker run', { error: err.message });
408
+ }
409
+ }
410
+
411
+ this.log(result.ok ? 'info' : 'error', `Worker ${workerSlug}: ${result.ok ? 'completed' : 'failed'}`, {
412
+ triggerType,
413
+ durationMs: result.durationMs
414
+ });
415
+
416
+ return result;
417
+ }
418
+
419
+ async _processInbox() {
420
+ const inboxDir = path.join(this.projectDir, '.aioson', 'squads', this.squadSlug, 'inbox');
421
+ let entries;
422
+ try { entries = await fs.readdir(inboxDir); } catch { return; }
423
+
424
+ for (const file of entries.filter(f => f.endsWith('.json'))) {
425
+ const filePath = path.join(inboxDir, file);
426
+ try {
427
+ const raw = await fs.readFile(filePath, 'utf8');
428
+ const msg = JSON.parse(raw);
429
+ const result = await this._executeWorker(msg.worker, msg.payload, 'inter-squad');
430
+ if (result.ok) {
431
+ await fs.unlink(filePath);
432
+ } else {
433
+ const failDir = path.join(inboxDir, 'failed');
434
+ await fs.mkdir(failDir, { recursive: true });
435
+ await fs.rename(filePath, path.join(failDir, file));
436
+ }
437
+ } catch {
438
+ // arquivo corrompido — mover para failed
439
+ }
440
+ }
441
+ }
442
+
443
+ _upsertDaemonRecord(status) {
444
+ if (!this.db) return;
445
+ try {
446
+ this.db.prepare(`
447
+ INSERT INTO squad_daemons (squad_slug, status, pid, port, started_at, last_heartbeat, config_json)
448
+ VALUES (@squad_slug, @status, @pid, @port, @started_at, @last_heartbeat, @config_json)
449
+ ON CONFLICT(squad_slug) DO UPDATE SET
450
+ status = excluded.status,
451
+ pid = excluded.pid,
452
+ port = excluded.port,
453
+ started_at = CASE WHEN excluded.status = 'running' THEN excluded.started_at ELSE squad_daemons.started_at END,
454
+ last_heartbeat = excluded.last_heartbeat,
455
+ config_json = excluded.config_json,
456
+ error_message = NULL
457
+ `).run({
458
+ squad_slug: this.squadSlug,
459
+ status,
460
+ pid: process.pid,
461
+ port: this.webhookPort || null,
462
+ started_at: this.startedAt || new Date().toISOString(),
463
+ last_heartbeat: new Date().toISOString(),
464
+ config_json: JSON.stringify({
465
+ cronJobs: this.cronJobs.map(j => ({ worker: j.workerSlug, cron: j.cron }))
466
+ })
467
+ });
468
+ } catch {
469
+ // Ignore — daemon record is nice-to-have
470
+ }
471
+ }
472
+
473
+ _updateHeartbeat() {
474
+ if (!this.db) return;
475
+ try {
476
+ this.db.prepare(
477
+ "UPDATE squad_daemons SET last_heartbeat = datetime('now') WHERE squad_slug = ?"
478
+ ).run(this.squadSlug);
479
+ } catch {
480
+ // Ignore
481
+ }
482
+ }
483
+ }
484
+
485
+ module.exports = {
486
+ SquadDaemon,
487
+ parseCronExpression,
488
+ cronMatches,
489
+ parseCronField
490
+ };
@@ -0,0 +1,223 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs/promises');
4
+ const path = require('node:path');
5
+ const {
6
+ getSquadOverview,
7
+ getRecentContent,
8
+ getLearnings,
9
+ getRecentDeliveries,
10
+ getRecentEvents,
11
+ getSquadMetrics
12
+ } = require('./metrics');
13
+ const { getContextUsage } = require('./context-monitor');
14
+ const { getTokenUsage } = require('./token-tracker');
15
+ const { getActiveProcesses, stopProcess } = require('./process-monitor');
16
+ const { getHunks, updateHunk, getReviewProgress, HUNK_STATES } = require('./hunk-review');
17
+ const { generateRecovery, readRecovery } = require('../squad/recovery-context');
18
+ const { getLogsForTask, getSessionLog } = require('./execution-logs');
19
+
20
+ const SQUADS_DIR = path.join('.aioson', 'squads');
21
+
22
+ async function loadSquadList(projectDir) {
23
+ const squadsDir = path.join(projectDir, SQUADS_DIR);
24
+ let entries;
25
+ try {
26
+ entries = await fs.readdir(squadsDir, { withFileTypes: true });
27
+ } catch {
28
+ return [];
29
+ }
30
+
31
+ const squads = [];
32
+ for (const entry of entries) {
33
+ if (!entry.isDirectory()) continue;
34
+ const manifestPath = path.join(squadsDir, entry.name, 'squad.manifest.json');
35
+ try {
36
+ const raw = await fs.readFile(manifestPath, 'utf8');
37
+ const manifest = JSON.parse(raw);
38
+ squads.push({
39
+ slug: entry.name,
40
+ name: manifest.name || entry.name,
41
+ mode: manifest.mode || 'mixed',
42
+ goal: manifest.goal || '',
43
+ status: manifest.status || 'active',
44
+ executorCount: (manifest.executors || []).length,
45
+ manifest
46
+ });
47
+ } catch {
48
+ squads.push({
49
+ slug: entry.name,
50
+ name: entry.name,
51
+ mode: 'unknown',
52
+ goal: '',
53
+ status: 'unknown',
54
+ executorCount: 0,
55
+ manifest: null
56
+ });
57
+ }
58
+ }
59
+ return squads;
60
+ }
61
+
62
+ function detectPanels(manifest) {
63
+ const panels = ['overview', 'content', 'learnings', 'logs'];
64
+
65
+ if (manifest) {
66
+ if (manifest.mode === 'content') panels.push('content-preview');
67
+ if (manifest.mode === 'software') panels.push('tasks');
68
+
69
+ const hasWebhooks = manifest.outputStrategy &&
70
+ manifest.outputStrategy.delivery &&
71
+ Array.isArray(manifest.outputStrategy.delivery.webhooks) &&
72
+ manifest.outputStrategy.delivery.webhooks.length > 0;
73
+ const hasMcps = Array.isArray(manifest.mcps) && manifest.mcps.length > 0;
74
+ if (hasWebhooks || hasMcps) panels.push('integrations');
75
+
76
+ const channelMcps = (manifest.mcps || []).filter(function (m) {
77
+ return ['whatsapp', 'telegram', 'sms', 'voice'].some(function (ch) {
78
+ return (m.slug || '').includes(ch);
79
+ });
80
+ });
81
+ if (channelMcps.length > 0) panels.push('channels');
82
+ }
83
+
84
+ panels.push('metrics');
85
+ panels.push('processes');
86
+ return panels;
87
+ }
88
+
89
+ function loadSquadData(db, squadSlug) {
90
+ const overview = getSquadOverview(db, squadSlug);
91
+ const content = getRecentContent(db, squadSlug);
92
+ const learnings = getLearnings(db, squadSlug);
93
+ const deliveries = getRecentDeliveries(db, squadSlug);
94
+ const events = getRecentEvents(db, squadSlug);
95
+ const customMetrics = getSquadMetrics(db, squadSlug);
96
+
97
+ return {
98
+ overview,
99
+ content,
100
+ learnings,
101
+ deliveries,
102
+ events,
103
+ customMetrics,
104
+ pipelineInfo: overview.pipelineInfo,
105
+ metrics: {
106
+ content_items: overview.contentItems,
107
+ sessions: overview.sessions,
108
+ learnings: overview.learnings,
109
+ delivery_rate: overview.deliveryRate
110
+ }
111
+ };
112
+ }
113
+
114
+ /**
115
+ * GET /api/squads/:slug/context
116
+ * Returns context usage with warning levels for all agents in a squad.
117
+ */
118
+ async function getContextData(projectDir, squadSlug, agentSlug) {
119
+ return getContextUsage(projectDir, squadSlug, agentSlug || null);
120
+ }
121
+
122
+ /**
123
+ * GET /api/squads/:slug/tokens?breakdown=true
124
+ * Returns token usage with cost estimates and waste flags.
125
+ */
126
+ async function getTokenData(projectDir, squadSlug, breakdown) {
127
+ return getTokenUsage(projectDir, squadSlug, breakdown);
128
+ }
129
+
130
+ /**
131
+ * GET /api/processes[?squad=:slug]
132
+ * Returns active agent processes across all squads (or filtered by squad).
133
+ */
134
+ async function getProcesses(projectDir, squadSlug) {
135
+ return getActiveProcesses(projectDir, squadSlug || null);
136
+ }
137
+
138
+ /**
139
+ * POST /api/processes/:pid/stop
140
+ * Sends SIGTERM to the process and removes its process file.
141
+ */
142
+ async function stopProcessById(projectDir, pid) {
143
+ return stopProcess(projectDir, pid);
144
+ }
145
+
146
+ /**
147
+ * GET /api/tasks/:id/hunks
148
+ * Returns hunk review state for a task.
149
+ * Requires squadSlug and optionally diff (to init on first call).
150
+ */
151
+ async function getTaskHunks(projectDir, squadSlug, taskId, diff) {
152
+ return getHunks(projectDir, squadSlug, taskId, diff || null);
153
+ }
154
+
155
+ /**
156
+ * POST /api/tasks/:id/hunks/:hunkId/approve
157
+ */
158
+ async function approveHunk(projectDir, squadSlug, taskId, hunkId) {
159
+ return updateHunk(projectDir, squadSlug, taskId, hunkId, HUNK_STATES.APPROVED, null);
160
+ }
161
+
162
+ /**
163
+ * POST /api/tasks/:id/hunks/:hunkId/reject
164
+ */
165
+ async function rejectHunk(projectDir, squadSlug, taskId, hunkId, comment) {
166
+ return updateHunk(projectDir, squadSlug, taskId, hunkId, HUNK_STATES.REJECTED, comment || null);
167
+ }
168
+
169
+ /**
170
+ * POST /api/tasks/:id/hunks/:hunkId/comment
171
+ */
172
+ async function commentHunk(projectDir, squadSlug, taskId, hunkId, comment) {
173
+ return updateHunk(projectDir, squadSlug, taskId, hunkId, HUNK_STATES.REVISED, comment || null);
174
+ }
175
+
176
+ /**
177
+ * GET /api/squads/:slug/agents/:agent/recovery
178
+ * Returns the recovery-context.md for an agent (generates if missing).
179
+ */
180
+ async function getAgentRecovery(projectDir, squadSlug, agentSlug) {
181
+ // Try to read existing first
182
+ const existing = await readRecovery(projectDir, squadSlug);
183
+ if (existing) return { ok: true, content: existing, squadSlug, agentSlug };
184
+ // Generate on demand
185
+ const result = await generateRecovery(projectDir, squadSlug, agentSlug);
186
+ if (!result.ok) return result;
187
+ const content = await readRecovery(projectDir, squadSlug);
188
+ return { ok: true, content, squadSlug, agentSlug, tokens: result.tokens };
189
+ }
190
+
191
+ /**
192
+ * GET /api/squads/:slug/tasks/:taskId/logs
193
+ * Returns all session logs for a task, sorted oldest-first.
194
+ */
195
+ async function getTaskLogs(projectDir, squadSlug, taskId) {
196
+ return getLogsForTask(projectDir, squadSlug, taskId);
197
+ }
198
+
199
+ /**
200
+ * GET /api/squads/:slug/tasks/:taskId/logs/:sessionId
201
+ * Returns a single session log by sessionId.
202
+ */
203
+ async function getTaskSessionLog(projectDir, squadSlug, taskId, sessionId) {
204
+ return getSessionLog(projectDir, squadSlug, taskId, sessionId);
205
+ }
206
+
207
+ module.exports = {
208
+ loadSquadList,
209
+ detectPanels,
210
+ loadSquadData,
211
+ getContextData,
212
+ getTokenData,
213
+ getProcesses,
214
+ stopProcessById,
215
+ getTaskHunks,
216
+ approveHunk,
217
+ rejectHunk,
218
+ commentHunk,
219
+ getAgentRecovery,
220
+ getReviewProgress,
221
+ getTaskLogs,
222
+ getTaskSessionLog
223
+ };