@ebowwa/coder 0.7.63 → 0.7.65

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 (364) hide show
  1. package/dist/core/__tests__/permissions.test.d.ts +12 -0
  2. package/dist/core/__tests__/permissions.test.d.ts.map +1 -0
  3. package/dist/core/__tests__/permissions.test.js +851 -0
  4. package/dist/core/agent-loop/__tests__/compaction.test.d.ts +5 -0
  5. package/dist/core/agent-loop/__tests__/compaction.test.d.ts.map +1 -0
  6. package/dist/core/agent-loop/__tests__/compaction.test.js +209 -0
  7. package/dist/core/agent-loop/__tests__/formatters.test.d.ts +5 -0
  8. package/dist/core/agent-loop/__tests__/formatters.test.d.ts.map +1 -0
  9. package/dist/core/agent-loop/__tests__/formatters.test.js +195 -0
  10. package/dist/core/agent-loop/__tests__/index.test.d.ts +5 -0
  11. package/dist/core/agent-loop/__tests__/index.test.d.ts.map +1 -0
  12. package/dist/core/agent-loop/__tests__/index.test.js +121 -0
  13. package/dist/core/agent-loop/__tests__/loop-state.test.d.ts +5 -0
  14. package/dist/core/agent-loop/__tests__/loop-state.test.d.ts.map +1 -0
  15. package/dist/core/agent-loop/__tests__/loop-state.test.js +340 -0
  16. package/dist/core/agent-loop/__tests__/message-builder.test.d.ts +5 -0
  17. package/dist/core/agent-loop/__tests__/message-builder.test.d.ts.map +1 -0
  18. package/dist/core/agent-loop/__tests__/message-builder.test.js +178 -0
  19. package/dist/core/agent-loop/__tests__/tool-executor.test.d.ts +5 -0
  20. package/dist/core/agent-loop/__tests__/tool-executor.test.d.ts.map +1 -0
  21. package/dist/core/agent-loop/__tests__/tool-executor.test.js +331 -0
  22. package/dist/core/agent-loop/compaction.d.ts +39 -0
  23. package/dist/core/agent-loop/compaction.d.ts.map +1 -0
  24. package/dist/core/agent-loop/compaction.js +51 -0
  25. package/dist/core/agent-loop/formatters.d.ts +21 -0
  26. package/dist/core/agent-loop/formatters.d.ts.map +1 -0
  27. package/dist/core/agent-loop/formatters.js +42 -0
  28. package/dist/core/agent-loop/index.d.ts +25 -0
  29. package/dist/core/agent-loop/index.d.ts.map +1 -0
  30. package/dist/core/agent-loop/index.js +83 -0
  31. package/dist/core/agent-loop/loop-state.d.ts +74 -0
  32. package/dist/core/agent-loop/loop-state.d.ts.map +1 -0
  33. package/dist/core/agent-loop/loop-state.js +147 -0
  34. package/dist/core/agent-loop/message-builder.d.ts +13 -0
  35. package/dist/core/agent-loop/message-builder.d.ts.map +1 -0
  36. package/dist/core/agent-loop/message-builder.js +49 -0
  37. package/dist/core/agent-loop/tool-executor.d.ts +23 -0
  38. package/dist/core/agent-loop/tool-executor.d.ts.map +1 -0
  39. package/dist/core/agent-loop/tool-executor.js +152 -0
  40. package/dist/core/agent-loop/turn-executor.d.ts +57 -0
  41. package/dist/core/agent-loop/turn-executor.d.ts.map +1 -0
  42. package/dist/core/agent-loop/turn-executor.js +124 -0
  43. package/dist/core/agent-loop/types.d.ts +141 -0
  44. package/dist/core/agent-loop/types.d.ts.map +1 -0
  45. package/dist/core/agent-loop/types.js +4 -0
  46. package/dist/core/agent-loop.d.ts +17 -0
  47. package/dist/core/agent-loop.d.ts.map +1 -0
  48. package/dist/core/agent-loop.js +16 -0
  49. package/dist/core/api-client-impl.d.ts +62 -0
  50. package/dist/core/api-client-impl.d.ts.map +1 -0
  51. package/dist/core/api-client-impl.js +479 -0
  52. package/dist/core/api-client.d.ts +6 -0
  53. package/dist/core/api-client.d.ts.map +1 -0
  54. package/dist/core/api-client.js +5 -0
  55. package/dist/core/checkpoints.d.ts +128 -0
  56. package/dist/core/checkpoints.d.ts.map +1 -0
  57. package/dist/core/checkpoints.js +438 -0
  58. package/dist/core/claude-md.d.ts +71 -0
  59. package/dist/core/claude-md.d.ts.map +1 -0
  60. package/dist/core/claude-md.js +198 -0
  61. package/dist/core/cognitive-security/hooks.d.ts +138 -0
  62. package/dist/core/cognitive-security/hooks.d.ts.map +1 -0
  63. package/dist/core/cognitive-security/hooks.js +389 -0
  64. package/dist/core/cognitive-security/index.d.ts +751 -0
  65. package/dist/core/cognitive-security/index.d.ts.map +1 -0
  66. package/dist/core/cognitive-security/index.js +1123 -0
  67. package/dist/core/cognitive-security/middleware.d.ts +136 -0
  68. package/dist/core/cognitive-security/middleware.d.ts.map +1 -0
  69. package/dist/core/cognitive-security/middleware.js +376 -0
  70. package/dist/core/config-loader.d.ts +127 -0
  71. package/dist/core/config-loader.d.ts.map +1 -0
  72. package/dist/core/config-loader.js +219 -0
  73. package/dist/core/context-compaction.d.ts +87 -0
  74. package/dist/core/context-compaction.d.ts.map +1 -0
  75. package/dist/core/context-compaction.js +428 -0
  76. package/dist/core/git-status.d.ts +25 -0
  77. package/dist/core/git-status.d.ts.map +1 -0
  78. package/dist/core/git-status.js +204 -0
  79. package/dist/core/image.d.ts +69 -0
  80. package/dist/core/image.d.ts.map +1 -0
  81. package/dist/core/image.js +290 -0
  82. package/dist/core/image.test.d.ts +2 -0
  83. package/dist/core/image.test.d.ts.map +1 -0
  84. package/dist/core/image.test.js +149 -0
  85. package/dist/core/models.d.ts +123 -0
  86. package/dist/core/models.d.ts.map +1 -0
  87. package/dist/core/models.js +325 -0
  88. package/dist/core/permissions.d.ts +81 -0
  89. package/dist/core/permissions.d.ts.map +1 -0
  90. package/dist/core/permissions.js +327 -0
  91. package/dist/core/retry.d.ts +25 -0
  92. package/dist/core/retry.d.ts.map +1 -0
  93. package/dist/core/retry.js +121 -0
  94. package/dist/core/session-store.d.ts +9 -0
  95. package/dist/core/session-store.d.ts.map +1 -0
  96. package/dist/core/session-store.js +10 -0
  97. package/dist/core/sessions/export.d.ts +47 -0
  98. package/dist/core/sessions/export.d.ts.map +1 -0
  99. package/dist/core/sessions/export.js +256 -0
  100. package/dist/core/sessions/index.d.ts +132 -0
  101. package/dist/core/sessions/index.d.ts.map +1 -0
  102. package/dist/core/sessions/index.js +442 -0
  103. package/dist/core/sessions/metadata.d.ts +77 -0
  104. package/dist/core/sessions/metadata.d.ts.map +1 -0
  105. package/dist/core/sessions/metadata.js +233 -0
  106. package/dist/core/sessions/persistence.d.ts +72 -0
  107. package/dist/core/sessions/persistence.d.ts.map +1 -0
  108. package/dist/core/sessions/persistence.js +201 -0
  109. package/dist/core/sessions/types.d.ts +110 -0
  110. package/dist/core/sessions/types.d.ts.map +1 -0
  111. package/dist/core/sessions/types.js +4 -0
  112. package/dist/core/stream-highlighter.d.ts +18 -0
  113. package/dist/core/stream-highlighter.d.ts.map +1 -0
  114. package/dist/core/stream-highlighter.js +916 -0
  115. package/dist/core/system-reminders.d.ts +89 -0
  116. package/dist/core/system-reminders.d.ts.map +1 -0
  117. package/dist/core/system-reminders.js +285 -0
  118. package/dist/ecosystem/hooks/__tests__/index.test.d.ts +5 -0
  119. package/dist/ecosystem/hooks/__tests__/index.test.d.ts.map +1 -0
  120. package/dist/ecosystem/hooks/__tests__/index.test.js +458 -0
  121. package/dist/ecosystem/hooks/index.d.ts +59 -0
  122. package/dist/ecosystem/hooks/index.d.ts.map +1 -0
  123. package/dist/ecosystem/hooks/index.js +294 -0
  124. package/dist/ecosystem/hooks/prompt-evaluator.d.ts +32 -0
  125. package/dist/ecosystem/hooks/prompt-evaluator.d.ts.map +1 -0
  126. package/dist/ecosystem/hooks/prompt-evaluator.js +229 -0
  127. package/dist/ecosystem/skills/index.d.ts +55 -0
  128. package/dist/ecosystem/skills/index.d.ts.map +1 -0
  129. package/dist/ecosystem/skills/index.js +258 -0
  130. package/dist/ecosystem/tools/__tests__/index.test.d.ts +7 -0
  131. package/dist/ecosystem/tools/__tests__/index.test.d.ts.map +1 -0
  132. package/dist/ecosystem/tools/__tests__/index.test.js +856 -0
  133. package/dist/ecosystem/tools/index.d.ts +24 -0
  134. package/dist/ecosystem/tools/index.d.ts.map +1 -0
  135. package/dist/ecosystem/tools/index.js +1709 -0
  136. package/dist/index.d.ts +24 -0
  137. package/dist/index.d.ts.map +1 -0
  138. package/dist/index.js +33688 -49712
  139. package/dist/interfaces/mcp/client.d.ts +40 -0
  140. package/dist/interfaces/mcp/client.d.ts.map +1 -0
  141. package/dist/interfaces/mcp/client.js +309 -0
  142. package/dist/interfaces/ui/index.d.ts +36 -0
  143. package/dist/interfaces/ui/index.d.ts.map +1 -0
  144. package/dist/interfaces/ui/index.js +61 -0
  145. package/dist/interfaces/ui/spinner.d.ts +140 -0
  146. package/dist/interfaces/ui/spinner.d.ts.map +1 -0
  147. package/dist/interfaces/ui/spinner.js +342 -0
  148. package/dist/interfaces/ui/terminal/cli/index.d.ts +12 -0
  149. package/dist/interfaces/ui/terminal/cli/index.d.ts.map +1 -0
  150. package/dist/interfaces/ui/terminal/cli/index.js +32012 -50526
  151. package/dist/interfaces/ui/terminal/native/README.md +53 -0
  152. package/dist/interfaces/ui/terminal/native/claude_code_native.darwin-x64.node +0 -0
  153. package/dist/interfaces/ui/terminal/native/claude_code_native.dylib +0 -0
  154. package/dist/interfaces/ui/terminal/native/index.d.ts +0 -0
  155. package/dist/interfaces/ui/terminal/native/index.darwin-arm64.node +0 -0
  156. package/dist/interfaces/ui/terminal/native/index.js +43 -0
  157. package/dist/interfaces/ui/terminal/native/index.node +0 -0
  158. package/dist/interfaces/ui/terminal/native/package.json +34 -0
  159. package/dist/interfaces/ui/terminal/shared/args.d.ts +39 -0
  160. package/dist/interfaces/ui/terminal/shared/args.d.ts.map +1 -0
  161. package/dist/interfaces/ui/terminal/shared/args.js +176 -0
  162. package/dist/interfaces/ui/terminal/shared/index.d.ts +11 -0
  163. package/dist/interfaces/ui/terminal/shared/index.d.ts.map +1 -0
  164. package/dist/interfaces/ui/terminal/shared/index.js +16 -0
  165. package/dist/interfaces/ui/terminal/shared/loading-state.d.ts +124 -0
  166. package/dist/interfaces/ui/terminal/shared/loading-state.d.ts.map +1 -0
  167. package/dist/interfaces/ui/terminal/shared/loading-state.js +246 -0
  168. package/dist/interfaces/ui/terminal/shared/query.d.ts +22 -0
  169. package/dist/interfaces/ui/terminal/shared/query.d.ts.map +1 -0
  170. package/dist/interfaces/ui/terminal/shared/query.js +100 -0
  171. package/dist/interfaces/ui/terminal/shared/setup.d.ts +33 -0
  172. package/dist/interfaces/ui/terminal/shared/setup.d.ts.map +1 -0
  173. package/dist/interfaces/ui/terminal/shared/setup.js +226 -0
  174. package/dist/interfaces/ui/terminal/shared/status-line.d.ts +117 -0
  175. package/dist/interfaces/ui/terminal/shared/status-line.d.ts.map +1 -0
  176. package/dist/interfaces/ui/terminal/shared/status-line.js +267 -0
  177. package/dist/interfaces/ui/terminal/shared/system-prompt.d.ts +38 -0
  178. package/dist/interfaces/ui/terminal/shared/system-prompt.d.ts.map +1 -0
  179. package/dist/interfaces/ui/terminal/shared/system-prompt.js +102 -0
  180. package/dist/interfaces/ui/terminal/tui/HelpPanel.d.ts +39 -0
  181. package/dist/interfaces/ui/terminal/tui/HelpPanel.d.ts.map +1 -0
  182. package/dist/interfaces/ui/terminal/tui/HelpPanel.js +215 -0
  183. package/dist/interfaces/ui/terminal/tui/InputContext.d.ts +91 -0
  184. package/dist/interfaces/ui/terminal/tui/InputContext.d.ts.map +1 -0
  185. package/dist/interfaces/ui/terminal/tui/InputContext.js +154 -0
  186. package/dist/interfaces/ui/terminal/tui/InputField.d.ts +18 -0
  187. package/dist/interfaces/ui/terminal/tui/InputField.d.ts.map +1 -0
  188. package/dist/interfaces/ui/terminal/tui/InputField.js +41 -0
  189. package/dist/interfaces/ui/terminal/tui/InteractiveTUI.d.ts +16 -0
  190. package/dist/interfaces/ui/terminal/tui/InteractiveTUI.d.ts.map +1 -0
  191. package/dist/interfaces/ui/terminal/tui/InteractiveTUI.js +451 -0
  192. package/dist/interfaces/ui/terminal/tui/MessageArea.d.ts +10 -0
  193. package/dist/interfaces/ui/terminal/tui/MessageArea.d.ts.map +1 -0
  194. package/dist/interfaces/ui/terminal/tui/MessageArea.js +91 -0
  195. package/dist/interfaces/ui/terminal/tui/MessageStore.d.ts +48 -0
  196. package/dist/interfaces/ui/terminal/tui/MessageStore.d.ts.map +1 -0
  197. package/dist/interfaces/ui/terminal/tui/MessageStore.js +151 -0
  198. package/dist/interfaces/ui/terminal/tui/StatusBar.d.ts +9 -0
  199. package/dist/interfaces/ui/terminal/tui/StatusBar.d.ts.map +1 -0
  200. package/dist/interfaces/ui/terminal/tui/StatusBar.js +36 -0
  201. package/dist/interfaces/ui/terminal/tui/commands.d.ts +21 -0
  202. package/dist/interfaces/ui/terminal/tui/commands.d.ts.map +1 -0
  203. package/dist/interfaces/ui/terminal/tui/commands.js +359 -0
  204. package/dist/interfaces/ui/terminal/tui/components/InteractiveElements.d.ts +115 -0
  205. package/dist/interfaces/ui/terminal/tui/components/InteractiveElements.d.ts.map +1 -0
  206. package/dist/interfaces/ui/terminal/tui/components/InteractiveElements.js +306 -0
  207. package/dist/interfaces/ui/terminal/tui/components/MultilineInput.d.ts +92 -0
  208. package/dist/interfaces/ui/terminal/tui/components/MultilineInput.d.ts.map +1 -0
  209. package/dist/interfaces/ui/terminal/tui/components/MultilineInput.js +399 -0
  210. package/dist/interfaces/ui/terminal/tui/components/PaneManager.d.ts +59 -0
  211. package/dist/interfaces/ui/terminal/tui/components/PaneManager.d.ts.map +1 -0
  212. package/dist/interfaces/ui/terminal/tui/components/PaneManager.js +139 -0
  213. package/dist/interfaces/ui/terminal/tui/components/Sidebar.d.ts +68 -0
  214. package/dist/interfaces/ui/terminal/tui/components/Sidebar.d.ts.map +1 -0
  215. package/dist/interfaces/ui/terminal/tui/components/Sidebar.js +340 -0
  216. package/dist/interfaces/ui/terminal/tui/components/index.d.ts +23 -0
  217. package/dist/interfaces/ui/terminal/tui/components/index.d.ts.map +1 -0
  218. package/dist/interfaces/ui/terminal/tui/components/index.js +51 -0
  219. package/dist/interfaces/ui/terminal/tui/console.d.ts +20 -0
  220. package/dist/interfaces/ui/terminal/tui/console.d.ts.map +1 -0
  221. package/dist/interfaces/ui/terminal/tui/console.js +46 -0
  222. package/dist/interfaces/ui/terminal/tui/index.d.ts +20 -0
  223. package/dist/interfaces/ui/terminal/tui/index.d.ts.map +1 -0
  224. package/dist/interfaces/ui/terminal/tui/index.js +28 -0
  225. package/dist/interfaces/ui/terminal/tui/run.d.ts +13 -0
  226. package/dist/interfaces/ui/terminal/tui/run.d.ts.map +1 -0
  227. package/dist/interfaces/ui/terminal/tui/run.js +31 -0
  228. package/dist/interfaces/ui/terminal/tui/spinner.d.ts +44 -0
  229. package/dist/interfaces/ui/terminal/tui/spinner.d.ts.map +1 -0
  230. package/dist/interfaces/ui/terminal/tui/spinner.js +59 -0
  231. package/dist/interfaces/ui/terminal/tui/tui-app.d.ts +39 -0
  232. package/dist/interfaces/ui/terminal/tui/tui-app.d.ts.map +1 -0
  233. package/dist/interfaces/ui/terminal/tui/tui-app.js +198 -0
  234. package/dist/interfaces/ui/terminal/tui/tui-footer.d.ts +167 -0
  235. package/dist/interfaces/ui/terminal/tui/tui-footer.d.ts.map +1 -0
  236. package/dist/interfaces/ui/terminal/tui/tui-footer.js +330 -0
  237. package/dist/interfaces/ui/terminal/tui/types.d.ts +165 -0
  238. package/dist/interfaces/ui/terminal/tui/types.d.ts.map +1 -0
  239. package/dist/interfaces/ui/terminal/tui/types.js +5 -0
  240. package/dist/interfaces/ui/terminal/tui/useInputHandler.d.ts +23 -0
  241. package/dist/interfaces/ui/terminal/tui/useInputHandler.d.ts.map +1 -0
  242. package/dist/interfaces/ui/terminal/tui/useInputHandler.js +72 -0
  243. package/dist/interfaces/ui/terminal/tui/useNativeInput.d.ts +90 -0
  244. package/dist/interfaces/ui/terminal/tui/useNativeInput.d.ts.map +1 -0
  245. package/dist/interfaces/ui/terminal/tui/useNativeInput.js +188 -0
  246. package/dist/native/README.md +53 -0
  247. package/dist/native/claude_code_native.darwin-x64.node +0 -0
  248. package/dist/native/claude_code_native.dylib +0 -0
  249. package/dist/native/index.d.ts +0 -0
  250. package/dist/native/index.d.ts.map +1 -0
  251. package/dist/native/index.darwin-arm64.node +0 -0
  252. package/dist/native/index.js +43 -0
  253. package/dist/native/index.node +0 -0
  254. package/dist/native/package.json +34 -0
  255. package/dist/teammates/index.d.ts +161 -0
  256. package/dist/teammates/index.d.ts.map +1 -0
  257. package/dist/teammates/index.js +827 -0
  258. package/dist/types/index.d.ts +482 -0
  259. package/dist/types/index.d.ts.map +1 -0
  260. package/dist/types/index.js +52 -0
  261. package/native/index.darwin-arm64.node +0 -0
  262. package/native/index.js +33 -19
  263. package/package.json +6 -3
  264. package/packages/src/core/__tests__/permissions.test.ts +1091 -0
  265. package/packages/src/core/agent-loop/__tests__/compaction.test.ts +283 -0
  266. package/packages/src/core/agent-loop/__tests__/formatters.test.ts +234 -0
  267. package/packages/src/core/agent-loop/__tests__/index.test.ts +162 -0
  268. package/packages/src/core/agent-loop/__tests__/loop-state.test.ts +413 -0
  269. package/packages/src/core/agent-loop/__tests__/message-builder.test.ts +229 -0
  270. package/packages/src/core/agent-loop/__tests__/tool-executor.test.ts +457 -0
  271. package/packages/src/core/agent-loop/compaction.ts +92 -0
  272. package/packages/src/core/agent-loop/formatters.ts +50 -0
  273. package/packages/src/core/agent-loop/index.ts +137 -0
  274. package/packages/src/core/agent-loop/loop-state.ts +187 -0
  275. package/packages/src/core/agent-loop/message-builder.ts +62 -0
  276. package/packages/src/core/agent-loop/tool-executor.ts +211 -0
  277. package/packages/src/core/agent-loop/turn-executor.ts +226 -0
  278. package/packages/src/core/agent-loop/types.ts +152 -0
  279. package/packages/src/core/agent-loop.ts +18 -0
  280. package/packages/src/core/api-client-impl.ts +729 -0
  281. package/packages/src/core/api-client.ts +6 -0
  282. package/packages/src/core/checkpoints.ts +606 -0
  283. package/packages/src/core/claude-md.ts +272 -0
  284. package/packages/src/core/cognitive-security/hooks.ts +591 -0
  285. package/packages/src/core/cognitive-security/index.ts +2041 -0
  286. package/packages/src/core/cognitive-security/middleware.ts +536 -0
  287. package/packages/src/core/config/todo +7 -0
  288. package/packages/src/core/config-loader.ts +324 -0
  289. package/packages/src/core/context/__tests__/integration.test.ts +334 -0
  290. package/packages/src/core/context/compaction.ts +170 -0
  291. package/packages/src/core/context/constants.ts +58 -0
  292. package/packages/src/core/context/extraction.ts +85 -0
  293. package/packages/src/core/context/index.ts +66 -0
  294. package/packages/src/core/context/summarization.ts +251 -0
  295. package/packages/src/core/context/token-estimation.ts +98 -0
  296. package/packages/src/core/context/types.ts +59 -0
  297. package/packages/src/core/git-status.ts +262 -0
  298. package/packages/src/core/image.test.ts +180 -0
  299. package/packages/src/core/image.ts +350 -0
  300. package/packages/src/core/lmdb.db +0 -0
  301. package/packages/src/core/lmdb.db-lock +0 -0
  302. package/packages/src/core/models.ts +507 -0
  303. package/packages/src/core/normalizers/todo +8 -0
  304. package/packages/src/core/permissions.ts +431 -0
  305. package/packages/src/core/providers/README.md +230 -0
  306. package/packages/src/core/providers/__tests__/providers.test.ts +135 -0
  307. package/packages/src/core/providers/index.ts +419 -0
  308. package/packages/src/core/providers/types.ts +132 -0
  309. package/packages/src/core/retry.ts +180 -0
  310. package/packages/src/core/session-store.ts +36 -0
  311. package/packages/src/core/sessions/export.ts +329 -0
  312. package/packages/src/core/sessions/index.ts +587 -0
  313. package/packages/src/core/sessions/metadata.ts +309 -0
  314. package/packages/src/core/sessions/persistence.ts +244 -0
  315. package/packages/src/core/sessions/types.ts +169 -0
  316. package/packages/src/core/stream-highlighter.ts +1123 -0
  317. package/packages/src/core/system-reminders.ts +402 -0
  318. package/packages/src/core/todo +8 -0
  319. package/packages/src/ecosystem/hooks/__tests__/index.test.ts +561 -0
  320. package/packages/src/ecosystem/hooks/index.ts +341 -0
  321. package/packages/src/ecosystem/hooks/prompt-evaluator.ts +300 -0
  322. package/packages/src/ecosystem/skills/index.ts +295 -0
  323. package/packages/src/ecosystem/tools/__tests__/index.test.ts +1335 -0
  324. package/packages/src/ecosystem/tools/index.ts +2051 -0
  325. package/packages/src/index.ts +141 -0
  326. package/packages/src/interfaces/mcp/client.ts +389 -0
  327. package/packages/src/interfaces/ui/index.ts +158 -0
  328. package/packages/src/interfaces/ui/lmdb.db +0 -0
  329. package/packages/src/interfaces/ui/lmdb.db-lock +0 -0
  330. package/packages/src/interfaces/ui/spinner.ts +451 -0
  331. package/packages/src/interfaces/ui/terminal/bridge/index.ts +370 -0
  332. package/packages/src/interfaces/ui/terminal/bridge/ipc.ts +829 -0
  333. package/packages/src/interfaces/ui/terminal/bridge/screen-export.ts +968 -0
  334. package/packages/src/interfaces/ui/terminal/bridge/types.ts +226 -0
  335. package/packages/src/interfaces/ui/terminal/bridge/useBridge.ts +210 -0
  336. package/packages/src/interfaces/ui/terminal/cli/bootstrap.ts +132 -0
  337. package/packages/src/interfaces/ui/terminal/cli/index.ts +415 -0
  338. package/packages/src/interfaces/ui/terminal/cli/interactive/index.ts +110 -0
  339. package/packages/src/interfaces/ui/terminal/cli/interactive/input-handler.ts +393 -0
  340. package/packages/src/interfaces/ui/terminal/cli/interactive/interactive-runner.ts +820 -0
  341. package/packages/src/interfaces/ui/terminal/cli/interactive/message-store.ts +299 -0
  342. package/packages/src/interfaces/ui/terminal/cli/interactive/types.ts +274 -0
  343. package/packages/src/interfaces/ui/terminal/lmdb.db +0 -0
  344. package/packages/src/interfaces/ui/terminal/lmdb.db-lock +0 -0
  345. package/packages/src/interfaces/ui/terminal/shared/args.ts +222 -0
  346. package/packages/src/interfaces/ui/terminal/shared/index.ts +84 -0
  347. package/packages/src/interfaces/ui/terminal/shared/loading-state.ts +322 -0
  348. package/packages/src/interfaces/ui/terminal/shared/query.ts +152 -0
  349. package/packages/src/interfaces/ui/terminal/shared/setup.ts +299 -0
  350. package/packages/src/interfaces/ui/terminal/shared/spinner-frames.ts +73 -0
  351. package/packages/src/interfaces/ui/terminal/shared/status-line.ts +366 -0
  352. package/packages/src/interfaces/ui/terminal/shared/system-prompt.ts +146 -0
  353. package/packages/src/lmdb.db +0 -0
  354. package/packages/src/lmdb.db-lock +0 -0
  355. package/packages/src/native/index.ts +2722 -0
  356. package/packages/src/native/tui_v2_types.ts +39 -0
  357. package/packages/src/teammates/coordination.test.ts +279 -0
  358. package/packages/src/teammates/coordination.ts +646 -0
  359. package/packages/src/teammates/index.ts +1052 -0
  360. package/packages/src/teammates/integration.test.ts +272 -0
  361. package/packages/src/teammates/runner.test.ts +235 -0
  362. package/packages/src/teammates/runner.ts +750 -0
  363. package/packages/src/teammates/schemas.ts +673 -0
  364. package/packages/src/types/index.ts +723 -0
@@ -0,0 +1,1052 @@
1
+ /**
2
+ * Teammate System - Multi-agent coordination
3
+ *
4
+ * Messaging Architecture:
5
+ * - File-based inbox system for cross-process communication
6
+ * - Messages stored as JSON files in ~/.claude/teams/{team}/inboxes/{teammateId}/
7
+ * - pending/ for unread messages, processed/ for read messages
8
+ */
9
+
10
+ import type { Teammate, Team, TeammateMessage, TeammateStatus } from "../types/index.js";
11
+ import { spawn } from "child_process";
12
+ import { mkdirSync, rmSync, existsSync, readFileSync, readdirSync, renameSync, writeFileSync, statSync } from "fs";
13
+ import { join, basename } from "path";
14
+ import {
15
+ parseTeam,
16
+ parseTeammate,
17
+ parseTeammateMessage,
18
+ parseStoredMessage,
19
+ safeParseTeam,
20
+ safeParseTeammate,
21
+ safeParseTeammateMessage,
22
+ safeParseStoredMessage,
23
+ ValidationError,
24
+ sanitizeForFilePath,
25
+ } from "./schemas.js";
26
+
27
+ // ============================================
28
+ // FILE-BASED INBOX TYPES
29
+ // ============================================
30
+
31
+ interface StoredMessage extends TeammateMessage {
32
+ id: string;
33
+ teamName: string;
34
+ createdAt: number;
35
+ readAt?: number;
36
+ }
37
+
38
+ // ============================================
39
+ // TEAMMATE MANAGER
40
+ // ============================================
41
+
42
+ export class TeammateManager {
43
+ private teams = new Map<string, Team>();
44
+ private teammates = new Map<string, Teammate>();
45
+ private storagePath: string;
46
+
47
+ constructor(storagePath = "~/.claude/teams") {
48
+ this.storagePath = storagePath.replace("~", process.env.HOME || "");
49
+ // Ensure storage directory exists
50
+ if (!existsSync(this.storagePath)) {
51
+ mkdirSync(this.storagePath, { recursive: true });
52
+ }
53
+ // Load existing teams from disk
54
+ this.loadTeams();
55
+ // Ensure inbox directories exist for all teammates
56
+ this.ensureInboxDirectories();
57
+ }
58
+
59
+ // ============================================
60
+ // INBOX PATH HELPERS
61
+ // ============================================
62
+
63
+ /**
64
+ * Get safe directory name for a team (prevents ENAMETOOLONG)
65
+ */
66
+ private getSafeTeamDir(teamName: string): string {
67
+ return sanitizeForFilePath(teamName);
68
+ }
69
+
70
+ /**
71
+ * Get safe directory name for a teammate (prevents ENAMETOOLONG)
72
+ */
73
+ private getSafeTeammateDir(teammateId: string): string {
74
+ return sanitizeForFilePath(teammateId);
75
+ }
76
+
77
+ private getInboxPath(teamName: string, teammateId: string): string {
78
+ const safeTeamName = this.getSafeTeamDir(teamName);
79
+ const safeTeammateId = this.getSafeTeammateDir(teammateId);
80
+ return join(this.storagePath, safeTeamName, "inboxes", safeTeammateId);
81
+ }
82
+
83
+ private getPendingPath(teamName: string, teammateId: string): string {
84
+ return join(this.getInboxPath(teamName, teammateId), "pending");
85
+ }
86
+
87
+ private getProcessedPath(teamName: string, teammateId: string): string {
88
+ return join(this.getInboxPath(teamName, teammateId), "processed");
89
+ }
90
+
91
+ private ensureInboxDirectories(): void {
92
+ for (const team of this.teams.values()) {
93
+ for (const teammate of team.teammates) {
94
+ const pendingPath = this.getPendingPath(team.name, teammate.teammateId);
95
+ const processedPath = this.getProcessedPath(team.name, teammate.teammateId);
96
+
97
+ if (!existsSync(pendingPath)) {
98
+ mkdirSync(pendingPath, { recursive: true });
99
+ }
100
+ if (!existsSync(processedPath)) {
101
+ mkdirSync(processedPath, { recursive: true });
102
+ }
103
+ }
104
+ }
105
+ }
106
+
107
+ private generateMessageId(): string {
108
+ return `msg_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
109
+ }
110
+
111
+ // ============================================
112
+ // TEAM MANAGEMENT
113
+ // ============================================
114
+
115
+ createTeam(config: Omit<Team, "status">): Team {
116
+ const team: Team = {
117
+ ...config,
118
+ status: "active",
119
+ };
120
+
121
+ this.teams.set(config.name, team);
122
+
123
+ // Store teammates
124
+ for (const teammate of config.teammates) {
125
+ this.teammates.set(teammate.teammateId, teammate);
126
+ }
127
+
128
+ // Create inbox directories for all teammates
129
+ for (const teammate of config.teammates) {
130
+ const pendingPath = this.getPendingPath(config.name, teammate.teammateId);
131
+ const processedPath = this.getProcessedPath(config.name, teammate.teammateId);
132
+
133
+ if (!existsSync(pendingPath)) {
134
+ mkdirSync(pendingPath, { recursive: true });
135
+ }
136
+ if (!existsSync(processedPath)) {
137
+ mkdirSync(processedPath, { recursive: true });
138
+ }
139
+ }
140
+
141
+ // Persist to disk (fire and forget)
142
+ this.persistTeam(team).catch((err) => {
143
+ console.error(`Failed to persist team ${config.name}:`, err);
144
+ });
145
+
146
+ return team;
147
+ }
148
+
149
+ getTeam(name: string): Team | undefined {
150
+ return this.teams.get(name);
151
+ }
152
+
153
+ listTeams(): Team[] {
154
+ return Array.from(this.teams.values());
155
+ }
156
+
157
+ deleteTeam(name: string): void {
158
+ const team = this.teams.get(name);
159
+ if (team) {
160
+ // Remove teammates and their message queues
161
+ for (const teammate of team.teammates) {
162
+ this.teammates.delete(teammate.teammateId);
163
+ }
164
+ this.teams.delete(name);
165
+
166
+ // Delete team directory from disk (use safe path)
167
+ const teamDir = join(this.storagePath, this.getSafeTeamDir(name));
168
+ try {
169
+ rmSync(teamDir, { recursive: true, force: true });
170
+ } catch (err) {
171
+ console.error(`Failed to delete team directory ${teamDir}:`, err);
172
+ }
173
+ }
174
+ }
175
+
176
+ // ============================================
177
+ // TEAMMATE MANAGEMENT
178
+ // ============================================
179
+
180
+ getTeammate(id: string): Teammate | undefined {
181
+ return this.teammates.get(id);
182
+ }
183
+
184
+ updateTeammateStatus(id: string, status: TeammateStatus): void {
185
+ const teammate = this.teammates.get(id);
186
+ if (teammate) {
187
+ teammate.status = status;
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Add a new teammate to an existing team
193
+ */
194
+ addTeammate(teamName: string, teammate: Teammate): boolean {
195
+ const team = this.teams.get(teamName);
196
+ if (!team) return false;
197
+
198
+ // Add to team
199
+ team.teammates.push(teammate);
200
+ this.teammates.set(teammate.teammateId, teammate);
201
+
202
+ // Create inbox directories
203
+ const pendingPath = this.getPendingPath(teamName, teammate.teammateId);
204
+ const processedPath = this.getProcessedPath(teamName, teammate.teammateId);
205
+
206
+ if (!existsSync(pendingPath)) {
207
+ mkdirSync(pendingPath, { recursive: true });
208
+ }
209
+ if (!existsSync(processedPath)) {
210
+ mkdirSync(processedPath, { recursive: true });
211
+ }
212
+
213
+ // Persist updated team
214
+ this.persistTeam(team).catch((err) => {
215
+ console.error(`Failed to persist team ${teamName}:`, err);
216
+ });
217
+
218
+ return true;
219
+ }
220
+
221
+ /**
222
+ * Remove a teammate from a team
223
+ */
224
+ removeTeammate(teamName: string, teammateId: string): boolean {
225
+ const team = this.teams.get(teamName);
226
+ if (!team) return false;
227
+
228
+ // Remove from team
229
+ const index = team.teammates.findIndex(t => t.teammateId === teammateId);
230
+ if (index === -1) return false;
231
+
232
+ team.teammates.splice(index, 1);
233
+ this.teammates.delete(teammateId);
234
+
235
+ // Keep inbox directory for history (don't delete)
236
+
237
+ // Persist updated team
238
+ this.persistTeam(team).catch((err) => {
239
+ console.error(`Failed to persist team ${teamName}:`, err);
240
+ });
241
+
242
+ return true;
243
+ }
244
+
245
+ // ============================================
246
+ // SPAWNING
247
+ // ============================================
248
+
249
+ async spawnTeammate(
250
+ teammate: Teammate,
251
+ options: {
252
+ session?: string;
253
+ workingDir?: string;
254
+ } = {}
255
+ ): Promise<void> {
256
+ const { session, workingDir = process.cwd() } = options;
257
+
258
+ // Check if inside tmux
259
+ const insideTmux = !!process.env.TMUX;
260
+
261
+ if (!insideTmux) {
262
+ // Spawn in new terminal
263
+ await this.spawnInTerminal(teammate, { session, workingDir });
264
+ } else {
265
+ // Spawn in tmux pane
266
+ await this.spawnInTmux(teammate, { session, workingDir });
267
+ }
268
+
269
+ this.updateTeammateStatus(teammate.teammateId, "in_progress");
270
+ }
271
+
272
+ private async spawnInTerminal(
273
+ teammate: Teammate,
274
+ options: { session?: string; workingDir: string }
275
+ ): Promise<void> {
276
+ // Build claude command
277
+ const args = [
278
+ "bun",
279
+ "run",
280
+ "src/interfaces/ui/terminal/cli/index.ts",
281
+ "--teammate-mode",
282
+ "--agent-id",
283
+ teammate.teammateId,
284
+ "--agent-name",
285
+ teammate.name,
286
+ "--team-name",
287
+ teammate.teamName,
288
+ "--agent-color",
289
+ teammate.color,
290
+ ];
291
+
292
+ if (teammate.planModeRequired) {
293
+ args.push("--permission-mode", "plan");
294
+ }
295
+
296
+ // Use AppleScript on macOS to open new Terminal
297
+ if (process.platform === "darwin") {
298
+ const script = `
299
+ tell application "Terminal"
300
+ do script "cd ${options.workingDir} && ${args.join(" ")}"
301
+ activate
302
+ end tell
303
+ `;
304
+
305
+ spawn("osascript", ["-e", script]);
306
+ } else {
307
+ // Linux: use xterm or similar
308
+ spawn("xterm", ["-e", args.join(" ")]);
309
+ }
310
+ }
311
+
312
+ private async spawnInTmux(
313
+ teammate: Teammate,
314
+ options: { session?: string; workingDir: string }
315
+ ): Promise<void> {
316
+ const sessionName = options.session || process.env.TMUX?.split(",")[0]?.split(":")[0] || "claude";
317
+
318
+ // Create new pane
319
+ await this.tmuxCommand(["split-window", "-t", sessionName, "-c", options.workingDir]);
320
+
321
+ // Get pane ID
322
+ const paneId = await this.tmuxCommand(["display-message", "-p", "#{pane_id}"]);
323
+
324
+ if (paneId) {
325
+ teammate.paneId = paneId.trim();
326
+ }
327
+
328
+ // Send claude command
329
+ const args = [
330
+ "bun",
331
+ "run",
332
+ "src/interfaces/ui/terminal/cli/index.ts",
333
+ "--teammate-mode",
334
+ "--agent-id",
335
+ teammate.teammateId,
336
+ "--agent-name",
337
+ teammate.name,
338
+ "--team-name",
339
+ teammate.teamName,
340
+ ];
341
+
342
+ await this.tmuxCommand(["send-keys", "-t", teammate.paneId || "", args.join(" "), "Enter"]);
343
+ }
344
+
345
+ private async tmuxCommand(args: string[]): Promise<string> {
346
+ return new Promise((resolve) => {
347
+ const proc = spawn("tmux", args);
348
+ let output = "";
349
+
350
+ proc.stdout?.on("data", (data: Buffer) => {
351
+ output += data.toString();
352
+ });
353
+
354
+ proc.on("close", () => {
355
+ resolve(output);
356
+ });
357
+ });
358
+ }
359
+
360
+ // ============================================
361
+ // MESSAGING (FILE-BASED)
362
+ // ============================================
363
+
364
+ /**
365
+ * Get the team name for a teammate
366
+ */
367
+ private getTeamNameForTeammate(teammateId: string): string | undefined {
368
+ const teammate = this.teammates.get(teammateId);
369
+ return teammate?.teamName;
370
+ }
371
+
372
+ /**
373
+ * Write a message to a teammate's file-based inbox
374
+ */
375
+ private writeMessageToInbox(
376
+ teamName: string,
377
+ toId: string,
378
+ msg: StoredMessage
379
+ ): void {
380
+ const pendingPath = this.getPendingPath(teamName, toId);
381
+
382
+ // Ensure inbox exists
383
+ if (!existsSync(pendingPath)) {
384
+ mkdirSync(pendingPath, { recursive: true });
385
+ }
386
+
387
+ // Write message as JSON file
388
+ const msgPath = join(pendingPath, `${msg.id}.json`);
389
+ writeFileSync(msgPath, JSON.stringify(msg, null, 2));
390
+ }
391
+
392
+ /**
393
+ * Read all pending messages from a teammate's inbox
394
+ * Validates messages using Zod schema, skipping malformed ones
395
+ */
396
+ private readPendingMessages(teamName: string, teammateId: string): StoredMessage[] {
397
+ const pendingPath = this.getPendingPath(teamName, teammateId);
398
+ const messages: StoredMessage[] = [];
399
+
400
+ if (!existsSync(pendingPath)) {
401
+ return messages;
402
+ }
403
+
404
+ try {
405
+ const files = readdirSync(pendingPath)
406
+ .filter(f => f.endsWith('.json'))
407
+ .sort(); // Oldest first (by filename timestamp)
408
+
409
+ for (const file of files) {
410
+ try {
411
+ const msgPath = join(pendingPath, file);
412
+ const content = readFileSync(msgPath, 'utf-8');
413
+
414
+ // Parse JSON first, then validate
415
+ let parsed: unknown;
416
+ try {
417
+ parsed = JSON.parse(content);
418
+ } catch {
419
+ continue; // Skip non-JSON files
420
+ }
421
+
422
+ const result = safeParseStoredMessage(parsed);
423
+
424
+ if (result.success && result.data) {
425
+ messages.push(result.data);
426
+ } else {
427
+ // Log validation error but skip this message
428
+ console.error(`Failed to parse message from ${msgPath}:`, result.error);
429
+ }
430
+ } catch (err) {
431
+ // Skip malformed messages
432
+ console.error(`Error reading message file:`, err);
433
+ }
434
+ }
435
+ } catch (err) {
436
+ // Directory read error
437
+ console.error(`Error reading pending messages:`, err);
438
+ }
439
+
440
+ return messages;
441
+ }
442
+
443
+ /**
444
+ * Move a message from pending to processed
445
+ */
446
+ private markMessageProcessed(teamName: string, teammateId: string, msgId: string): void {
447
+ const pendingPath = this.getPendingPath(teamName, teammateId);
448
+ const processedPath = this.getProcessedPath(teamName, teammateId);
449
+
450
+ const pendingFile = join(pendingPath, `${msgId}.json`);
451
+ const processedFile = join(processedPath, `${msgId}.json`);
452
+
453
+ if (existsSync(pendingFile)) {
454
+ // Ensure processed directory exists
455
+ if (!existsSync(processedPath)) {
456
+ mkdirSync(processedPath, { recursive: true });
457
+ }
458
+
459
+ // Update message with readAt timestamp
460
+ try {
461
+ const content = readFileSync(pendingFile, 'utf-8');
462
+ const msg = JSON.parse(content) as StoredMessage;
463
+ msg.readAt = Date.now();
464
+ writeFileSync(processedFile, JSON.stringify(msg, null, 2));
465
+ rmSync(pendingFile);
466
+ } catch {
467
+ // If update fails, just move the file
468
+ try {
469
+ renameSync(pendingFile, processedFile);
470
+ } catch {
471
+ // Ignore move errors
472
+ }
473
+ }
474
+ }
475
+ }
476
+
477
+ /**
478
+ * Broadcast a message to all teammates in a team
479
+ */
480
+ broadcast(teamName: string, message: string, fromId?: string): void {
481
+ const team = this.teams.get(teamName);
482
+ if (!team) return;
483
+
484
+ const baseMsg: Omit<StoredMessage, 'id' | 'teamName' | 'createdAt'> = {
485
+ type: "broadcast",
486
+ from: fromId || "system",
487
+ content: message,
488
+ timestamp: Date.now(),
489
+ };
490
+
491
+ // Write message to each teammate's inbox
492
+ for (const teammate of team.teammates) {
493
+ // Don't send to sender
494
+ if (fromId && teammate.teammateId === fromId) continue;
495
+
496
+ const msg: StoredMessage = {
497
+ ...baseMsg,
498
+ id: this.generateMessageId(),
499
+ teamName,
500
+ createdAt: Date.now(),
501
+ to: teammate.teammateId,
502
+ };
503
+
504
+ this.writeMessageToInbox(teamName, teammate.teammateId, msg);
505
+ }
506
+ }
507
+
508
+ /**
509
+ * Send a direct message to a specific teammate
510
+ */
511
+ sendDirect(toId: string, fromId: string, message: string): void {
512
+ const teamName = this.getTeamNameForTeammate(toId);
513
+ if (!teamName) return;
514
+
515
+ const msg: StoredMessage = {
516
+ id: this.generateMessageId(),
517
+ type: "direct",
518
+ from: fromId,
519
+ to: toId,
520
+ content: message,
521
+ timestamp: Date.now(),
522
+ teamName,
523
+ createdAt: Date.now(),
524
+ };
525
+
526
+ this.writeMessageToInbox(teamName, toId, msg);
527
+ }
528
+
529
+ /**
530
+ * Inject a message as if it came from the user (for teammate integration)
531
+ * This integrates messages into conversation flow
532
+ */
533
+ injectUserMessageToTeammate(toId: string, message: string): void {
534
+ const teamName = this.getTeamNameForTeammate(toId);
535
+ if (!teamName) return;
536
+
537
+ const msg: StoredMessage = {
538
+ id: this.generateMessageId(),
539
+ type: "notification", // Use notification type for injected messages
540
+ from: "user",
541
+ to: toId,
542
+ content: message,
543
+ timestamp: Date.now(),
544
+ teamName,
545
+ createdAt: Date.now(),
546
+ };
547
+
548
+ this.writeMessageToInbox(teamName, toId, msg);
549
+ }
550
+
551
+ /**
552
+ * Retrieve and mark all messages as processed for a teammate
553
+ * Returns messages in chronological order (oldest first)
554
+ */
555
+ getMessages(teammateId: string): TeammateMessage[] {
556
+ const teamName = this.getTeamNameForTeammate(teammateId);
557
+ if (!teamName) return [];
558
+
559
+ const storedMsgs = this.readPendingMessages(teamName, teammateId);
560
+
561
+ // Mark all as processed
562
+ for (const msg of storedMsgs) {
563
+ this.markMessageProcessed(teamName, teammateId, msg.id);
564
+ }
565
+
566
+ // Convert to TeammateMessage format
567
+ return storedMsgs.map(msg => ({
568
+ type: msg.type,
569
+ from: msg.from,
570
+ to: msg.to,
571
+ content: msg.content,
572
+ timestamp: msg.timestamp,
573
+ }));
574
+ }
575
+
576
+ /**
577
+ * Check if a teammate has pending messages
578
+ */
579
+ hasMessages(teammateId: string): boolean {
580
+ const teamName = this.getTeamNameForTeammate(teammateId);
581
+ if (!teamName) return false;
582
+
583
+ const pendingPath = this.getPendingPath(teamName, teammateId);
584
+
585
+ if (!existsSync(pendingPath)) return false;
586
+
587
+ try {
588
+ const files = readdirSync(pendingPath);
589
+ return files.some(f => f.endsWith('.json'));
590
+ } catch {
591
+ return false;
592
+ }
593
+ }
594
+
595
+ /**
596
+ * Peek at messages without marking them as processed
597
+ */
598
+ peekMessages(teammateId: string): TeammateMessage[] {
599
+ const teamName = this.getTeamNameForTeammate(teammateId);
600
+ if (!teamName) return [];
601
+
602
+ const storedMsgs = this.readPendingMessages(teamName, teammateId);
603
+
604
+ return storedMsgs.map(msg => ({
605
+ type: msg.type,
606
+ from: msg.from,
607
+ to: msg.to,
608
+ content: msg.content,
609
+ timestamp: msg.timestamp,
610
+ }));
611
+ }
612
+
613
+ /**
614
+ * Clear all pending messages for a teammate (move to processed)
615
+ */
616
+ clearMessages(teammateId: string): void {
617
+ const teamName = this.getTeamNameForTeammate(teammateId);
618
+ if (!teamName) return;
619
+
620
+ const pendingPath = this.getPendingPath(teamName, teammateId);
621
+
622
+ if (!existsSync(pendingPath)) return;
623
+
624
+ const storedMsgs = this.readPendingMessages(teamName, teammateId);
625
+ for (const msg of storedMsgs) {
626
+ this.markMessageProcessed(teamName, teammateId, msg.id);
627
+ }
628
+ }
629
+
630
+ /**
631
+ * Get count of pending messages for a teammate
632
+ */
633
+ getMessageCount(teammateId: string): number {
634
+ const teamName = this.getTeamNameForTeammate(teammateId);
635
+ if (!teamName) return 0;
636
+
637
+ const pendingPath = this.getPendingPath(teamName, teammateId);
638
+
639
+ if (!existsSync(pendingPath)) return 0;
640
+
641
+ try {
642
+ const files = readdirSync(pendingPath);
643
+ return files.filter(f => f.endsWith('.json')).length;
644
+ } catch {
645
+ return 0;
646
+ }
647
+ }
648
+
649
+ /**
650
+ * Get processed messages (history) for a teammate
651
+ */
652
+ getProcessedMessages(teammateId: string, limit = 100): StoredMessage[] {
653
+ const teamName = this.getTeamNameForTeammate(teammateId);
654
+ if (!teamName) return [];
655
+
656
+ const processedPath = this.getProcessedPath(teamName, teammateId);
657
+ const messages: StoredMessage[] = [];
658
+
659
+ if (!existsSync(processedPath)) {
660
+ return messages;
661
+ }
662
+
663
+ try {
664
+ const files = readdirSync(processedPath)
665
+ .filter(f => f.endsWith('.json'))
666
+ .sort()
667
+ .reverse() // Newest first
668
+ .slice(0, limit);
669
+
670
+ for (const file of files) {
671
+ try {
672
+ const msgPath = join(processedPath, file);
673
+ const content = readFileSync(msgPath, 'utf-8');
674
+ messages.push(JSON.parse(content) as StoredMessage);
675
+ } catch {
676
+ // Skip malformed
677
+ }
678
+ }
679
+ } catch {
680
+ // Directory read error
681
+ }
682
+
683
+ return messages;
684
+ }
685
+
686
+ /**
687
+ * Clean up old processed messages (older than maxAgeMs)
688
+ */
689
+ cleanupProcessedMessages(teammateId: string, maxAgeMs = 7 * 24 * 60 * 60 * 1000): number {
690
+ const teamName = this.getTeamNameForTeammate(teammateId);
691
+ if (!teamName) return 0;
692
+
693
+ const processedPath = this.getProcessedPath(teamName, teammateId);
694
+ const cutoff = Date.now() - maxAgeMs;
695
+ let deleted = 0;
696
+
697
+ if (!existsSync(processedPath)) return 0;
698
+
699
+ try {
700
+ const files = readdirSync(processedPath).filter(f => f.endsWith('.json'));
701
+
702
+ for (const file of files) {
703
+ try {
704
+ const msgPath = join(processedPath, file);
705
+ const content = readFileSync(msgPath, 'utf-8');
706
+ const msg = JSON.parse(content) as StoredMessage;
707
+
708
+ if (msg.readAt && msg.readAt < cutoff) {
709
+ rmSync(msgPath);
710
+ deleted++;
711
+ }
712
+ } catch {
713
+ // Skip errors
714
+ }
715
+ }
716
+ } catch {
717
+ // Directory read error
718
+ }
719
+
720
+ return deleted;
721
+ }
722
+
723
+ /**
724
+ * Wait for all teammates in a team to become idle
725
+ * Returns when all teammates have status 'idle', 'completed', or 'failed'
726
+ */
727
+ async waitForTeammatesToBecomeIdle(
728
+ teamName: string,
729
+ options: { timeout?: number; pollInterval?: number } = {}
730
+ ): Promise<{ success: boolean; timedOut: boolean; statuses: Record<string, TeammateStatus> }> {
731
+ const { timeout = 60000, pollInterval = 1000 } = options;
732
+ const startTime = Date.now();
733
+ const idleStatuses: TeammateStatus[] = ['idle', 'completed', 'failed'];
734
+
735
+ while (true) {
736
+ const team = this.teams.get(teamName);
737
+ if (!team) {
738
+ return { success: false, timedOut: false, statuses: {} };
739
+ }
740
+
741
+ const statuses: Record<string, TeammateStatus> = {};
742
+ let allIdle = true;
743
+
744
+ for (const teammate of team.teammates) {
745
+ statuses[teammate.teammateId] = teammate.status;
746
+ if (!idleStatuses.includes(teammate.status)) {
747
+ allIdle = false;
748
+ }
749
+ }
750
+
751
+ if (allIdle) {
752
+ return { success: true, timedOut: false, statuses };
753
+ }
754
+
755
+ // Check timeout
756
+ if (Date.now() - startTime > timeout) {
757
+ return { success: false, timedOut: true, statuses };
758
+ }
759
+
760
+ // Wait before polling again
761
+ await new Promise(resolve => setTimeout(resolve, pollInterval));
762
+ }
763
+ }
764
+
765
+ /**
766
+ * Get inbox statistics for a teammate
767
+ */
768
+ getInboxStats(teammateId: string): {
769
+ pending: number;
770
+ processed: number;
771
+ oldestPending?: number;
772
+ newestPending?: number;
773
+ } {
774
+ const teamName = this.getTeamNameForTeammate(teammateId);
775
+ if (!teamName) return { pending: 0, processed: 0 };
776
+
777
+ const pendingPath = this.getPendingPath(teamName, teammateId);
778
+ const processedPath = this.getProcessedPath(teamName, teammateId);
779
+
780
+ let pending = 0;
781
+ let processed = 0;
782
+ let oldestPending: number | undefined;
783
+ let newestPending: number | undefined;
784
+
785
+ // Count pending
786
+ if (existsSync(pendingPath)) {
787
+ try {
788
+ const files = readdirSync(pendingPath).filter(f => f.endsWith('.json'));
789
+ pending = files.length;
790
+
791
+ for (const file of files) {
792
+ try {
793
+ const msgPath = join(pendingPath, file);
794
+ const content = readFileSync(msgPath, 'utf-8');
795
+ const msg = JSON.parse(content) as StoredMessage;
796
+
797
+ if (!oldestPending || msg.createdAt < oldestPending) {
798
+ oldestPending = msg.createdAt;
799
+ }
800
+ if (!newestPending || msg.createdAt > newestPending) {
801
+ newestPending = msg.createdAt;
802
+ }
803
+ } catch {
804
+ // Skip
805
+ }
806
+ }
807
+ } catch {
808
+ // Skip
809
+ }
810
+ }
811
+
812
+ // Count processed
813
+ if (existsSync(processedPath)) {
814
+ try {
815
+ const files = readdirSync(processedPath).filter(f => f.endsWith('.json'));
816
+ processed = files.length;
817
+ } catch {
818
+ // Skip
819
+ }
820
+ }
821
+
822
+ return { pending, processed, oldestPending, newestPending };
823
+ }
824
+
825
+ // ============================================
826
+ // PERSISTENCE
827
+ // ============================================
828
+
829
+ /**
830
+ * Persist a team configuration to disk
831
+ */
832
+ private async persistTeam(team: Team): Promise<void> {
833
+ const teamDir = join(this.storagePath, this.getSafeTeamDir(team.name));
834
+ const configPath = join(teamDir, "config.json");
835
+
836
+ // Ensure directory exists
837
+ if (!existsSync(teamDir)) {
838
+ mkdirSync(teamDir, { recursive: true });
839
+ }
840
+
841
+ // Write .gitkeep to ensure directory is tracked
842
+ await Bun.write(join(teamDir, ".gitkeep"), "");
843
+
844
+ // Build config object
845
+ const config = {
846
+ name: team.name,
847
+ description: team.description,
848
+ teammates: team.teammates,
849
+ taskListId: team.taskListId,
850
+ status: team.status,
851
+ coordination: team.coordination,
852
+ updatedAt: Date.now(),
853
+ };
854
+
855
+ // Write config as formatted JSON
856
+ await Bun.write(configPath, JSON.stringify(config, null, 2));
857
+ }
858
+
859
+ /**
860
+ * Load all teams from disk at startup
861
+ * Uses synchronous operations for constructor compatibility
862
+ * Validates all data using Zod schemas
863
+ */
864
+ loadTeams(): void {
865
+ // Use Bun's glob to find team configs
866
+ const glob = new Bun.Glob("**/config.json");
867
+
868
+ try {
869
+ const files = Array.from(glob.scanSync(this.storagePath));
870
+ for (const file of files) {
871
+ try {
872
+ const filePath = join(this.storagePath, file);
873
+ const content = Bun.file(filePath);
874
+
875
+ // Check if file exists and is readable (sync check via size)
876
+ const size = content.size;
877
+ if (size === 0) {
878
+ continue;
879
+ }
880
+
881
+ // Read file synchronously using readFileSync
882
+ // Bun.file().text() is async, so we use fs.readFileSync for sync operation
883
+ const text = readFileSync(filePath, "utf-8");
884
+
885
+ // Parse JSON first, then validate with Zod schema
886
+ let parsed: unknown;
887
+ try {
888
+ parsed = JSON.parse(text);
889
+ } catch (parseError) {
890
+ console.error(`Failed to parse JSON from ${filePath}:`, parseError);
891
+ continue;
892
+ }
893
+
894
+ // Validate using Zod schema
895
+ const result = safeParseTeam(parsed);
896
+
897
+ if (!result.success || !result.data) {
898
+ // Log validation error but skip this config
899
+ console.error(`Failed to load team config from ${filePath}:`, result.error);
900
+ continue;
901
+ }
902
+
903
+ const team = result.data;
904
+ this.teams.set(team.name, team);
905
+
906
+ // Index teammates
907
+ for (const teammate of team.teammates) {
908
+ this.teammates.set(teammate.teammateId, teammate);
909
+ }
910
+ } catch (error) {
911
+ // Silently skip malformed configs
912
+ console.error(`Error reading team config:`, error);
913
+ }
914
+ }
915
+ } catch (error) {
916
+ // Storage path may not exist yet - that's okay
917
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
918
+ console.error("Error loading teams:", error);
919
+ }
920
+ }
921
+ }
922
+
923
+ /**
924
+ * Persist all teams to disk (useful for shutdown)
925
+ */
926
+ async persistAllTeams(): Promise<void> {
927
+ const promises = Array.from(this.teams.values()).map((team) => this.persistTeam(team));
928
+ await Promise.all(promises);
929
+ }
930
+ }
931
+
932
+ // ============================================
933
+ // TEAMMATE TEMPLATES
934
+ // ============================================
935
+
936
+ export const teammateTemplates = {
937
+ /**
938
+ * Architect - Plans and designs
939
+ */
940
+ architect: (teamName: string): Omit<Teammate, "teammateId"> => ({
941
+ name: "architect",
942
+ teamName,
943
+ color: "blue",
944
+ prompt: `You are an architect on the ${teamName} team.
945
+ Your role is to design and plan the technical architecture.
946
+ Focus on:
947
+ - System design and component relationships
948
+ - API contracts and interfaces
949
+ - Data models and schemas
950
+ - Trade-offs and design decisions`,
951
+ planModeRequired: true,
952
+ status: "pending",
953
+ }),
954
+
955
+ /**
956
+ * Implementer - Writes code
957
+ */
958
+ implementer: (teamName: string): Omit<Teammate, "teammateId"> => ({
959
+ name: "implementer",
960
+ teamName,
961
+ color: "green",
962
+ prompt: `You are an implementer on the ${teamName} team.
963
+ Your role is to write clean, working code based on the architecture.
964
+ Focus on:
965
+ - Implementing the designed architecture
966
+ - Writing tests
967
+ - Following coding standards
968
+ - Handling edge cases`,
969
+ planModeRequired: false,
970
+ status: "pending",
971
+ }),
972
+
973
+ /**
974
+ * Reviewer - Reviews code
975
+ */
976
+ reviewer: (teamName: string): Omit<Teammate, "teammateId"> => ({
977
+ name: "reviewer",
978
+ teamName,
979
+ color: "yellow",
980
+ prompt: `You are a code reviewer on the ${teamName} team.
981
+ Your role is to review code changes and provide feedback.
982
+ Focus on:
983
+ - Code quality and readability
984
+ - Potential bugs and issues
985
+ - Performance considerations
986
+ - Test coverage`,
987
+ planModeRequired: false,
988
+ status: "pending",
989
+ }),
990
+
991
+ /**
992
+ * Tester - Tests features
993
+ */
994
+ tester: (teamName: string): Omit<Teammate, "teammateId"> => ({
995
+ name: "tester",
996
+ teamName,
997
+ color: "orange",
998
+ prompt: `You are a tester on the ${teamName} team.
999
+ Your role is to ensure features work correctly.
1000
+ Focus on:
1001
+ - Writing comprehensive tests
1002
+ - Finding edge cases
1003
+ - Verifying requirements
1004
+ - Reporting bugs`,
1005
+ planModeRequired: false,
1006
+ status: "pending",
1007
+ }),
1008
+ };
1009
+
1010
+ // ============================================
1011
+ // HELPER FUNCTIONS
1012
+ // ============================================
1013
+
1014
+ export function generateTeammateId(): string {
1015
+ return `teammate_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
1016
+ }
1017
+
1018
+ export function createTeammate(
1019
+ config: Omit<Teammate, "teammateId" | "status">
1020
+ ): Teammate {
1021
+ return {
1022
+ ...config,
1023
+ teammateId: generateTeammateId(),
1024
+ status: "pending",
1025
+ };
1026
+ }
1027
+
1028
+ // Export types
1029
+ export type { StoredMessage };
1030
+
1031
+ // Re-export runner module
1032
+ export {
1033
+ TeammateModeRunner,
1034
+ getTeammateRunner,
1035
+ setTeammateRunner,
1036
+ isTeammateModeActive,
1037
+ type TeammateModeConfig,
1038
+ type TeammateModeState,
1039
+ } from "./runner.js";
1040
+
1041
+ // Re-export coordination module
1042
+ export {
1043
+ CoordinationManager,
1044
+ createCoordinationMessage,
1045
+ parseCoordinationMessage,
1046
+ type CoordinationEvent,
1047
+ type CoordinationEventType,
1048
+ type CoordinationCallback,
1049
+ type CoordinationConfig,
1050
+ type ProgressReport,
1051
+ type FileClaim,
1052
+ } from "./coordination.js";