@a1hvdy/cc-openclaw 0.3.2

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 (491) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +207 -0
  3. package/configs/.gitkeep +0 -0
  4. package/configs/council-reviewer-prompt.md +82 -0
  5. package/configs/council-system-prompt.md +141 -0
  6. package/dist/scripts/bench/ab-harness.d.ts +58 -0
  7. package/dist/scripts/bench/ab-harness.d.ts.map +1 -0
  8. package/dist/scripts/bench/ab-harness.js +78 -0
  9. package/dist/scripts/bench/ab-harness.js.map +1 -0
  10. package/dist/src/channels/adapter.d.ts +103 -0
  11. package/dist/src/channels/adapter.d.ts.map +1 -0
  12. package/dist/src/channels/adapter.js +38 -0
  13. package/dist/src/channels/adapter.js.map +1 -0
  14. package/dist/src/channels/telegram/completion-summary.d.ts +22 -0
  15. package/dist/src/channels/telegram/completion-summary.d.ts.map +1 -0
  16. package/dist/src/channels/telegram/completion-summary.js +186 -0
  17. package/dist/src/channels/telegram/completion-summary.js.map +1 -0
  18. package/dist/src/channels/telegram/error-renderer.d.ts +30 -0
  19. package/dist/src/channels/telegram/error-renderer.d.ts.map +1 -0
  20. package/dist/src/channels/telegram/error-renderer.js +133 -0
  21. package/dist/src/channels/telegram/error-renderer.js.map +1 -0
  22. package/dist/src/channels/telegram/event-reducer.d.ts +34 -0
  23. package/dist/src/channels/telegram/event-reducer.d.ts.map +1 -0
  24. package/dist/src/channels/telegram/event-reducer.js +579 -0
  25. package/dist/src/channels/telegram/event-reducer.js.map +1 -0
  26. package/dist/src/channels/telegram/index.d.ts +14 -0
  27. package/dist/src/channels/telegram/index.d.ts.map +1 -0
  28. package/dist/src/channels/telegram/index.js +14 -0
  29. package/dist/src/channels/telegram/index.js.map +1 -0
  30. package/dist/src/channels/telegram/injector.d.ts +54 -0
  31. package/dist/src/channels/telegram/injector.d.ts.map +1 -0
  32. package/dist/src/channels/telegram/injector.js +200 -0
  33. package/dist/src/channels/telegram/injector.js.map +1 -0
  34. package/dist/src/channels/telegram/live-card.d.ts +230 -0
  35. package/dist/src/channels/telegram/live-card.d.ts.map +1 -0
  36. package/dist/src/channels/telegram/live-card.js +916 -0
  37. package/dist/src/channels/telegram/live-card.js.map +1 -0
  38. package/dist/src/channels/telegram/state-machine.d.ts +23 -0
  39. package/dist/src/channels/telegram/state-machine.d.ts.map +1 -0
  40. package/dist/src/channels/telegram/state-machine.js +72 -0
  41. package/dist/src/channels/telegram/state-machine.js.map +1 -0
  42. package/dist/src/channels/telegram/tool-tracker.d.ts +147 -0
  43. package/dist/src/channels/telegram/tool-tracker.d.ts.map +1 -0
  44. package/dist/src/channels/telegram/tool-tracker.js +520 -0
  45. package/dist/src/channels/telegram/tool-tracker.js.map +1 -0
  46. package/dist/src/circuit-breaker.d.ts +22 -0
  47. package/dist/src/circuit-breaker.d.ts.map +1 -0
  48. package/dist/src/circuit-breaker.js +47 -0
  49. package/dist/src/circuit-breaker.js.map +1 -0
  50. package/dist/src/command-router/cc-handler.d.ts +67 -0
  51. package/dist/src/command-router/cc-handler.d.ts.map +1 -0
  52. package/dist/src/command-router/cc-handler.js +980 -0
  53. package/dist/src/command-router/cc-handler.js.map +1 -0
  54. package/dist/src/command-router/index.d.ts +3 -0
  55. package/dist/src/command-router/index.d.ts.map +1 -0
  56. package/dist/src/command-router/index.js +2 -0
  57. package/dist/src/command-router/index.js.map +1 -0
  58. package/dist/src/constants.d.ts +132 -0
  59. package/dist/src/constants.d.ts.map +1 -0
  60. package/dist/src/constants.js +140 -0
  61. package/dist/src/constants.js.map +1 -0
  62. package/dist/src/council/consensus.d.ts +21 -0
  63. package/dist/src/council/consensus.d.ts.map +1 -0
  64. package/dist/src/council/consensus.js +52 -0
  65. package/dist/src/council/consensus.js.map +1 -0
  66. package/dist/src/council/council.d.ts +68 -0
  67. package/dist/src/council/council.d.ts.map +1 -0
  68. package/dist/src/council/council.js +914 -0
  69. package/dist/src/council/council.js.map +1 -0
  70. package/dist/src/council/index.d.ts +3 -0
  71. package/dist/src/council/index.d.ts.map +1 -0
  72. package/dist/src/council/index.js +3 -0
  73. package/dist/src/council/index.js.map +1 -0
  74. package/dist/src/engines/base-oneshot-session.d.ts +88 -0
  75. package/dist/src/engines/base-oneshot-session.d.ts.map +1 -0
  76. package/dist/src/engines/base-oneshot-session.js +228 -0
  77. package/dist/src/engines/base-oneshot-session.js.map +1 -0
  78. package/dist/src/engines/index.d.ts +7 -0
  79. package/dist/src/engines/index.d.ts.map +1 -0
  80. package/dist/src/engines/index.js +7 -0
  81. package/dist/src/engines/index.js.map +1 -0
  82. package/dist/src/engines/persistent-codex-session.d.ts +17 -0
  83. package/dist/src/engines/persistent-codex-session.d.ts.map +1 -0
  84. package/dist/src/engines/persistent-codex-session.js +106 -0
  85. package/dist/src/engines/persistent-codex-session.js.map +1 -0
  86. package/dist/src/engines/persistent-cursor-session.d.ts +22 -0
  87. package/dist/src/engines/persistent-cursor-session.d.ts.map +1 -0
  88. package/dist/src/engines/persistent-cursor-session.js +242 -0
  89. package/dist/src/engines/persistent-cursor-session.js.map +1 -0
  90. package/dist/src/engines/persistent-custom-session.d.ts +79 -0
  91. package/dist/src/engines/persistent-custom-session.d.ts.map +1 -0
  92. package/dist/src/engines/persistent-custom-session.js +939 -0
  93. package/dist/src/engines/persistent-custom-session.js.map +1 -0
  94. package/dist/src/engines/persistent-gemini-session.d.ts +22 -0
  95. package/dist/src/engines/persistent-gemini-session.d.ts.map +1 -0
  96. package/dist/src/engines/persistent-gemini-session.js +217 -0
  97. package/dist/src/engines/persistent-gemini-session.js.map +1 -0
  98. package/dist/src/engines/persistent-session.d.ts +77 -0
  99. package/dist/src/engines/persistent-session.d.ts.map +1 -0
  100. package/dist/src/engines/persistent-session.js +730 -0
  101. package/dist/src/engines/persistent-session.js.map +1 -0
  102. package/dist/src/health/handler.d.ts +40 -0
  103. package/dist/src/health/handler.d.ts.map +1 -0
  104. package/dist/src/health/handler.js +70 -0
  105. package/dist/src/health/handler.js.map +1 -0
  106. package/dist/src/health/index.d.ts +2 -0
  107. package/dist/src/health/index.d.ts.map +1 -0
  108. package/dist/src/health/index.js +2 -0
  109. package/dist/src/health/index.js.map +1 -0
  110. package/dist/src/index.d.ts +49 -0
  111. package/dist/src/index.d.ts.map +1 -0
  112. package/dist/src/index.js +84 -0
  113. package/dist/src/index.js.map +1 -0
  114. package/dist/src/lib/auto-recovery.d.ts +45 -0
  115. package/dist/src/lib/auto-recovery.d.ts.map +1 -0
  116. package/dist/src/lib/auto-recovery.js +217 -0
  117. package/dist/src/lib/auto-recovery.js.map +1 -0
  118. package/dist/src/lib/cache-parity.d.ts +39 -0
  119. package/dist/src/lib/cache-parity.d.ts.map +1 -0
  120. package/dist/src/lib/cache-parity.js +92 -0
  121. package/dist/src/lib/cache-parity.js.map +1 -0
  122. package/dist/src/lib/circuit-breaker.d.ts +22 -0
  123. package/dist/src/lib/circuit-breaker.d.ts.map +1 -0
  124. package/dist/src/lib/circuit-breaker.js +47 -0
  125. package/dist/src/lib/circuit-breaker.js.map +1 -0
  126. package/dist/src/lib/config.d.ts +74 -0
  127. package/dist/src/lib/config.d.ts.map +1 -0
  128. package/dist/src/lib/config.js +244 -0
  129. package/dist/src/lib/config.js.map +1 -0
  130. package/dist/src/lib/drift-detector.d.ts +47 -0
  131. package/dist/src/lib/drift-detector.d.ts.map +1 -0
  132. package/dist/src/lib/drift-detector.js +192 -0
  133. package/dist/src/lib/drift-detector.js.map +1 -0
  134. package/dist/src/lib/error-formatter.d.ts +78 -0
  135. package/dist/src/lib/error-formatter.d.ts.map +1 -0
  136. package/dist/src/lib/error-formatter.js +149 -0
  137. package/dist/src/lib/error-formatter.js.map +1 -0
  138. package/dist/src/lib/heartbeat-workaround.d.ts +45 -0
  139. package/dist/src/lib/heartbeat-workaround.d.ts.map +1 -0
  140. package/dist/src/lib/heartbeat-workaround.js +61 -0
  141. package/dist/src/lib/heartbeat-workaround.js.map +1 -0
  142. package/dist/src/lib/index.d.ts +8 -0
  143. package/dist/src/lib/index.d.ts.map +1 -0
  144. package/dist/src/lib/index.js +8 -0
  145. package/dist/src/lib/index.js.map +1 -0
  146. package/dist/src/lib/register-guard.d.ts +49 -0
  147. package/dist/src/lib/register-guard.d.ts.map +1 -0
  148. package/dist/src/lib/register-guard.js +73 -0
  149. package/dist/src/lib/register-guard.js.map +1 -0
  150. package/dist/src/lib/route-flag.d.ts +50 -0
  151. package/dist/src/lib/route-flag.d.ts.map +1 -0
  152. package/dist/src/lib/route-flag.js +52 -0
  153. package/dist/src/lib/route-flag.js.map +1 -0
  154. package/dist/src/lib/sysprompt-strip.d.ts +54 -0
  155. package/dist/src/lib/sysprompt-strip.d.ts.map +1 -0
  156. package/dist/src/lib/sysprompt-strip.js +75 -0
  157. package/dist/src/lib/sysprompt-strip.js.map +1 -0
  158. package/dist/src/lib/telemetry.d.ts +39 -0
  159. package/dist/src/lib/telemetry.d.ts.map +1 -0
  160. package/dist/src/lib/telemetry.js +73 -0
  161. package/dist/src/lib/telemetry.js.map +1 -0
  162. package/dist/src/lib/test-mode.d.ts +27 -0
  163. package/dist/src/lib/test-mode.d.ts.map +1 -0
  164. package/dist/src/lib/test-mode.js +38 -0
  165. package/dist/src/lib/test-mode.js.map +1 -0
  166. package/dist/src/lib/vendor-paths.d.ts +15 -0
  167. package/dist/src/lib/vendor-paths.d.ts.map +1 -0
  168. package/dist/src/lib/vendor-paths.js +32 -0
  169. package/dist/src/lib/vendor-paths.js.map +1 -0
  170. package/dist/src/logger.d.ts +17 -0
  171. package/dist/src/logger.d.ts.map +1 -0
  172. package/dist/src/logger.js +46 -0
  173. package/dist/src/logger.js.map +1 -0
  174. package/dist/src/mcp/bridge.d.ts +22 -0
  175. package/dist/src/mcp/bridge.d.ts.map +1 -0
  176. package/dist/src/mcp/bridge.js +78 -0
  177. package/dist/src/mcp/bridge.js.map +1 -0
  178. package/dist/src/mcp/index.d.ts +3 -0
  179. package/dist/src/mcp/index.d.ts.map +1 -0
  180. package/dist/src/mcp/index.js +2 -0
  181. package/dist/src/mcp/index.js.map +1 -0
  182. package/dist/src/models.d.ts +70 -0
  183. package/dist/src/models.d.ts.map +1 -0
  184. package/dist/src/models.js +289 -0
  185. package/dist/src/models.js.map +1 -0
  186. package/dist/src/openai-compat/cli-stream-parser.d.ts +135 -0
  187. package/dist/src/openai-compat/cli-stream-parser.d.ts.map +1 -0
  188. package/dist/src/openai-compat/cli-stream-parser.js +195 -0
  189. package/dist/src/openai-compat/cli-stream-parser.js.map +1 -0
  190. package/dist/src/openai-compat/index.d.ts +2 -0
  191. package/dist/src/openai-compat/index.d.ts.map +1 -0
  192. package/dist/src/openai-compat/index.js +2 -0
  193. package/dist/src/openai-compat/index.js.map +1 -0
  194. package/dist/src/openai-compat/openai-compat.d.ts +281 -0
  195. package/dist/src/openai-compat/openai-compat.d.ts.map +1 -0
  196. package/dist/src/openai-compat/openai-compat.js +939 -0
  197. package/dist/src/openai-compat/openai-compat.js.map +1 -0
  198. package/dist/src/openai-compat/skill-resolver.d.ts +36 -0
  199. package/dist/src/openai-compat/skill-resolver.d.ts.map +1 -0
  200. package/dist/src/openai-compat/skill-resolver.js +134 -0
  201. package/dist/src/openai-compat/skill-resolver.js.map +1 -0
  202. package/dist/src/openai-compat/sse-translator.d.ts +32 -0
  203. package/dist/src/openai-compat/sse-translator.d.ts.map +1 -0
  204. package/dist/src/openai-compat/sse-translator.js +155 -0
  205. package/dist/src/openai-compat/sse-translator.js.map +1 -0
  206. package/dist/src/proxy/anthropic-adapter.d.ts +137 -0
  207. package/dist/src/proxy/anthropic-adapter.d.ts.map +1 -0
  208. package/dist/src/proxy/anthropic-adapter.js +392 -0
  209. package/dist/src/proxy/anthropic-adapter.js.map +1 -0
  210. package/dist/src/proxy/handler.d.ts +40 -0
  211. package/dist/src/proxy/handler.d.ts.map +1 -0
  212. package/dist/src/proxy/handler.js +378 -0
  213. package/dist/src/proxy/handler.js.map +1 -0
  214. package/dist/src/proxy/index.d.ts +5 -0
  215. package/dist/src/proxy/index.d.ts.map +1 -0
  216. package/dist/src/proxy/index.js +5 -0
  217. package/dist/src/proxy/index.js.map +1 -0
  218. package/dist/src/proxy/schema-cleaner.d.ts +12 -0
  219. package/dist/src/proxy/schema-cleaner.d.ts.map +1 -0
  220. package/dist/src/proxy/schema-cleaner.js +34 -0
  221. package/dist/src/proxy/schema-cleaner.js.map +1 -0
  222. package/dist/src/proxy/thought-cache.d.ts +20 -0
  223. package/dist/src/proxy/thought-cache.d.ts.map +1 -0
  224. package/dist/src/proxy/thought-cache.js +53 -0
  225. package/dist/src/proxy/thought-cache.js.map +1 -0
  226. package/dist/src/session/embedded-server.d.ts +26 -0
  227. package/dist/src/session/embedded-server.d.ts.map +1 -0
  228. package/dist/src/session/embedded-server.js +367 -0
  229. package/dist/src/session/embedded-server.js.map +1 -0
  230. package/dist/src/session/inbox-manager.d.ts +39 -0
  231. package/dist/src/session/inbox-manager.d.ts.map +1 -0
  232. package/dist/src/session/inbox-manager.js +111 -0
  233. package/dist/src/session/inbox-manager.js.map +1 -0
  234. package/dist/src/session/index.d.ts +4 -0
  235. package/dist/src/session/index.d.ts.map +1 -0
  236. package/dist/src/session/index.js +4 -0
  237. package/dist/src/session/index.js.map +1 -0
  238. package/dist/src/session/session-manager.d.ts +212 -0
  239. package/dist/src/session/session-manager.d.ts.map +1 -0
  240. package/dist/src/session/session-manager.js +1351 -0
  241. package/dist/src/session/session-manager.js.map +1 -0
  242. package/dist/src/session-bootstrap/cwd-patch.d.ts +51 -0
  243. package/dist/src/session-bootstrap/cwd-patch.d.ts.map +1 -0
  244. package/dist/src/session-bootstrap/cwd-patch.js +955 -0
  245. package/dist/src/session-bootstrap/cwd-patch.js.map +1 -0
  246. package/dist/src/session-bootstrap/index.d.ts +4 -0
  247. package/dist/src/session-bootstrap/index.d.ts.map +1 -0
  248. package/dist/src/session-bootstrap/index.js +4 -0
  249. package/dist/src/session-bootstrap/index.js.map +1 -0
  250. package/dist/src/session-bootstrap/sysprompt-strip.d.ts +26 -0
  251. package/dist/src/session-bootstrap/sysprompt-strip.d.ts.map +1 -0
  252. package/dist/src/session-bootstrap/sysprompt-strip.js +57 -0
  253. package/dist/src/session-bootstrap/sysprompt-strip.js.map +1 -0
  254. package/dist/src/session-bootstrap/think-conflict-resolver.d.ts +33 -0
  255. package/dist/src/session-bootstrap/think-conflict-resolver.d.ts.map +1 -0
  256. package/dist/src/session-bootstrap/think-conflict-resolver.js +234 -0
  257. package/dist/src/session-bootstrap/think-conflict-resolver.js.map +1 -0
  258. package/dist/src/types.d.ts +489 -0
  259. package/dist/src/types.d.ts.map +1 -0
  260. package/dist/src/types.js +8 -0
  261. package/dist/src/types.js.map +1 -0
  262. package/dist/src/validation.d.ts +32 -0
  263. package/dist/src/validation.d.ts.map +1 -0
  264. package/dist/src/validation.js +104 -0
  265. package/dist/src/validation.js.map +1 -0
  266. package/dist/tests/_helpers/subprocess-mock.d.ts +35 -0
  267. package/dist/tests/_helpers/subprocess-mock.d.ts.map +1 -0
  268. package/dist/tests/_helpers/subprocess-mock.js +136 -0
  269. package/dist/tests/_helpers/subprocess-mock.js.map +1 -0
  270. package/dist/tests/auto-recovery.test.d.ts +2 -0
  271. package/dist/tests/auto-recovery.test.d.ts.map +1 -0
  272. package/dist/tests/auto-recovery.test.js +189 -0
  273. package/dist/tests/auto-recovery.test.js.map +1 -0
  274. package/dist/tests/bench-harness.test.d.ts +2 -0
  275. package/dist/tests/bench-harness.test.d.ts.map +1 -0
  276. package/dist/tests/bench-harness.test.js +21 -0
  277. package/dist/tests/bench-harness.test.js.map +1 -0
  278. package/dist/tests/cache-parity.test.d.ts +2 -0
  279. package/dist/tests/cache-parity.test.d.ts.map +1 -0
  280. package/dist/tests/cache-parity.test.js +401 -0
  281. package/dist/tests/cache-parity.test.js.map +1 -0
  282. package/dist/tests/command-router.test.d.ts +2 -0
  283. package/dist/tests/command-router.test.d.ts.map +1 -0
  284. package/dist/tests/command-router.test.js +60 -0
  285. package/dist/tests/command-router.test.js.map +1 -0
  286. package/dist/tests/council.test.d.ts +2 -0
  287. package/dist/tests/council.test.d.ts.map +1 -0
  288. package/dist/tests/council.test.js +20 -0
  289. package/dist/tests/council.test.js.map +1 -0
  290. package/dist/tests/drift-detector.test.d.ts +2 -0
  291. package/dist/tests/drift-detector.test.d.ts.map +1 -0
  292. package/dist/tests/drift-detector.test.js +268 -0
  293. package/dist/tests/drift-detector.test.js.map +1 -0
  294. package/dist/tests/eager-bootstrap-gating.test.d.ts +9 -0
  295. package/dist/tests/eager-bootstrap-gating.test.d.ts.map +1 -0
  296. package/dist/tests/eager-bootstrap-gating.test.js +97 -0
  297. package/dist/tests/eager-bootstrap-gating.test.js.map +1 -0
  298. package/dist/tests/engines.test.d.ts +2 -0
  299. package/dist/tests/engines.test.d.ts.map +1 -0
  300. package/dist/tests/engines.test.js +8 -0
  301. package/dist/tests/engines.test.js.map +1 -0
  302. package/dist/tests/error-formatter.test.d.ts +2 -0
  303. package/dist/tests/error-formatter.test.d.ts.map +1 -0
  304. package/dist/tests/error-formatter.test.js +220 -0
  305. package/dist/tests/error-formatter.test.js.map +1 -0
  306. package/dist/tests/health.test.d.ts +2 -0
  307. package/dist/tests/health.test.d.ts.map +1 -0
  308. package/dist/tests/health.test.js +110 -0
  309. package/dist/tests/health.test.js.map +1 -0
  310. package/dist/tests/heartbeat-workaround.test.d.ts +2 -0
  311. package/dist/tests/heartbeat-workaround.test.d.ts.map +1 -0
  312. package/dist/tests/heartbeat-workaround.test.js +90 -0
  313. package/dist/tests/heartbeat-workaround.test.js.map +1 -0
  314. package/dist/tests/index.test.d.ts +2 -0
  315. package/dist/tests/index.test.d.ts.map +1 -0
  316. package/dist/tests/index.test.js +7 -0
  317. package/dist/tests/index.test.js.map +1 -0
  318. package/dist/tests/lib-sysprompt-strip.test.d.ts +2 -0
  319. package/dist/tests/lib-sysprompt-strip.test.d.ts.map +1 -0
  320. package/dist/tests/lib-sysprompt-strip.test.js +145 -0
  321. package/dist/tests/lib-sysprompt-strip.test.js.map +1 -0
  322. package/dist/tests/listener-activation.test.d.ts +2 -0
  323. package/dist/tests/listener-activation.test.d.ts.map +1 -0
  324. package/dist/tests/listener-activation.test.js +87 -0
  325. package/dist/tests/listener-activation.test.js.map +1 -0
  326. package/dist/tests/mcp-bridge.test.d.ts +2 -0
  327. package/dist/tests/mcp-bridge.test.d.ts.map +1 -0
  328. package/dist/tests/mcp-bridge.test.js +137 -0
  329. package/dist/tests/mcp-bridge.test.js.map +1 -0
  330. package/dist/tests/openai-compat.test.d.ts +2 -0
  331. package/dist/tests/openai-compat.test.d.ts.map +1 -0
  332. package/dist/tests/openai-compat.test.js +8 -0
  333. package/dist/tests/openai-compat.test.js.map +1 -0
  334. package/dist/tests/proxy-heartbeat-integration.test.d.ts +15 -0
  335. package/dist/tests/proxy-heartbeat-integration.test.d.ts.map +1 -0
  336. package/dist/tests/proxy-heartbeat-integration.test.js +122 -0
  337. package/dist/tests/proxy-heartbeat-integration.test.js.map +1 -0
  338. package/dist/tests/proxy.test.d.ts +2 -0
  339. package/dist/tests/proxy.test.d.ts.map +1 -0
  340. package/dist/tests/proxy.test.js +8 -0
  341. package/dist/tests/proxy.test.js.map +1 -0
  342. package/dist/tests/register-guard-stacking.test.d.ts +2 -0
  343. package/dist/tests/register-guard-stacking.test.d.ts.map +1 -0
  344. package/dist/tests/register-guard-stacking.test.js +61 -0
  345. package/dist/tests/register-guard-stacking.test.js.map +1 -0
  346. package/dist/tests/register-guard.test.d.ts +2 -0
  347. package/dist/tests/register-guard.test.d.ts.map +1 -0
  348. package/dist/tests/register-guard.test.js +129 -0
  349. package/dist/tests/register-guard.test.js.map +1 -0
  350. package/dist/tests/route-flag-rollback.test.d.ts +2 -0
  351. package/dist/tests/route-flag-rollback.test.d.ts.map +1 -0
  352. package/dist/tests/route-flag-rollback.test.js +70 -0
  353. package/dist/tests/route-flag-rollback.test.js.map +1 -0
  354. package/dist/tests/route-flag.test.d.ts +2 -0
  355. package/dist/tests/route-flag.test.d.ts.map +1 -0
  356. package/dist/tests/route-flag.test.js +101 -0
  357. package/dist/tests/route-flag.test.js.map +1 -0
  358. package/dist/tests/session-bootstrap.test.d.ts +2 -0
  359. package/dist/tests/session-bootstrap.test.d.ts.map +1 -0
  360. package/dist/tests/session-bootstrap.test.js +183 -0
  361. package/dist/tests/session-bootstrap.test.js.map +1 -0
  362. package/dist/tests/session.test.d.ts +2 -0
  363. package/dist/tests/session.test.d.ts.map +1 -0
  364. package/dist/tests/session.test.js +17 -0
  365. package/dist/tests/session.test.js.map +1 -0
  366. package/dist/tests/state-machine.test.d.ts +2 -0
  367. package/dist/tests/state-machine.test.d.ts.map +1 -0
  368. package/dist/tests/state-machine.test.js +133 -0
  369. package/dist/tests/state-machine.test.js.map +1 -0
  370. package/dist/tests/streaming/cli-stream-parser.test.d.ts +2 -0
  371. package/dist/tests/streaming/cli-stream-parser.test.d.ts.map +1 -0
  372. package/dist/tests/streaming/cli-stream-parser.test.js +233 -0
  373. package/dist/tests/streaming/cli-stream-parser.test.js.map +1 -0
  374. package/dist/tests/streaming/feature-flag.test.d.ts +14 -0
  375. package/dist/tests/streaming/feature-flag.test.d.ts.map +1 -0
  376. package/dist/tests/streaming/feature-flag.test.js +163 -0
  377. package/dist/tests/streaming/feature-flag.test.js.map +1 -0
  378. package/dist/tests/streaming/no-tools-prompt.test.d.ts +17 -0
  379. package/dist/tests/streaming/no-tools-prompt.test.d.ts.map +1 -0
  380. package/dist/tests/streaming/no-tools-prompt.test.js +229 -0
  381. package/dist/tests/streaming/no-tools-prompt.test.js.map +1 -0
  382. package/dist/tests/streaming/skill-plus-tools.test.d.ts +14 -0
  383. package/dist/tests/streaming/skill-plus-tools.test.d.ts.map +1 -0
  384. package/dist/tests/streaming/skill-plus-tools.test.js +234 -0
  385. package/dist/tests/streaming/skill-plus-tools.test.js.map +1 -0
  386. package/dist/tests/streaming/sse-translator.test.d.ts +2 -0
  387. package/dist/tests/streaming/sse-translator.test.d.ts.map +1 -0
  388. package/dist/tests/streaming/sse-translator.test.js +227 -0
  389. package/dist/tests/streaming/sse-translator.test.js.map +1 -0
  390. package/dist/tests/streaming/tool-result-roundtrip.test.d.ts +11 -0
  391. package/dist/tests/streaming/tool-result-roundtrip.test.d.ts.map +1 -0
  392. package/dist/tests/streaming/tool-result-roundtrip.test.js +215 -0
  393. package/dist/tests/streaming/tool-result-roundtrip.test.js.map +1 -0
  394. package/dist/tests/streaming/tool-use-translation.test.d.ts +10 -0
  395. package/dist/tests/streaming/tool-use-translation.test.d.ts.map +1 -0
  396. package/dist/tests/streaming/tool-use-translation.test.js +251 -0
  397. package/dist/tests/streaming/tool-use-translation.test.js.map +1 -0
  398. package/dist/tests/telegram-bridge.test.d.ts +2 -0
  399. package/dist/tests/telegram-bridge.test.d.ts.map +1 -0
  400. package/dist/tests/telegram-bridge.test.js +17 -0
  401. package/dist/tests/telegram-bridge.test.js.map +1 -0
  402. package/dist/tests/telegram-injector.test.d.ts +2 -0
  403. package/dist/tests/telegram-injector.test.d.ts.map +1 -0
  404. package/dist/tests/telegram-injector.test.js +74 -0
  405. package/dist/tests/telegram-injector.test.js.map +1 -0
  406. package/dist/tests/telemetry.test.d.ts +2 -0
  407. package/dist/tests/telemetry.test.d.ts.map +1 -0
  408. package/dist/tests/telemetry.test.js +405 -0
  409. package/dist/tests/telemetry.test.js.map +1 -0
  410. package/dist/tests/test-mode.test.d.ts +2 -0
  411. package/dist/tests/test-mode.test.d.ts.map +1 -0
  412. package/dist/tests/test-mode.test.js +39 -0
  413. package/dist/tests/test-mode.test.js.map +1 -0
  414. package/mcp-config.template.json +13 -0
  415. package/mcp-tools.json +1 -0
  416. package/openclaw-mcp-bridge.cjs +152 -0
  417. package/openclaw.plugin.json +30 -0
  418. package/package.json +45 -0
  419. package/skills/.gitkeep +0 -0
  420. package/stubs/commands-status-deps.runtime.js +10 -0
  421. package/stubs/status.runtime.js +149 -0
  422. package/vendor/base-oneshot-session.d.ts +87 -0
  423. package/vendor/base-oneshot-session.js +227 -0
  424. package/vendor/base-oneshot-session.js.map +1 -0
  425. package/vendor/circuit-breaker.d.ts +21 -0
  426. package/vendor/circuit-breaker.js +47 -0
  427. package/vendor/circuit-breaker.js.map +1 -0
  428. package/vendor/consensus.d.ts +20 -0
  429. package/vendor/consensus.js +52 -0
  430. package/vendor/consensus.js.map +1 -0
  431. package/vendor/constants.d.ts +130 -0
  432. package/vendor/constants.js +139 -0
  433. package/vendor/constants.js.map +1 -0
  434. package/vendor/council.d.ts +67 -0
  435. package/vendor/council.js +913 -0
  436. package/vendor/council.js.map +1 -0
  437. package/vendor/embedded-server.d.ts +25 -0
  438. package/vendor/embedded-server.js +360 -0
  439. package/vendor/embedded-server.js.map +1 -0
  440. package/vendor/inbox-manager.d.ts +38 -0
  441. package/vendor/inbox-manager.js +111 -0
  442. package/vendor/inbox-manager.js.map +1 -0
  443. package/vendor/index.d.ts +63 -0
  444. package/vendor/index.js +705 -0
  445. package/vendor/index.js.map +1 -0
  446. package/vendor/logger.d.ts +16 -0
  447. package/vendor/logger.js +44 -0
  448. package/vendor/logger.js.map +1 -0
  449. package/vendor/models.d.ts +69 -0
  450. package/vendor/models.js +289 -0
  451. package/vendor/models.js.map +1 -0
  452. package/vendor/openai-compat.d.ts +197 -0
  453. package/vendor/openai-compat.js +721 -0
  454. package/vendor/openai-compat.js.map +1 -0
  455. package/vendor/persistent-codex-session.d.ts +16 -0
  456. package/vendor/persistent-codex-session.js +105 -0
  457. package/vendor/persistent-codex-session.js.map +1 -0
  458. package/vendor/persistent-cursor-session.d.ts +21 -0
  459. package/vendor/persistent-cursor-session.js +241 -0
  460. package/vendor/persistent-cursor-session.js.map +1 -0
  461. package/vendor/persistent-custom-session.d.ts +78 -0
  462. package/vendor/persistent-custom-session.js +937 -0
  463. package/vendor/persistent-custom-session.js.map +1 -0
  464. package/vendor/persistent-gemini-session.d.ts +21 -0
  465. package/vendor/persistent-gemini-session.js +216 -0
  466. package/vendor/persistent-gemini-session.js.map +1 -0
  467. package/vendor/persistent-session.d.ts +74 -0
  468. package/vendor/persistent-session.js +684 -0
  469. package/vendor/persistent-session.js.map +1 -0
  470. package/vendor/proxy/anthropic-adapter.d.ts +136 -0
  471. package/vendor/proxy/anthropic-adapter.js +392 -0
  472. package/vendor/proxy/anthropic-adapter.js.map +1 -0
  473. package/vendor/proxy/handler.d.ts +39 -0
  474. package/vendor/proxy/handler.js +323 -0
  475. package/vendor/proxy/handler.js.map +1 -0
  476. package/vendor/proxy/schema-cleaner.d.ts +11 -0
  477. package/vendor/proxy/schema-cleaner.js +34 -0
  478. package/vendor/proxy/schema-cleaner.js.map +1 -0
  479. package/vendor/proxy/thought-cache.d.ts +19 -0
  480. package/vendor/proxy/thought-cache.js +53 -0
  481. package/vendor/proxy/thought-cache.js.map +1 -0
  482. package/vendor/session-manager.d.ts +211 -0
  483. package/vendor/session-manager.js +1345 -0
  484. package/vendor/session-manager.js.map +1 -0
  485. package/vendor/skill-resolver.js +107 -0
  486. package/vendor/types.d.ts +466 -0
  487. package/vendor/types.js +8 -0
  488. package/vendor/types.js.map +1 -0
  489. package/vendor/validation.d.ts +31 -0
  490. package/vendor/validation.js +104 -0
  491. package/vendor/validation.js.map +1 -0
@@ -0,0 +1,914 @@
1
+ /**
2
+ * Council — Multi-agent collaboration engine
3
+ *
4
+ * Ported from three-minds and adapted to use SessionManager + ISession
5
+ * directly (no HTTP/SSE to external services).
6
+ *
7
+ * Key patterns:
8
+ * - Git worktree isolation per agent
9
+ * - Two-phase protocol: planning round → execution rounds
10
+ * - Consensus voting: all agents vote YES to complete
11
+ * - Parallel execution via Promise.allSettled
12
+ * - Engine-agnostic: agents can use Claude, Codex, or any ISession engine
13
+ */
14
+ import { randomUUID } from 'node:crypto';
15
+ import { spawn } from 'node:child_process';
16
+ import { EventEmitter } from 'node:events';
17
+ import * as fs from 'node:fs';
18
+ import * as path from 'node:path';
19
+ import { getHomeOrTmp } from '../lib/config.js';
20
+ import { parseConsensus, stripConsensusTags, hasConsensusMarker } from './consensus.js';
21
+ import { DEFAULT_AGENT_TIMEOUT_MS, MIN_TASK_LENGTH, INTER_ROUND_DELAY_MS, EMPTY_RESPONSE_MAX_RETRIES, EMPTY_RESPONSE_RETRY_DELAY_MS, MIN_COMPLETE_RESPONSE_LENGTH, FOLLOWUP_MAX_RETRIES, HISTORY_PREVIEW_CHARS, SUMMARY_PREVIEW_CHARS, SUMMARY_SHORT_CHARS, COMPACT_CONTEXT_CHARS, DEFAULT_MAX_TURNS_PER_AGENT, GIT_CMD_TIMEOUT_MS, WORKTREE_CMD_TIMEOUT_MS, FOLLOWUP_TIMEOUT_MS, GIT_LOG_DEPTH, DEFAULT_MAX_ROUNDS, } from '../constants.js';
22
+ import { createConsoleLogger } from '../logger.js';
23
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
24
+ // ─── Git Utilities ──────────────────────────────────────────────────────────
25
+ function spawnAsync(cmd, args, opts = {}) {
26
+ return new Promise((resolve, reject) => {
27
+ const child = spawn(cmd, args, {
28
+ stdio: ['pipe', 'pipe', 'pipe'],
29
+ cwd: opts.cwd,
30
+ });
31
+ let stdout = '';
32
+ let stderr = '';
33
+ child.stdout.on('data', (d) => {
34
+ stdout += d.toString();
35
+ });
36
+ child.stderr.on('data', (d) => {
37
+ stderr += d.toString();
38
+ });
39
+ const timer = opts.timeout
40
+ ? setTimeout(() => {
41
+ child.kill('SIGTERM');
42
+ reject(new Error('spawn timeout'));
43
+ }, opts.timeout)
44
+ : null;
45
+ child.on('close', (code) => {
46
+ if (timer)
47
+ clearTimeout(timer);
48
+ if (code !== 0)
49
+ reject(new Error(`${cmd} exited with code ${code}: ${stderr.trim()}`));
50
+ else
51
+ resolve({ stdout, stderr });
52
+ });
53
+ child.on('error', (err) => {
54
+ if (timer)
55
+ clearTimeout(timer);
56
+ reject(err);
57
+ });
58
+ });
59
+ }
60
+ const VALID_AGENT_NAME = /^[a-zA-Z0-9_-]+$/;
61
+ /** Best-effort cleanup of already-created worktrees when a batch creation fails */
62
+ async function cleanupCreatedWorktrees(worktreeMap, projectDir, logger) {
63
+ const log = logger || createConsoleLogger('Council');
64
+ for (const [createdAgent, createdPath] of worktreeMap) {
65
+ await spawnAsync('git', ['-C', projectDir, 'worktree', 'remove', '--force', createdPath], {
66
+ timeout: GIT_CMD_TIMEOUT_MS,
67
+ }).catch((err) => {
68
+ log.error(`Failed to cleanup worktree for ${createdAgent}:`, err.message);
69
+ });
70
+ }
71
+ }
72
+ /** Set up git worktrees — one isolated directory per agent */
73
+ async function setupWorktrees(projectDir, agents, logger) {
74
+ const log = logger || createConsoleLogger('Council');
75
+ const worktreeMap = new Map();
76
+ // Validate agent names before using them in git branch names
77
+ for (const agent of agents) {
78
+ if (!VALID_AGENT_NAME.test(agent.name)) {
79
+ throw new Error(`Invalid agent name '${agent.name}': must match /^[a-zA-Z0-9_-]+$/`);
80
+ }
81
+ }
82
+ if (!fs.existsSync(projectDir)) {
83
+ fs.mkdirSync(projectDir, { recursive: true });
84
+ }
85
+ // Ensure git repo
86
+ const isGit = await spawnAsync('git', ['-C', projectDir, 'rev-parse', '--git-dir'], { timeout: GIT_CMD_TIMEOUT_MS })
87
+ .then(() => true)
88
+ .catch(() => false);
89
+ if (!isGit) {
90
+ await spawnAsync('git', ['-C', projectDir, 'init'], { timeout: GIT_CMD_TIMEOUT_MS });
91
+ }
92
+ // Git user config
93
+ await spawnAsync('git', ['-C', projectDir, 'config', '--local', 'user.email', 'council@openclaw'], {
94
+ timeout: GIT_CMD_TIMEOUT_MS,
95
+ }).catch((err) => {
96
+ log.error('Failed to set git user.email:', err.message);
97
+ });
98
+ await spawnAsync('git', ['-C', projectDir, 'config', '--local', 'user.name', 'Council'], {
99
+ timeout: GIT_CMD_TIMEOUT_MS,
100
+ }).catch((err) => {
101
+ log.error('Failed to set git user.name:', err.message);
102
+ });
103
+ // Ensure at least one commit
104
+ const hasCommit = await spawnAsync('git', ['-C', projectDir, 'rev-parse', 'HEAD'], { timeout: GIT_CMD_TIMEOUT_MS })
105
+ .then(() => true)
106
+ .catch(() => false);
107
+ if (!hasCommit) {
108
+ await spawnAsync('git', ['-C', projectDir, 'add', '-A'], { timeout: GIT_CMD_TIMEOUT_MS }).catch((err) => {
109
+ log.error('Failed to git add:', err.message);
110
+ });
111
+ await spawnAsync('git', ['-C', projectDir, 'commit', '--allow-empty', '-m', 'council: initial'], {
112
+ timeout: GIT_CMD_TIMEOUT_MS,
113
+ });
114
+ }
115
+ // Create worktree per agent
116
+ for (const agent of agents) {
117
+ const wtDir = path.join(projectDir, '.worktrees', agent.name);
118
+ const branch = `council/${agent.name}`;
119
+ if (fs.existsSync(wtDir)) {
120
+ const isValid = await spawnAsync('git', ['-C', wtDir, 'rev-parse', '--git-dir'], { timeout: GIT_CMD_TIMEOUT_MS })
121
+ .then(() => true)
122
+ .catch(() => false);
123
+ if (isValid) {
124
+ // Warn: hard reset discards uncommitted changes from any previous run
125
+ const dirty = await spawnAsync('git', ['-C', wtDir, 'status', '--porcelain'], { timeout: GIT_CMD_TIMEOUT_MS })
126
+ .then((r) => r.stdout.trim().length > 0)
127
+ .catch(() => false);
128
+ if (dirty) {
129
+ log.warn(`Worktree ${wtDir} has uncommitted changes — discarding via hard reset`);
130
+ }
131
+ try {
132
+ await spawnAsync('git', ['-C', wtDir, 'checkout', branch], { timeout: GIT_CMD_TIMEOUT_MS });
133
+ await spawnAsync('git', ['-C', wtDir, 'reset', '--hard', 'HEAD'], { timeout: GIT_CMD_TIMEOUT_MS });
134
+ worktreeMap.set(agent.name, wtDir);
135
+ continue;
136
+ }
137
+ catch (err) {
138
+ log.error(`Failed to reuse worktree ${wtDir} for branch ${branch}:`, err.message);
139
+ // Fall through to re-create the worktree below
140
+ }
141
+ }
142
+ await spawnAsync('git', ['-C', projectDir, 'worktree', 'remove', '--force', wtDir], {
143
+ timeout: GIT_CMD_TIMEOUT_MS,
144
+ }).catch((err) => {
145
+ log.error(`Failed to remove worktree ${wtDir}:`, err.message);
146
+ });
147
+ }
148
+ await spawnAsync('git', ['-C', projectDir, 'branch', '-D', branch], { timeout: GIT_CMD_TIMEOUT_MS }).catch((err) => {
149
+ log.error(`Failed to delete branch ${branch}:`, err.message);
150
+ });
151
+ try {
152
+ await spawnAsync('git', ['-C', projectDir, 'worktree', 'add', wtDir, '-b', branch], {
153
+ timeout: WORKTREE_CMD_TIMEOUT_MS,
154
+ });
155
+ }
156
+ catch (err) {
157
+ await cleanupCreatedWorktrees(worktreeMap, projectDir, log);
158
+ throw new Error(`Failed to create worktree for ${agent.name} at ${wtDir}: ${err.message}`);
159
+ }
160
+ if (!fs.existsSync(wtDir)) {
161
+ await cleanupCreatedWorktrees(worktreeMap, projectDir, log);
162
+ throw new Error(`Worktree directory not created: ${wtDir}`);
163
+ }
164
+ worktreeMap.set(agent.name, wtDir);
165
+ }
166
+ // Write CLAUDE.md constraints in each worktree
167
+ for (const agent of agents) {
168
+ const wtDir = worktreeMap.get(agent.name);
169
+ if (wtDir)
170
+ writeWorktreeClaudeMd(wtDir, agent.name, agent.emoji, projectDir);
171
+ }
172
+ return worktreeMap;
173
+ }
174
+ function writeWorktreeClaudeMd(wtDir, agentName, emoji, projectDir) {
175
+ const claudeDir = path.join(wtDir, '.claude');
176
+ if (!fs.existsSync(claudeDir))
177
+ fs.mkdirSync(claudeDir, { recursive: true });
178
+ const content = `# ${emoji} ${agentName}
179
+
180
+ > This file is auto-generated by the system and takes priority over all conversation context.
181
+
182
+ ## Identity
183
+
184
+ You are **${emoji} ${agentName}**.
185
+ Your working branch: \`council/${agentName}\`
186
+ Your working directory: \`${wtDir}\`
187
+
188
+ Only tasks marked \`[Claimed: council/${agentName}]\` in plan.md belong to you.
189
+
190
+ ## Workspace Boundary
191
+
192
+ Only operate within \`${wtDir}\` and \`${projectDir}\`.
193
+ Never access: \`~/\`, \`/Users/\`, \`~/.openclaw/\`, or any path outside your workspace.
194
+
195
+ ## Efficiency Rules
196
+
197
+ - Complete Round 1 within 2-3 minutes — planning only
198
+ - Empty projects: write the plan directly, no exploration needed
199
+ - One \`ls\` is enough — never scan repeatedly
200
+ `;
201
+ fs.writeFileSync(path.join(claudeDir, 'CLAUDE.md'), content);
202
+ }
203
+ // ─── Prompt Building ────────────────────────────────────────────────────────
204
+ function buildAgentPrompt(agent, task, round, previousResponses, allAgents) {
205
+ const otherAgents = allAgents.filter((a) => a.name !== agent.name);
206
+ // Build history with tail-first truncation (preserve reports and votes)
207
+ let history = '';
208
+ // Filter out empty responses so they don't pollute the collaboration history
209
+ const substantiveResponses = previousResponses.filter((resp) => {
210
+ const stripped = resp.content.replace(/^\[Agent completed[^\]]*\]\s*/i, '').trim();
211
+ return stripped.length > 0;
212
+ });
213
+ if (substantiveResponses.length > 0) {
214
+ history = '\n\n## Previous Collaboration History\n\n';
215
+ let currentRound = 0;
216
+ for (const resp of substantiveResponses) {
217
+ if (resp.round !== currentRound) {
218
+ currentRound = resp.round;
219
+ history += `### Round ${currentRound}\n\n`;
220
+ }
221
+ const clean = stripConsensusTags(resp.content);
222
+ const preview = clean.length > HISTORY_PREVIEW_CHARS ? '...' + clean.slice(-HISTORY_PREVIEW_CHARS) : clean;
223
+ history += `**${resp.agent}** (${resp.consensus ? 'YES — agree to finish' : 'NO — continue'}):\n${preview}\n\n`;
224
+ }
225
+ }
226
+ if (round === 1) {
227
+ return `# Round 1 — Planning Round
228
+
229
+ ## Task
230
+ ${task}
231
+
232
+ ## Your Partners
233
+ ${otherAgents.map((a) => `- ${a.emoji} ${a.name}`).join('\n')}
234
+ ${history}
235
+ ## Rules: Planning Only — No Code
236
+
237
+ This is Round 1, a **pure planning round**. All members work independently in parallel to create plan.md.
238
+
239
+ **What you must do (in order, complete quickly):**
240
+ 1. \`git log --oneline -5\` to check current state
241
+ 2. If the project is empty (only initial commit), **no research needed** — write the plan directly from the task description
242
+ 3. If the project has existing code, quickly check the file structure in your workspace (one \`ls\` only), then write the plan
243
+ 4. Create \`plan.md\` (with task checklist, phase breakdown, claim status) and merge into main
244
+ 5. If another member's plan.md already exists on main, merge your improvements into it
245
+
246
+ **What you must never do:**
247
+ - Do not write any business code
248
+ - Do not repeatedly ls / glob / find to explore directories
249
+ - Do not read any files outside your workspace
250
+ - Do not spend more than 2-3 minutes on this round
251
+
252
+ ## Consensus Vote
253
+
254
+ At the **end** of your response, you must vote:
255
+ - \`[CONSENSUS: NO]\` — normal for Round 1 (execution still needed after planning)
256
+ - \`[CONSENSUS: YES]\` — only if the task is extremely simple
257
+
258
+ Start writing plan.md now!`;
259
+ }
260
+ return `# Round ${round} — Execution Round
261
+
262
+ ## Task
263
+ ${task}
264
+
265
+ ## Your Partners
266
+ ${otherAgents.map((a) => `- ${a.emoji} ${a.name}`).join('\n')}
267
+ ${history}
268
+ ## Your Work
269
+
270
+ plan.md was created by all members in Round 1. Now execute according to plan:
271
+
272
+ 1. **Check current state** — pull main, read plan.md, understand latest progress
273
+ 2. **Claim and execute tasks** — pick unclaimed tasks from plan.md, write code, modify files, run tests
274
+ 3. **Review others' work** — if other members have output, review and suggest improvements or fix directly
275
+ 4. **Report results** — briefly describe what you did
276
+
277
+ ## Consensus Vote
278
+
279
+ At the **end** of your response, you must vote (pick one):
280
+
281
+ - \`[CONSENSUS: YES]\` — task complete, quality meets standards, ready to finish
282
+ - \`[CONSENSUS: NO]\` — still work to do or issues to resolve
283
+
284
+ Collaboration ends **only when all members vote YES**.
285
+
286
+ Start working!`;
287
+ }
288
+ /** Resolve the path to configs/ relative to this module (works from both src/ and dist/) */
289
+ function resolveConfigPath(filename) {
290
+ // Try relative to source first, then relative to dist
291
+ const candidates = [
292
+ path.join(path.dirname(import.meta.url.replace('file://', '')), '..', '..', 'configs', filename),
293
+ path.join(path.dirname(import.meta.url.replace('file://', '')), '..', '..', '..', 'configs', filename),
294
+ ];
295
+ for (const p of candidates) {
296
+ if (fs.existsSync(p))
297
+ return p;
298
+ }
299
+ return candidates[0]; // fallback — will error on read
300
+ }
301
+ function buildSystemPrompt(agent, allAgents, worktreePath) {
302
+ const otherAgents = allAgents.filter((a) => a.name !== agent.name);
303
+ const otherBranches = otherAgents.map((a) => `\`council/${a.name}\``).join(', ');
304
+ const templatePath = resolveConfigPath('council-system-prompt.md');
305
+ const template = fs.readFileSync(templatePath, 'utf-8');
306
+ return template
307
+ .replace(/\{\{emoji\}\}/g, agent.emoji)
308
+ .replace(/\{\{name\}\}/g, agent.name)
309
+ .replace(/\{\{persona\}\}/g, agent.persona)
310
+ .replace(/\{\{workDir\}\}/g, worktreePath)
311
+ .replace(/\{\{otherBranches\}\}/g, otherBranches);
312
+ }
313
+ // ─── Council Engine ─────────────────────────────────────────────────────────
314
+ export class Council extends EventEmitter {
315
+ config;
316
+ manager;
317
+ agentTimeoutMs;
318
+ _aborted = false;
319
+ _activeSessions = new Set();
320
+ _session = null;
321
+ _pendingInjection = null;
322
+ logger;
323
+ constructor(config, manager, logger) {
324
+ super();
325
+ this.config = config;
326
+ this.manager = manager;
327
+ this.agentTimeoutMs = config.agentTimeoutMs || DEFAULT_AGENT_TIMEOUT_MS;
328
+ this.logger = logger || createConsoleLogger('Council');
329
+ }
330
+ getSession() {
331
+ return this._session ?? undefined;
332
+ }
333
+ injectMessage(message) {
334
+ this._pendingInjection = message;
335
+ }
336
+ abort() {
337
+ this._aborted = true;
338
+ for (const name of this._activeSessions) {
339
+ this.manager.stopSession(name).catch(() => { });
340
+ }
341
+ this._activeSessions.clear();
342
+ }
343
+ emitEvent(event) {
344
+ const full = { ...event, timestamp: new Date().toISOString() };
345
+ this.emit('council-event', full);
346
+ }
347
+ // ─── Single Agent Execution ───────────────────────────────────────────
348
+ async runSingleAgent(agent, prompt, systemPrompt, workDir, round, sessionId) {
349
+ this.emitEvent({ type: 'agent-start', sessionId, round, agent: agent.name });
350
+ const sessionName = `council-${sessionId.slice(0, 8)}-${agent.name}-r${round}`;
351
+ this._activeSessions.add(sessionName);
352
+ let content = '';
353
+ try {
354
+ for (let attempt = 0; attempt <= EMPTY_RESPONSE_MAX_RETRIES; attempt++) {
355
+ if (this._aborted)
356
+ throw new Error('Council aborted');
357
+ if (attempt > 0) {
358
+ this.emitEvent({
359
+ type: 'agent-chunk',
360
+ sessionId,
361
+ round,
362
+ agent: agent.name,
363
+ content: `\n[Empty response, retry ${attempt}/${EMPTY_RESPONSE_MAX_RETRIES}]\n`,
364
+ });
365
+ await sleep(EMPTY_RESPONSE_RETRY_DELAY_MS);
366
+ }
367
+ // Start a session for this agent
368
+ const engine = agent.engine || 'claude';
369
+ await this.manager.startSession({
370
+ name: sessionName,
371
+ cwd: workDir,
372
+ engine,
373
+ model: agent.model,
374
+ baseUrl: agent.baseUrl,
375
+ permissionMode: agent.permissionMode ?? this.config.defaultPermissionMode ?? 'bypassPermissions',
376
+ appendSystemPrompt: systemPrompt,
377
+ maxTurns: this.config.maxTurnsPerAgent || DEFAULT_MAX_TURNS_PER_AGENT,
378
+ maxBudgetUsd: this.config.maxBudgetUsd,
379
+ customEngine: agent.customEngine,
380
+ });
381
+ // Send the prompt and wait for completion
382
+ const result = await this.manager.sendMessage(sessionName, prompt, {
383
+ timeout: this.agentTimeoutMs,
384
+ onChunk: (chunk) => {
385
+ this.emitEvent({ type: 'agent-chunk', sessionId, round, agent: agent.name, content: chunk });
386
+ },
387
+ });
388
+ content = result.output;
389
+ // Check if response is substantive
390
+ const stripped = content.replace(/^\[Agent completed[^\]]*\]\s*/i, '').trim();
391
+ if (stripped.length > 0 || hasConsensusMarker(content))
392
+ break;
393
+ if (attempt === EMPTY_RESPONSE_MAX_RETRIES) {
394
+ this.logger.info(`${agent.name}: empty after ${EMPTY_RESPONSE_MAX_RETRIES} retries`);
395
+ }
396
+ }
397
+ // Follow-up if response is too short and has no consensus marker
398
+ const strippedContent = content.replace(/^\[Agent completed[^\]]*\]\s*/i, '').trim();
399
+ if (!this._aborted && strippedContent.length < MIN_COMPLETE_RESPONSE_LENGTH && !hasConsensusMarker(content)) {
400
+ for (let i = 0; i < FOLLOWUP_MAX_RETRIES; i++) {
401
+ if (this._aborted)
402
+ break;
403
+ try {
404
+ const followup = await this.manager.sendMessage(sessionName, 'Stop all tool calls. Output your complete report now, including your consensus vote [CONSENSUS: YES] or [CONSENSUS: NO].', { timeout: FOLLOWUP_TIMEOUT_MS });
405
+ if (followup.output.trim().length > 0) {
406
+ content = followup.output;
407
+ if (hasConsensusMarker(content) || content.length >= MIN_COMPLETE_RESPONSE_LENGTH)
408
+ break;
409
+ }
410
+ }
411
+ catch {
412
+ break;
413
+ }
414
+ await sleep(EMPTY_RESPONSE_RETRY_DELAY_MS);
415
+ }
416
+ }
417
+ }
418
+ finally {
419
+ // Stop session — fire-and-forget
420
+ this.manager.stopSession(sessionName).catch(() => { });
421
+ this._activeSessions.delete(sessionName);
422
+ }
423
+ const consensus = parseConsensus(content);
424
+ const response = {
425
+ agent: agent.name,
426
+ round,
427
+ content,
428
+ consensus,
429
+ sessionKey: sessionName,
430
+ timestamp: new Date().toISOString(),
431
+ };
432
+ this.emitEvent({ type: 'agent-complete', sessionId, round, agent: agent.name, content, consensus });
433
+ return response;
434
+ }
435
+ // ─── Initialisation (synchronous — returns handle immediately) ──────
436
+ init(task) {
437
+ if (!task || task.trim().length < MIN_TASK_LENGTH) {
438
+ throw new Error(`Task description too short (min ${MIN_TASK_LENGTH} chars)`);
439
+ }
440
+ const session = {
441
+ id: randomUUID(),
442
+ task: task.trim(),
443
+ config: this.config,
444
+ responses: [],
445
+ status: 'running',
446
+ startTime: new Date().toISOString(),
447
+ };
448
+ this._session = session;
449
+ return session;
450
+ }
451
+ // ─── Main Orchestration Loop ──────────────────────────────────────────
452
+ async run(task) {
453
+ // Allow run(task) as shorthand for init(task) + run()
454
+ if (task && !this._session)
455
+ this.init(task);
456
+ const session = this._session;
457
+ if (!session)
458
+ throw new Error('Council not initialised — call init() first');
459
+ const trimmedTask = session.task;
460
+ // Safety check: prevent council from running inside the program's own directory
461
+ const moduleRoot = path.resolve(path.dirname(import.meta.url.replace('file://', '')), '..', '..');
462
+ const resolvedProjectDir = path.resolve(this.config.projectDir);
463
+ if (resolvedProjectDir === moduleRoot || resolvedProjectDir.startsWith(moduleRoot + '/')) {
464
+ throw new Error(`SAFETY: projectDir (${resolvedProjectDir}) is inside program root (${moduleRoot}). Refusing to start council.`);
465
+ }
466
+ if (this.config.agents.length === 0) {
467
+ throw new Error('Council requires at least one agent');
468
+ }
469
+ this.logger.info(`Starting: ${this.config.agents.length} agents, max ${this.config.maxRounds} rounds`);
470
+ this.logger.info(`Task: ${trimmedTask}`);
471
+ this.logger.info(`Dir: ${this.config.projectDir}`);
472
+ this.logger.warn('Agents run with permissionMode=bypassPermissions for autonomous execution');
473
+ this.emitEvent({ type: 'session-start', sessionId: session.id, task: trimmedTask });
474
+ // Set up git worktrees
475
+ let worktreeMap;
476
+ try {
477
+ worktreeMap = await setupWorktrees(this.config.projectDir, this.config.agents, this.logger);
478
+ }
479
+ catch (err) {
480
+ session.status = 'error';
481
+ session.endTime = new Date().toISOString();
482
+ throw err;
483
+ }
484
+ this.logger.info('Worktrees:');
485
+ for (const [name, wtPath] of worktreeMap) {
486
+ this.logger.info(` ${name}: ${wtPath}`);
487
+ }
488
+ try {
489
+ for (let round = 1; round <= this.config.maxRounds; round++) {
490
+ if (this._aborted)
491
+ break;
492
+ this.logger.info(`Round ${round} (${this.config.agents.length} agents parallel)`);
493
+ this.emitEvent({ type: 'round-start', sessionId: session.id, round });
494
+ // Check for user injection
495
+ const injection = this._pendingInjection;
496
+ this._pendingInjection = null;
497
+ // Build prompts for all agents
498
+ const agentTasks = this.config.agents.map((agent) => {
499
+ const workDir = worktreeMap.get(agent.name) || this.config.projectDir;
500
+ let prompt = buildAgentPrompt(agent, trimmedTask, round, session.responses, this.config.agents);
501
+ if (injection) {
502
+ prompt += `\n\n## User Injection\n\n${injection}`;
503
+ }
504
+ const systemPrompt = buildSystemPrompt(agent, this.config.agents, workDir);
505
+ return { agent, prompt, systemPrompt, workDir };
506
+ });
507
+ // Execute all agents in parallel
508
+ const results = await Promise.allSettled(agentTasks.map(({ agent, prompt, systemPrompt, workDir }) => this.runSingleAgent(agent, prompt, systemPrompt, workDir, round, session.id)));
509
+ // Collect results
510
+ const roundVotes = [];
511
+ for (let i = 0; i < results.length; i++) {
512
+ const result = results[i];
513
+ const agent = this.config.agents[i];
514
+ if (result.status === 'fulfilled') {
515
+ roundVotes.push(result.value.consensus);
516
+ session.responses.push(result.value);
517
+ }
518
+ else {
519
+ const errMsg = result.reason?.message || 'Unknown error';
520
+ this.logger.error(`${agent.name} failed: ${errMsg}`);
521
+ this.emitEvent({ type: 'error', sessionId: session.id, round, agent: agent.name, error: errMsg });
522
+ roundVotes.push(false);
523
+ session.responses.push({
524
+ agent: agent.name,
525
+ round,
526
+ content: `Error: ${errMsg}`,
527
+ consensus: false,
528
+ sessionKey: '',
529
+ timestamp: new Date().toISOString(),
530
+ });
531
+ }
532
+ }
533
+ const allYes = roundVotes.length === this.config.agents.length && roundVotes.every((v) => v);
534
+ this.emitEvent({ type: 'round-end', sessionId: session.id, round, status: allYes ? 'consensus' : 'continue' });
535
+ if (allYes) {
536
+ this.logger.info(`Consensus reached at round ${round}`);
537
+ session.status = 'awaiting_user';
538
+ break;
539
+ }
540
+ else {
541
+ const yesCount = roundVotes.filter((v) => v).length;
542
+ this.logger.info(`Votes: ${yesCount}/${this.config.agents.length} YES`);
543
+ }
544
+ if (round < this.config.maxRounds) {
545
+ await sleep(INTER_ROUND_DELAY_MS);
546
+ }
547
+ }
548
+ if (this._aborted) {
549
+ session.status = 'error';
550
+ }
551
+ else if (session.status === 'running') {
552
+ session.status = 'max_rounds';
553
+ this.logger.info(`Max rounds (${this.config.maxRounds}) reached`);
554
+ }
555
+ session.endTime = new Date().toISOString();
556
+ session.compactContext = this.generateCompactContext(session);
557
+ session.finalSummary = this.generateSummary(session);
558
+ this.saveTranscript(session);
559
+ this.emitEvent({ type: 'complete', sessionId: session.id, status: session.status });
560
+ return session;
561
+ }
562
+ catch (err) {
563
+ session.status = 'error';
564
+ session.endTime = new Date().toISOString();
565
+ this.emitEvent({ type: 'error', sessionId: session.id, error: err.message });
566
+ throw err;
567
+ }
568
+ }
569
+ // ─── Summary & Transcript ─────────────────────────────────────────────
570
+ generateSummary(session) {
571
+ const maxRound = session.responses.length > 0 ? Math.max(...session.responses.map((r) => r.round)) : 0;
572
+ const statusText = session.status === 'awaiting_user' || session.status === 'consensus' ? 'Consensus reached' : 'Max rounds reached';
573
+ const lines = [
574
+ `# Council Summary\n`,
575
+ `- **Task**: ${session.task}`,
576
+ `- **Status**: ${statusText}`,
577
+ `- **Rounds**: ${maxRound}`,
578
+ `- **Directory**: ${session.config.projectDir}\n`,
579
+ `## Final Agent Status\n`,
580
+ ];
581
+ const lastResponses = session.responses.filter((r) => r.round === maxRound);
582
+ for (const resp of lastResponses) {
583
+ const agent = session.config.agents.find((a) => a.name === resp.agent);
584
+ const emoji = agent?.emoji || '';
585
+ const clean = stripConsensusTags(resp.content);
586
+ const preview = clean.slice(0, SUMMARY_SHORT_CHARS) + (clean.length > SUMMARY_SHORT_CHARS ? '...' : '');
587
+ lines.push(`### ${emoji} ${resp.agent}`);
588
+ lines.push(`- Vote: ${resp.consensus ? 'YES' : 'NO'}`);
589
+ lines.push(`- Summary:\n${preview}\n`);
590
+ }
591
+ return lines.join('\n');
592
+ }
593
+ generateCompactContext(session) {
594
+ const maxRound = session.responses.length > 0 ? Math.max(...session.responses.map((r) => r.round)) : 0;
595
+ const recent = session.responses.filter((r) => r.round >= maxRound - 1);
596
+ const summaries = recent.map((resp) => {
597
+ const clean = stripConsensusTags(resp.content).replace(/\s+/g, ' ').slice(0, COMPACT_CONTEXT_CHARS);
598
+ return `- [R${resp.round}] ${resp.agent}: ${clean}${clean.length >= COMPACT_CONTEXT_CHARS ? '...' : ''}`;
599
+ });
600
+ return [
601
+ `Task: ${session.task}`,
602
+ `Progress: round ${maxRound} / max ${session.config.maxRounds}`,
603
+ `Status: ${session.status}`,
604
+ 'Latest:',
605
+ ...summaries,
606
+ ].join('\n');
607
+ }
608
+ saveTranscript(session) {
609
+ const ts = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
610
+ const logDir = path.join(getHomeOrTmp(), '.openclaw', 'council-logs');
611
+ if (!fs.existsSync(logDir))
612
+ fs.mkdirSync(logDir, { recursive: true });
613
+ const filepath = path.join(logDir, `council-${ts}.md`);
614
+ let content = `# Council Transcript\n\n`;
615
+ content += `- **Time**: ${session.startTime}\n`;
616
+ content += `- **Task**: ${session.task}\n`;
617
+ content += `- **Status**: ${session.status}\n\n---\n\n`;
618
+ let currentRound = 0;
619
+ for (const resp of session.responses) {
620
+ if (resp.round !== currentRound) {
621
+ currentRound = resp.round;
622
+ content += `## Round ${currentRound}\n\n`;
623
+ }
624
+ const agent = session.config.agents.find((a) => a.name === resp.agent);
625
+ content += `### ${agent?.emoji || ''} ${resp.agent}\n\n${resp.content}\n\n`;
626
+ }
627
+ content += `---\n\n${session.finalSummary || ''}`;
628
+ fs.writeFileSync(filepath, content);
629
+ this.logger.info(`Transcript saved: ${filepath}`);
630
+ }
631
+ // ─── Post-Processing: Review / Accept / Reject ──────────────────────────
632
+ /**
633
+ * Produce a structured review of the council's output.
634
+ * Lists all changed files, branches, worktrees, plan.md status, and agent summaries.
635
+ * Does NOT modify any state — purely informational.
636
+ */
637
+ async review() {
638
+ const session = this._session;
639
+ if (!session)
640
+ throw new Error('Council not initialised');
641
+ const dir = session.config.projectDir;
642
+ // Gather branches
643
+ const branches = await spawnAsync('git', ['-C', dir, 'for-each-ref', '--format=%(refname:short)', 'refs/heads/'], {
644
+ timeout: GIT_CMD_TIMEOUT_MS,
645
+ })
646
+ .then((r) => r.stdout
647
+ .trim()
648
+ .split('\n')
649
+ .filter((b) => b.startsWith('council/')))
650
+ .catch(() => []);
651
+ // Gather worktrees
652
+ const worktrees = await spawnAsync('git', ['-C', dir, 'worktree', 'list', '--porcelain'], {
653
+ timeout: GIT_CMD_TIMEOUT_MS,
654
+ })
655
+ .then((r) => {
656
+ const lines = r.stdout.split('\n');
657
+ return lines.filter((l) => l.startsWith('worktree ')).map((l) => l.replace('worktree ', '').trim());
658
+ })
659
+ .catch(() => []);
660
+ // Filter to only council worktrees (not the main worktree)
661
+ const councilWorktrees = worktrees.filter((w) => w.includes('council') || w.includes('.worktrees'));
662
+ // Check plan.md
663
+ const planPath = path.join(dir, 'plan.md');
664
+ const planExists = fs.existsSync(planPath);
665
+ const planContent = planExists ? fs.readFileSync(planPath, 'utf-8') : undefined;
666
+ // Check reviews/
667
+ const reviewsDir = path.join(dir, 'reviews');
668
+ const reviews = fs.existsSync(reviewsDir) ? fs.readdirSync(reviewsDir).filter((f) => f.endsWith('.md')) : [];
669
+ // Diff stat: find changed files compared to initial state
670
+ const changedFiles = [];
671
+ try {
672
+ // Ensure git history is available before diffing
673
+ await spawnAsync('git', ['-C', dir, 'log', '--oneline', '--all', `-${GIT_LOG_DEPTH}`], {
674
+ timeout: GIT_CMD_TIMEOUT_MS,
675
+ });
676
+ // Get diff stat from recent history (rough heuristic)
677
+ const diffResult = await spawnAsync('git', ['-C', dir, 'diff', '--stat', '--numstat', 'HEAD~20', 'HEAD', '--'], {
678
+ timeout: WORKTREE_CMD_TIMEOUT_MS,
679
+ }).catch(() => ({ stdout: '', stderr: '' }));
680
+ if (diffResult.stdout.trim()) {
681
+ for (const line of diffResult.stdout.trim().split('\n')) {
682
+ const parts = line.split('\t');
683
+ if (parts.length >= 3) {
684
+ const insertions = parseInt(parts[0], 10) || 0;
685
+ const deletions = parseInt(parts[1], 10) || 0;
686
+ const file = parts[2];
687
+ if (file && !file.startsWith('-')) {
688
+ changedFiles.push({ file, status: 'clean', insertions, deletions });
689
+ }
690
+ }
691
+ }
692
+ }
693
+ // If no numstat, try a simpler approach
694
+ if (changedFiles.length === 0) {
695
+ const nameOnly = await spawnAsync('git', ['-C', dir, 'diff', '--name-only', 'HEAD~10', 'HEAD', '--'], {
696
+ timeout: GIT_CMD_TIMEOUT_MS,
697
+ }).catch(() => ({ stdout: '', stderr: '' }));
698
+ for (const file of nameOnly.stdout.trim().split('\n').filter(Boolean)) {
699
+ changedFiles.push({ file, status: 'clean', insertions: 0, deletions: 0 });
700
+ }
701
+ }
702
+ }
703
+ catch {
704
+ // Git diff failed — possibly shallow history; skip file listing
705
+ }
706
+ // Agent summaries from final round
707
+ const maxRound = session.responses.length > 0 ? Math.max(...session.responses.map((r) => r.round)) : 0;
708
+ const lastResponses = session.responses.filter((r) => r.round === maxRound);
709
+ const agentSummaries = lastResponses.map((resp) => {
710
+ const clean = stripConsensusTags(resp.content);
711
+ return {
712
+ agent: resp.agent,
713
+ consensus: resp.consensus,
714
+ preview: clean.slice(0, SUMMARY_PREVIEW_CHARS) + (clean.length > SUMMARY_PREVIEW_CHARS ? '...' : ''),
715
+ };
716
+ });
717
+ // Load reviewer guidance from config
718
+ let reviewerGuidance = '';
719
+ try {
720
+ const guidancePath = resolveConfigPath('council-reviewer-prompt.md');
721
+ reviewerGuidance = fs.readFileSync(guidancePath, 'utf-8');
722
+ }
723
+ catch {
724
+ reviewerGuidance = 'Reviewer guidance not found. Evaluate the council output independently.';
725
+ }
726
+ return {
727
+ councilId: session.id,
728
+ projectDir: dir,
729
+ status: session.status,
730
+ rounds: maxRound,
731
+ planExists,
732
+ planContent,
733
+ changedFiles,
734
+ branches,
735
+ worktrees: councilWorktrees,
736
+ reviews,
737
+ agentSummaries,
738
+ reviewerGuidance,
739
+ };
740
+ }
741
+ /**
742
+ * Internal cleanup helper — removes worktrees, branches, plan.md, and reviews/.
743
+ * Each cleanup step is independently gated by the `options` flags.
744
+ */
745
+ async _cleanup(projectDir, options) {
746
+ const result = {
747
+ worktreesRemoved: [],
748
+ branchesDeleted: [],
749
+ planDeleted: false,
750
+ reviewsDeleted: false,
751
+ };
752
+ // Remove council worktrees
753
+ if (options.removeWorktrees) {
754
+ const wtListResult = await spawnAsync('git', ['-C', projectDir, 'worktree', 'list'], {
755
+ timeout: GIT_CMD_TIMEOUT_MS,
756
+ }).catch(() => ({
757
+ stdout: '',
758
+ stderr: '',
759
+ }));
760
+ for (const line of wtListResult.stdout.split('\n')) {
761
+ const wtPath = line.split(/\s+/)[0];
762
+ if (wtPath && wtPath.includes('council')) {
763
+ // Safety: never remove the project dir itself
764
+ if (path.resolve(wtPath) === path.resolve(projectDir))
765
+ continue;
766
+ await spawnAsync('git', ['-C', projectDir, 'worktree', 'remove', '--force', wtPath], {
767
+ timeout: WORKTREE_CMD_TIMEOUT_MS,
768
+ }).catch((err) => this.logger.error(`Failed to remove worktree ${wtPath}:`, err.message));
769
+ result.worktreesRemoved.push(wtPath);
770
+ }
771
+ }
772
+ // Also remove .worktrees directory if it exists
773
+ const dotWorktrees = path.join(projectDir, '.worktrees');
774
+ if (fs.existsSync(dotWorktrees)) {
775
+ // Remove any remaining worktree dirs via git first
776
+ for (const entry of fs.readdirSync(dotWorktrees)) {
777
+ const wtPath = path.join(dotWorktrees, entry);
778
+ if (fs.statSync(wtPath).isDirectory()) {
779
+ await spawnAsync('git', ['-C', projectDir, 'worktree', 'remove', '--force', wtPath], {
780
+ timeout: WORKTREE_CMD_TIMEOUT_MS,
781
+ }).catch(() => { });
782
+ if (!result.worktreesRemoved.includes(wtPath))
783
+ result.worktreesRemoved.push(wtPath);
784
+ }
785
+ }
786
+ // Clean up the directory itself if empty
787
+ try {
788
+ fs.rmSync(dotWorktrees, { recursive: true, force: true });
789
+ }
790
+ catch {
791
+ // May fail if not empty; that's ok
792
+ }
793
+ }
794
+ await spawnAsync('git', ['-C', projectDir, 'worktree', 'prune'], { timeout: GIT_CMD_TIMEOUT_MS }).catch(() => { });
795
+ }
796
+ // Delete council branches
797
+ if (options.deleteBranches) {
798
+ const branchResult = await spawnAsync('git', ['-C', projectDir, 'for-each-ref', '--format=%(refname:short)', 'refs/heads/'], { timeout: GIT_CMD_TIMEOUT_MS }).catch(() => ({ stdout: '', stderr: '' }));
799
+ for (const branch of branchResult.stdout.trim().split('\n')) {
800
+ if (branch.startsWith('council/')) {
801
+ await spawnAsync('git', ['-C', projectDir, 'branch', '-D', branch], {
802
+ timeout: GIT_CMD_TIMEOUT_MS,
803
+ }).catch((err) => this.logger.error(`Failed to delete branch ${branch}:`, err.message));
804
+ result.branchesDeleted.push(branch);
805
+ }
806
+ }
807
+ }
808
+ // Remove plan.md
809
+ if (options.removePlan) {
810
+ const planPath = path.join(projectDir, 'plan.md');
811
+ result.planDeleted = fs.existsSync(planPath);
812
+ if (result.planDeleted)
813
+ fs.unlinkSync(planPath);
814
+ }
815
+ // Remove reviews/
816
+ if (options.removeReviews) {
817
+ const reviewsDir = path.join(projectDir, 'reviews');
818
+ result.reviewsDeleted = fs.existsSync(reviewsDir);
819
+ if (result.reviewsDeleted)
820
+ fs.rmSync(reviewsDir, { recursive: true, force: true });
821
+ }
822
+ return result;
823
+ }
824
+ /**
825
+ * Accept the council's work: clean up worktrees, branches, plan.md, and reviews/.
826
+ * Should only be called after reviewing via `review()`.
827
+ */
828
+ async accept() {
829
+ const session = this._session;
830
+ if (!session)
831
+ throw new Error('Council not initialised');
832
+ const dir = session.config.projectDir;
833
+ // Ensure we're on main
834
+ await spawnAsync('git', ['-C', dir, 'checkout', 'main'], { timeout: GIT_CMD_TIMEOUT_MS }).catch(() => spawnAsync('git', ['-C', dir, 'checkout', 'master'], { timeout: GIT_CMD_TIMEOUT_MS }).catch(() => { }));
835
+ const { worktreesRemoved, branchesDeleted, planDeleted, reviewsDeleted } = await this._cleanup(dir, {
836
+ removeWorktrees: true,
837
+ deleteBranches: true,
838
+ removePlan: true,
839
+ removeReviews: true,
840
+ });
841
+ // Update session status
842
+ session.status = 'accepted';
843
+ this.logger.info(`Accepted: ${branchesDeleted.length} branches, ${worktreesRemoved.length} worktrees cleaned up`);
844
+ return { councilId: session.id, branchesDeleted, worktreesRemoved, planDeleted, reviewsDeleted };
845
+ }
846
+ /**
847
+ * Reject the council's work: rewrite plan.md with feedback.
848
+ * Does NOT delete any worktrees or branches — the council can retry.
849
+ */
850
+ async reject(feedback) {
851
+ const session = this._session;
852
+ if (!session)
853
+ throw new Error('Council not initialised');
854
+ const dir = session.config.projectDir;
855
+ const planPath = path.join(dir, 'plan.md');
856
+ // Build rejection plan
857
+ const rejectionPlan = `# Project Plan (REJECTED & RESTARTED)
858
+
859
+ ## Reviewer Feedback
860
+ ${feedback}
861
+
862
+ ## Previous Status
863
+ - **Council ID**: ${session.id}
864
+ - **Rounds completed**: ${session.responses.length > 0 ? Math.max(...session.responses.map((r) => r.round)) : 0}
865
+ - **Final status**: ${session.status}
866
+
867
+ ## Tasks for Council
868
+ _Replace the tasks below with specific actionable items based on the feedback above._
869
+
870
+ - [ ] Address reviewer feedback
871
+ - [ ] Verify all changes compile and pass tests
872
+ - [ ] Update plan.md with accurate completion status
873
+ `;
874
+ fs.writeFileSync(planPath, rejectionPlan);
875
+ // Commit the rejection plan
876
+ await spawnAsync('git', ['-C', dir, 'add', 'plan.md'], { timeout: GIT_CMD_TIMEOUT_MS }).catch(() => { });
877
+ await spawnAsync('git', ['-C', dir, 'commit', '-m', 'council(reject): rewrite plan.md with reviewer feedback'], {
878
+ timeout: GIT_CMD_TIMEOUT_MS,
879
+ }).catch(() => { });
880
+ // Update session status
881
+ session.status = 'rejected';
882
+ this.logger.info('Rejected: plan.md rewritten with feedback');
883
+ return { councilId: session.id, planRewritten: true, feedback };
884
+ }
885
+ }
886
+ // ─── Default Config ─────────────────────────────────────────────────────────
887
+ export function getDefaultCouncilConfig(projectDir) {
888
+ return {
889
+ name: 'Three Minds Council',
890
+ agents: [
891
+ {
892
+ name: 'Planner',
893
+ emoji: '🔵',
894
+ persona: 'You are a technical planner. You decompose requirements into actionable plans, define product context and constraints, outline high-level architecture decisions, and deliberately avoid premature implementation details. Your goal is a clear, phased blueprint that other agents can execute against.',
895
+ role: 'gemini',
896
+ },
897
+ {
898
+ name: 'Generator',
899
+ emoji: '🟠',
900
+ persona: 'You are an implementation engineer. You execute strictly according to plan.md, delivering working code sprint by sprint. You prioritize correctness, shipping velocity, and minimal deviation from the plan. When the plan is ambiguous, you fill gaps conservatively without reinventing requirements.',
901
+ role: 'claude',
902
+ },
903
+ {
904
+ name: 'Evaluator',
905
+ emoji: '🟢',
906
+ persona: 'You are an independent quality gate. You do not trust that the implementation is correct — you verify it. You validate from real user paths, hunt for broken UX, edge cases, regressions, and inconsistencies. You must give an explicit blocking issue list or a reasoned approval. You are not a polite reviewer; you are the acceptance authority.',
907
+ role: 'gpt',
908
+ },
909
+ ],
910
+ maxRounds: DEFAULT_MAX_ROUNDS,
911
+ projectDir,
912
+ };
913
+ }
914
+ //# sourceMappingURL=council.js.map