whale-code 6.4.0

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 (319) hide show
  1. package/README.md +95 -0
  2. package/bin/swag-agent.js +9 -0
  3. package/bin/swagmanager-mcp.js +321 -0
  4. package/dist/cli/app.d.ts +26 -0
  5. package/dist/cli/app.js +64 -0
  6. package/dist/cli/chat/AgentSelector.d.ts +14 -0
  7. package/dist/cli/chat/AgentSelector.js +14 -0
  8. package/dist/cli/chat/ChatApp.d.ts +9 -0
  9. package/dist/cli/chat/ChatApp.js +267 -0
  10. package/dist/cli/chat/ChatInput.d.ts +39 -0
  11. package/dist/cli/chat/ChatInput.js +509 -0
  12. package/dist/cli/chat/MarkdownText.d.ts +10 -0
  13. package/dist/cli/chat/MarkdownText.js +20 -0
  14. package/dist/cli/chat/MessageList.d.ts +37 -0
  15. package/dist/cli/chat/MessageList.js +80 -0
  16. package/dist/cli/chat/ModelSelector.d.ts +20 -0
  17. package/dist/cli/chat/ModelSelector.js +73 -0
  18. package/dist/cli/chat/RewindViewer.d.ts +26 -0
  19. package/dist/cli/chat/RewindViewer.js +185 -0
  20. package/dist/cli/chat/StoreSelector.d.ts +14 -0
  21. package/dist/cli/chat/StoreSelector.js +24 -0
  22. package/dist/cli/chat/StreamingText.d.ts +12 -0
  23. package/dist/cli/chat/StreamingText.js +12 -0
  24. package/dist/cli/chat/SubagentPanel.d.ts +45 -0
  25. package/dist/cli/chat/SubagentPanel.js +110 -0
  26. package/dist/cli/chat/TeamPanel.d.ts +21 -0
  27. package/dist/cli/chat/TeamPanel.js +42 -0
  28. package/dist/cli/chat/ToolIndicator.d.ts +25 -0
  29. package/dist/cli/chat/ToolIndicator.js +436 -0
  30. package/dist/cli/chat/hooks/useAgentLoop.d.ts +39 -0
  31. package/dist/cli/chat/hooks/useAgentLoop.js +382 -0
  32. package/dist/cli/chat/hooks/useSlashCommands.d.ts +37 -0
  33. package/dist/cli/chat/hooks/useSlashCommands.js +387 -0
  34. package/dist/cli/commands/config-cmd.d.ts +10 -0
  35. package/dist/cli/commands/config-cmd.js +99 -0
  36. package/dist/cli/commands/doctor.d.ts +14 -0
  37. package/dist/cli/commands/doctor.js +172 -0
  38. package/dist/cli/commands/init.d.ts +16 -0
  39. package/dist/cli/commands/init.js +278 -0
  40. package/dist/cli/commands/mcp.d.ts +12 -0
  41. package/dist/cli/commands/mcp.js +162 -0
  42. package/dist/cli/login/LoginApp.d.ts +7 -0
  43. package/dist/cli/login/LoginApp.js +157 -0
  44. package/dist/cli/print-mode.d.ts +31 -0
  45. package/dist/cli/print-mode.js +202 -0
  46. package/dist/cli/serve-mode.d.ts +37 -0
  47. package/dist/cli/serve-mode.js +636 -0
  48. package/dist/cli/services/agent-definitions.d.ts +25 -0
  49. package/dist/cli/services/agent-definitions.js +91 -0
  50. package/dist/cli/services/agent-events.d.ts +178 -0
  51. package/dist/cli/services/agent-events.js +175 -0
  52. package/dist/cli/services/agent-loop.d.ts +90 -0
  53. package/dist/cli/services/agent-loop.js +762 -0
  54. package/dist/cli/services/agent-worker-base.d.ts +97 -0
  55. package/dist/cli/services/agent-worker-base.js +220 -0
  56. package/dist/cli/services/auth-service.d.ts +30 -0
  57. package/dist/cli/services/auth-service.js +160 -0
  58. package/dist/cli/services/background-processes.d.ts +126 -0
  59. package/dist/cli/services/background-processes.js +318 -0
  60. package/dist/cli/services/browser-auth.d.ts +24 -0
  61. package/dist/cli/services/browser-auth.js +180 -0
  62. package/dist/cli/services/claude-md-loader.d.ts +16 -0
  63. package/dist/cli/services/claude-md-loader.js +58 -0
  64. package/dist/cli/services/config-store.d.ts +47 -0
  65. package/dist/cli/services/config-store.js +79 -0
  66. package/dist/cli/services/debug-log.d.ts +10 -0
  67. package/dist/cli/services/debug-log.js +52 -0
  68. package/dist/cli/services/error-logger.d.ts +58 -0
  69. package/dist/cli/services/error-logger.js +269 -0
  70. package/dist/cli/services/file-history.d.ts +21 -0
  71. package/dist/cli/services/file-history.js +83 -0
  72. package/dist/cli/services/format-server-response.d.ts +16 -0
  73. package/dist/cli/services/format-server-response.js +440 -0
  74. package/dist/cli/services/git-context.d.ts +11 -0
  75. package/dist/cli/services/git-context.js +66 -0
  76. package/dist/cli/services/hooks.d.ts +85 -0
  77. package/dist/cli/services/hooks.js +258 -0
  78. package/dist/cli/services/interactive-tools.d.ts +125 -0
  79. package/dist/cli/services/interactive-tools.js +260 -0
  80. package/dist/cli/services/keybinding-manager.d.ts +52 -0
  81. package/dist/cli/services/keybinding-manager.js +115 -0
  82. package/dist/cli/services/local-tools.d.ts +22 -0
  83. package/dist/cli/services/local-tools.js +697 -0
  84. package/dist/cli/services/lsp-manager.d.ts +18 -0
  85. package/dist/cli/services/lsp-manager.js +717 -0
  86. package/dist/cli/services/mcp-client.d.ts +48 -0
  87. package/dist/cli/services/mcp-client.js +157 -0
  88. package/dist/cli/services/memory-manager.d.ts +16 -0
  89. package/dist/cli/services/memory-manager.js +57 -0
  90. package/dist/cli/services/model-manager.d.ts +18 -0
  91. package/dist/cli/services/model-manager.js +71 -0
  92. package/dist/cli/services/model-router.d.ts +26 -0
  93. package/dist/cli/services/model-router.js +149 -0
  94. package/dist/cli/services/permission-modes.d.ts +13 -0
  95. package/dist/cli/services/permission-modes.js +43 -0
  96. package/dist/cli/services/rewind.d.ts +84 -0
  97. package/dist/cli/services/rewind.js +194 -0
  98. package/dist/cli/services/ripgrep.d.ts +28 -0
  99. package/dist/cli/services/ripgrep.js +138 -0
  100. package/dist/cli/services/sandbox.d.ts +29 -0
  101. package/dist/cli/services/sandbox.js +97 -0
  102. package/dist/cli/services/server-tools.d.ts +61 -0
  103. package/dist/cli/services/server-tools.js +543 -0
  104. package/dist/cli/services/session-persistence.d.ts +23 -0
  105. package/dist/cli/services/session-persistence.js +99 -0
  106. package/dist/cli/services/subagent-worker.d.ts +19 -0
  107. package/dist/cli/services/subagent-worker.js +41 -0
  108. package/dist/cli/services/subagent.d.ts +47 -0
  109. package/dist/cli/services/subagent.js +647 -0
  110. package/dist/cli/services/system-prompt.d.ts +7 -0
  111. package/dist/cli/services/system-prompt.js +198 -0
  112. package/dist/cli/services/team-lead.d.ts +73 -0
  113. package/dist/cli/services/team-lead.js +512 -0
  114. package/dist/cli/services/team-state.d.ts +77 -0
  115. package/dist/cli/services/team-state.js +398 -0
  116. package/dist/cli/services/teammate.d.ts +31 -0
  117. package/dist/cli/services/teammate.js +689 -0
  118. package/dist/cli/services/telemetry.d.ts +61 -0
  119. package/dist/cli/services/telemetry.js +209 -0
  120. package/dist/cli/services/tools/agent-tools.d.ts +14 -0
  121. package/dist/cli/services/tools/agent-tools.js +347 -0
  122. package/dist/cli/services/tools/file-ops.d.ts +15 -0
  123. package/dist/cli/services/tools/file-ops.js +487 -0
  124. package/dist/cli/services/tools/search-tools.d.ts +8 -0
  125. package/dist/cli/services/tools/search-tools.js +186 -0
  126. package/dist/cli/services/tools/shell-exec.d.ts +10 -0
  127. package/dist/cli/services/tools/shell-exec.js +168 -0
  128. package/dist/cli/services/tools/task-manager.d.ts +28 -0
  129. package/dist/cli/services/tools/task-manager.js +209 -0
  130. package/dist/cli/services/tools/web-tools.d.ts +11 -0
  131. package/dist/cli/services/tools/web-tools.js +395 -0
  132. package/dist/cli/setup/SetupApp.d.ts +9 -0
  133. package/dist/cli/setup/SetupApp.js +191 -0
  134. package/dist/cli/shared/MatrixIntro.d.ts +4 -0
  135. package/dist/cli/shared/MatrixIntro.js +83 -0
  136. package/dist/cli/shared/Theme.d.ts +74 -0
  137. package/dist/cli/shared/Theme.js +127 -0
  138. package/dist/cli/shared/WhaleBanner.d.ts +10 -0
  139. package/dist/cli/shared/WhaleBanner.js +12 -0
  140. package/dist/cli/shared/markdown.d.ts +21 -0
  141. package/dist/cli/shared/markdown.js +756 -0
  142. package/dist/cli/status/StatusApp.d.ts +4 -0
  143. package/dist/cli/status/StatusApp.js +105 -0
  144. package/dist/cli/stores/StoreApp.d.ts +7 -0
  145. package/dist/cli/stores/StoreApp.js +81 -0
  146. package/dist/index.d.ts +15 -0
  147. package/dist/index.js +538 -0
  148. package/dist/local-agent/connection.d.ts +48 -0
  149. package/dist/local-agent/connection.js +332 -0
  150. package/dist/local-agent/discovery.d.ts +18 -0
  151. package/dist/local-agent/discovery.js +146 -0
  152. package/dist/local-agent/executor.d.ts +34 -0
  153. package/dist/local-agent/executor.js +241 -0
  154. package/dist/local-agent/index.d.ts +14 -0
  155. package/dist/local-agent/index.js +198 -0
  156. package/dist/node/adapters/base.d.ts +35 -0
  157. package/dist/node/adapters/base.js +10 -0
  158. package/dist/node/adapters/discord.d.ts +29 -0
  159. package/dist/node/adapters/discord.js +299 -0
  160. package/dist/node/adapters/email.d.ts +23 -0
  161. package/dist/node/adapters/email.js +218 -0
  162. package/dist/node/adapters/imessage.d.ts +17 -0
  163. package/dist/node/adapters/imessage.js +118 -0
  164. package/dist/node/adapters/slack.d.ts +26 -0
  165. package/dist/node/adapters/slack.js +259 -0
  166. package/dist/node/adapters/sms.d.ts +23 -0
  167. package/dist/node/adapters/sms.js +161 -0
  168. package/dist/node/adapters/telegram.d.ts +17 -0
  169. package/dist/node/adapters/telegram.js +101 -0
  170. package/dist/node/adapters/webchat.d.ts +27 -0
  171. package/dist/node/adapters/webchat.js +160 -0
  172. package/dist/node/adapters/whatsapp.d.ts +28 -0
  173. package/dist/node/adapters/whatsapp.js +230 -0
  174. package/dist/node/cli.d.ts +2 -0
  175. package/dist/node/cli.js +325 -0
  176. package/dist/node/config.d.ts +17 -0
  177. package/dist/node/config.js +31 -0
  178. package/dist/node/runtime.d.ts +50 -0
  179. package/dist/node/runtime.js +351 -0
  180. package/dist/server/handlers/__test-utils__/mock-supabase.d.ts +11 -0
  181. package/dist/server/handlers/__test-utils__/mock-supabase.js +393 -0
  182. package/dist/server/handlers/analytics.d.ts +17 -0
  183. package/dist/server/handlers/analytics.js +266 -0
  184. package/dist/server/handlers/api-keys.d.ts +6 -0
  185. package/dist/server/handlers/api-keys.js +221 -0
  186. package/dist/server/handlers/billing.d.ts +33 -0
  187. package/dist/server/handlers/billing.js +272 -0
  188. package/dist/server/handlers/browser.d.ts +10 -0
  189. package/dist/server/handlers/browser.js +517 -0
  190. package/dist/server/handlers/catalog.d.ts +99 -0
  191. package/dist/server/handlers/catalog.js +976 -0
  192. package/dist/server/handlers/comms.d.ts +254 -0
  193. package/dist/server/handlers/comms.js +588 -0
  194. package/dist/server/handlers/creations.d.ts +6 -0
  195. package/dist/server/handlers/creations.js +479 -0
  196. package/dist/server/handlers/crm.d.ts +89 -0
  197. package/dist/server/handlers/crm.js +538 -0
  198. package/dist/server/handlers/discovery.d.ts +6 -0
  199. package/dist/server/handlers/discovery.js +288 -0
  200. package/dist/server/handlers/embeddings.d.ts +92 -0
  201. package/dist/server/handlers/embeddings.js +197 -0
  202. package/dist/server/handlers/enrichment.d.ts +8 -0
  203. package/dist/server/handlers/enrichment.js +768 -0
  204. package/dist/server/handlers/image-gen.d.ts +6 -0
  205. package/dist/server/handlers/image-gen.js +409 -0
  206. package/dist/server/handlers/inventory.d.ts +319 -0
  207. package/dist/server/handlers/inventory.js +447 -0
  208. package/dist/server/handlers/kali.d.ts +10 -0
  209. package/dist/server/handlers/kali.js +210 -0
  210. package/dist/server/handlers/llm-providers.d.ts +6 -0
  211. package/dist/server/handlers/llm-providers.js +673 -0
  212. package/dist/server/handlers/local-agent.d.ts +6 -0
  213. package/dist/server/handlers/local-agent.js +118 -0
  214. package/dist/server/handlers/meta-ads.d.ts +111 -0
  215. package/dist/server/handlers/meta-ads.js +2279 -0
  216. package/dist/server/handlers/nodes.d.ts +33 -0
  217. package/dist/server/handlers/nodes.js +699 -0
  218. package/dist/server/handlers/operations.d.ts +138 -0
  219. package/dist/server/handlers/operations.js +131 -0
  220. package/dist/server/handlers/platform.d.ts +23 -0
  221. package/dist/server/handlers/platform.js +227 -0
  222. package/dist/server/handlers/supply-chain.d.ts +19 -0
  223. package/dist/server/handlers/supply-chain.js +327 -0
  224. package/dist/server/handlers/transcription.d.ts +17 -0
  225. package/dist/server/handlers/transcription.js +121 -0
  226. package/dist/server/handlers/video-gen.d.ts +6 -0
  227. package/dist/server/handlers/video-gen.js +466 -0
  228. package/dist/server/handlers/voice.d.ts +8 -0
  229. package/dist/server/handlers/voice.js +1146 -0
  230. package/dist/server/handlers/workflow-steps.d.ts +86 -0
  231. package/dist/server/handlers/workflow-steps.js +2349 -0
  232. package/dist/server/handlers/workflows.d.ts +7 -0
  233. package/dist/server/handlers/workflows.js +989 -0
  234. package/dist/server/index.d.ts +1 -0
  235. package/dist/server/index.js +2427 -0
  236. package/dist/server/lib/batch-client.d.ts +80 -0
  237. package/dist/server/lib/batch-client.js +467 -0
  238. package/dist/server/lib/code-worker-pool.d.ts +31 -0
  239. package/dist/server/lib/code-worker-pool.js +224 -0
  240. package/dist/server/lib/code-worker.d.ts +1 -0
  241. package/dist/server/lib/code-worker.js +188 -0
  242. package/dist/server/lib/compaction-service.d.ts +32 -0
  243. package/dist/server/lib/compaction-service.js +162 -0
  244. package/dist/server/lib/logger.d.ts +19 -0
  245. package/dist/server/lib/logger.js +46 -0
  246. package/dist/server/lib/otel.d.ts +38 -0
  247. package/dist/server/lib/otel.js +126 -0
  248. package/dist/server/lib/pg-rate-limiter.d.ts +21 -0
  249. package/dist/server/lib/pg-rate-limiter.js +86 -0
  250. package/dist/server/lib/prompt-sanitizer.d.ts +37 -0
  251. package/dist/server/lib/prompt-sanitizer.js +177 -0
  252. package/dist/server/lib/provider-capabilities.d.ts +85 -0
  253. package/dist/server/lib/provider-capabilities.js +190 -0
  254. package/dist/server/lib/provider-failover.d.ts +74 -0
  255. package/dist/server/lib/provider-failover.js +210 -0
  256. package/dist/server/lib/rate-limiter.d.ts +39 -0
  257. package/dist/server/lib/rate-limiter.js +147 -0
  258. package/dist/server/lib/server-agent-loop.d.ts +107 -0
  259. package/dist/server/lib/server-agent-loop.js +667 -0
  260. package/dist/server/lib/server-subagent.d.ts +78 -0
  261. package/dist/server/lib/server-subagent.js +203 -0
  262. package/dist/server/lib/session-checkpoint.d.ts +51 -0
  263. package/dist/server/lib/session-checkpoint.js +145 -0
  264. package/dist/server/lib/ssrf-guard.d.ts +13 -0
  265. package/dist/server/lib/ssrf-guard.js +240 -0
  266. package/dist/server/lib/supabase-client.d.ts +7 -0
  267. package/dist/server/lib/supabase-client.js +78 -0
  268. package/dist/server/lib/template-resolver.d.ts +31 -0
  269. package/dist/server/lib/template-resolver.js +215 -0
  270. package/dist/server/lib/utils.d.ts +16 -0
  271. package/dist/server/lib/utils.js +147 -0
  272. package/dist/server/local-agent-gateway.d.ts +82 -0
  273. package/dist/server/local-agent-gateway.js +426 -0
  274. package/dist/server/providers/anthropic.d.ts +20 -0
  275. package/dist/server/providers/anthropic.js +199 -0
  276. package/dist/server/providers/bedrock.d.ts +20 -0
  277. package/dist/server/providers/bedrock.js +194 -0
  278. package/dist/server/providers/gemini.d.ts +24 -0
  279. package/dist/server/providers/gemini.js +486 -0
  280. package/dist/server/providers/openai.d.ts +24 -0
  281. package/dist/server/providers/openai.js +522 -0
  282. package/dist/server/providers/registry.d.ts +32 -0
  283. package/dist/server/providers/registry.js +58 -0
  284. package/dist/server/providers/shared.d.ts +32 -0
  285. package/dist/server/providers/shared.js +124 -0
  286. package/dist/server/providers/types.d.ts +92 -0
  287. package/dist/server/providers/types.js +12 -0
  288. package/dist/server/proxy-handlers.d.ts +6 -0
  289. package/dist/server/proxy-handlers.js +89 -0
  290. package/dist/server/tool-router.d.ts +149 -0
  291. package/dist/server/tool-router.js +803 -0
  292. package/dist/server/validation.d.ts +24 -0
  293. package/dist/server/validation.js +301 -0
  294. package/dist/server/worker.d.ts +19 -0
  295. package/dist/server/worker.js +201 -0
  296. package/dist/setup.d.ts +8 -0
  297. package/dist/setup.js +181 -0
  298. package/dist/shared/agent-core.d.ts +157 -0
  299. package/dist/shared/agent-core.js +534 -0
  300. package/dist/shared/anthropic-types.d.ts +105 -0
  301. package/dist/shared/anthropic-types.js +7 -0
  302. package/dist/shared/api-client.d.ts +90 -0
  303. package/dist/shared/api-client.js +379 -0
  304. package/dist/shared/constants.d.ts +33 -0
  305. package/dist/shared/constants.js +80 -0
  306. package/dist/shared/sse-parser.d.ts +26 -0
  307. package/dist/shared/sse-parser.js +259 -0
  308. package/dist/shared/tool-dispatch.d.ts +52 -0
  309. package/dist/shared/tool-dispatch.js +191 -0
  310. package/dist/shared/types.d.ts +72 -0
  311. package/dist/shared/types.js +7 -0
  312. package/dist/updater.d.ts +25 -0
  313. package/dist/updater.js +140 -0
  314. package/dist/webchat/widget.d.ts +0 -0
  315. package/dist/webchat/widget.js +397 -0
  316. package/package.json +95 -0
  317. package/src/cli/services/builtin-skills/commit.md +19 -0
  318. package/src/cli/services/builtin-skills/review-pr.md +21 -0
  319. package/src/cli/services/builtin-skills/review.md +18 -0
@@ -0,0 +1,512 @@
1
+ /**
2
+ * Team Lead — Coordinator for Agent Teams
3
+ *
4
+ * Following Anthropic's official patterns:
5
+ * - Spawns and manages teammates
6
+ * - Creates and assigns tasks
7
+ * - Monitors progress
8
+ * - Synthesizes results
9
+ */
10
+ import { EventEmitter } from "events";
11
+ import { createTeam, loadTeam, saveTeam, addTeammate, addTask, getTeamProgress, setTeamStatus, sendMessage, getUnreadMessages, markMessagesRead, failTask, updateTeammate, } from "./team-state.js";
12
+ import { spawnTeammate } from "./teammate.js";
13
+ import { logSpan, generateTraceId, generateSpanId, getConversationId } from "./telemetry.js";
14
+ import { resolveConfig } from "./config-store.js";
15
+ import { getGlobalEmitter } from "./agent-events.js";
16
+ import { getModelShortName } from "./agent-loop.js";
17
+ // ============================================================================
18
+ // TEAM LEAD CLASS
19
+ // ============================================================================
20
+ const TEAM_TIMEOUT_MS = 5 * 60 * 1000; // 5 min total
21
+ const WORKER_STALL_MS = 2 * 60 * 1000; // 2 min no messages
22
+ export class TeamLead extends EventEmitter {
23
+ teamId = null;
24
+ workers = new Map();
25
+ lastMessageTime = new Map();
26
+ teamTimer = null;
27
+ stallInterval = null;
28
+ resolveTeam = null;
29
+ traceId;
30
+ spanId;
31
+ startTime = 0;
32
+ constructor() {
33
+ super();
34
+ this.traceId = generateTraceId();
35
+ this.spanId = generateSpanId();
36
+ }
37
+ /**
38
+ * Generate meaningful teammate names from task descriptions
39
+ * Extracts key action words to create role-based names
40
+ */
41
+ generateTeammateNames(tasks, count) {
42
+ const roles = [
43
+ "Researcher", "Analyst", "Writer", "Reviewer",
44
+ "Coordinator", "Specialist", "Explorer", "Synthesizer"
45
+ ];
46
+ // Extract keywords from task descriptions
47
+ const names = [];
48
+ const usedRoles = new Set();
49
+ for (let i = 0; i < count; i++) {
50
+ if (i < tasks.length) {
51
+ const desc = tasks[i].description.toLowerCase();
52
+ // Match task keywords to roles
53
+ let role;
54
+ if (desc.includes("research") || desc.includes("search") || desc.includes("find")) {
55
+ role = "Researcher";
56
+ }
57
+ else if (desc.includes("analyze") || desc.includes("analysis") || desc.includes("review")) {
58
+ role = "Analyst";
59
+ }
60
+ else if (desc.includes("write") || desc.includes("create") || desc.includes("draft")) {
61
+ role = "Writer";
62
+ }
63
+ else if (desc.includes("compile") || desc.includes("combine") || desc.includes("synthesize")) {
64
+ role = "Synthesizer";
65
+ }
66
+ else if (desc.includes("explore") || desc.includes("investigate")) {
67
+ role = "Explorer";
68
+ }
69
+ else {
70
+ // Use a generic role
71
+ role = roles[i % roles.length];
72
+ }
73
+ // Avoid duplicate names by adding number if needed
74
+ if (usedRoles.has(role)) {
75
+ let suffix = 2;
76
+ while (usedRoles.has(`${role} ${suffix}`))
77
+ suffix++;
78
+ role = `${role} ${suffix}`;
79
+ }
80
+ usedRoles.add(role);
81
+ names.push(role);
82
+ }
83
+ else {
84
+ // More teammates than tasks - use generic names
85
+ names.push(roles[i % roles.length]);
86
+ }
87
+ }
88
+ return names;
89
+ }
90
+ // ============================================================================
91
+ // TEAM CREATION
92
+ // ============================================================================
93
+ async createTeam(config) {
94
+ this.startTime = Date.now();
95
+ // Create team
96
+ const team = createTeam(config.name, "lead");
97
+ this.teamId = team.id;
98
+ this.emit("team_created", { teamId: team.id, name: config.name });
99
+ // Emit to global emitter for UI
100
+ const emitter = getGlobalEmitter();
101
+ emitter.emitTeamStart(team.id, config.name, config.teammateCount, config.tasks.length);
102
+ // Log team creation
103
+ logSpan({
104
+ action: "team.create",
105
+ durationMs: 0,
106
+ context: {
107
+ traceId: this.traceId,
108
+ spanId: this.spanId,
109
+ conversationId: getConversationId(),
110
+ source: "claude_code",
111
+ serviceName: "whale-cli",
112
+ serviceVersion: "2.1.0",
113
+ },
114
+ storeId: resolveConfig().storeId || undefined,
115
+ details: {
116
+ // Team identification (for blackops detection)
117
+ is_team: true,
118
+ is_team_coordinator: true,
119
+ team_id: team.id,
120
+ team_name: config.name,
121
+ teammate_count: config.teammateCount,
122
+ task_count: config.tasks.length,
123
+ model: config.model || getModelShortName(),
124
+ // Display metadata
125
+ display_name: `Team: ${config.name}`,
126
+ display_icon: "person.3.fill",
127
+ display_color: "#10B981",
128
+ },
129
+ });
130
+ // Create tasks
131
+ const taskIdMap = new Map(); // description -> id
132
+ for (const taskConfig of config.tasks) {
133
+ const task = await addTask(team.id, {
134
+ description: taskConfig.description,
135
+ files: taskConfig.files,
136
+ model: taskConfig.model,
137
+ });
138
+ if (task) {
139
+ taskIdMap.set(taskConfig.description, task.id);
140
+ this.emit("task_created", { taskId: task.id, description: taskConfig.description });
141
+ }
142
+ }
143
+ // Set up dependencies (now that we have IDs)
144
+ const freshTeam = loadTeam(team.id);
145
+ for (const taskConfig of config.tasks) {
146
+ if (taskConfig.dependencies?.length) {
147
+ const task = freshTeam.tasks.find(t => t.description === taskConfig.description);
148
+ if (task) {
149
+ task.dependencies = taskConfig.dependencies
150
+ .map(dep => taskIdMap.get(dep))
151
+ .filter(Boolean);
152
+ }
153
+ }
154
+ }
155
+ saveTeam(freshTeam);
156
+ // Create teammates with meaningful names
157
+ const model = config.model || getModelShortName();
158
+ // Generate teammate names from tasks if not provided
159
+ const teammateNames = config.teammateNames || this.generateTeammateNames(config.tasks, config.teammateCount);
160
+ for (let i = 0; i < config.teammateCount; i++) {
161
+ const name = teammateNames[i] || `Agent ${i + 1}`;
162
+ const id = `teammate-${i + 1}-${Date.now()}`;
163
+ await addTeammate(team.id, {
164
+ id,
165
+ name,
166
+ model,
167
+ status: "idle",
168
+ });
169
+ this.emit("teammate_created", { teammateId: id, name });
170
+ }
171
+ return team.id;
172
+ }
173
+ // ============================================================================
174
+ // TEAM EXECUTION
175
+ // ============================================================================
176
+ async runTeam() {
177
+ if (!this.teamId) {
178
+ throw new Error("No team created. Call createTeam first.");
179
+ }
180
+ const team = loadTeam(this.teamId);
181
+ const cwd = process.cwd();
182
+ this.emit("team_started", { teamId: this.teamId, teammateCount: team.teammates.length });
183
+ // Spawn worker threads for each teammate
184
+ const parentConversationId = getConversationId();
185
+ for (const teammate of team.teammates) {
186
+ const worker = await spawnTeammate(this.teamId, teammate.id, teammate.name, teammate.model, cwd, parentConversationId, team.name);
187
+ this.lastMessageTime.set(teammate.id, Date.now());
188
+ // Handle messages from worker
189
+ worker.on("message", (msg) => {
190
+ this.lastMessageTime.set(msg.teammateId, Date.now());
191
+ this.handleTeammateMessage(msg);
192
+ });
193
+ worker.on("error", (err) => {
194
+ this.emit("teammate_error", { teammateId: teammate.id, error: err.message });
195
+ // Fail any in-progress task when worker crashes
196
+ this.handleTeammateFailure(teammate.id, err.message);
197
+ });
198
+ worker.on("exit", (code) => {
199
+ if (code !== 0) {
200
+ this.emit("teammate_exit", { teammateId: teammate.id, code });
201
+ // Fail any in-progress task on non-zero exit
202
+ this.handleTeammateFailure(teammate.id, `Worker exited with code ${code}`);
203
+ }
204
+ this.workers.delete(teammate.id);
205
+ this.checkCompletion();
206
+ });
207
+ this.workers.set(teammate.id, worker);
208
+ }
209
+ // Wait for all workers to complete — with timeout and stall detection
210
+ return new Promise((resolve) => {
211
+ this.resolveTeam = resolve;
212
+ const cleanup = () => {
213
+ if (this.teamTimer) {
214
+ clearTimeout(this.teamTimer);
215
+ this.teamTimer = null;
216
+ }
217
+ if (this.stallInterval) {
218
+ clearInterval(this.stallInterval);
219
+ this.stallInterval = null;
220
+ }
221
+ };
222
+ const forceComplete = (reason) => {
223
+ cleanup();
224
+ this.resolveTeam = null; // Prevent double-resolve from checkCompletion
225
+ // Terminate all remaining workers
226
+ for (const [id, worker] of this.workers) {
227
+ this.emit("teammate_timeout", { teammateId: id, reason });
228
+ this.handleTeammateFailure(id, `Terminated: ${reason}`);
229
+ worker.terminate();
230
+ }
231
+ this.workers.clear();
232
+ if (this.teamId)
233
+ setTeamStatus(this.teamId, "completed");
234
+ resolve(this.buildResult());
235
+ };
236
+ // Global team timeout
237
+ this.teamTimer = setTimeout(() => {
238
+ if (this.workers.size > 0) {
239
+ forceComplete(`Team timeout after ${TEAM_TIMEOUT_MS / 1000}s`);
240
+ }
241
+ }, TEAM_TIMEOUT_MS);
242
+ // Stall detection — check every 15s for stalled workers
243
+ this.stallInterval = setInterval(() => {
244
+ if (this.workers.size === 0) {
245
+ cleanup();
246
+ return;
247
+ }
248
+ const now = Date.now();
249
+ for (const [id, worker] of this.workers) {
250
+ const lastMsg = this.lastMessageTime.get(id) || 0;
251
+ if (now - lastMsg > WORKER_STALL_MS) {
252
+ this.emit("teammate_timeout", { teammateId: id, reason: "stall" });
253
+ this.handleTeammateFailure(id, `Worker stalled (no messages for ${WORKER_STALL_MS / 1000}s)`);
254
+ worker.terminate();
255
+ this.workers.delete(id);
256
+ }
257
+ }
258
+ }, 15_000);
259
+ });
260
+ }
261
+ // ============================================================================
262
+ // MESSAGE HANDLING
263
+ // ============================================================================
264
+ handleTeammateMessage(msg) {
265
+ const emitter = getGlobalEmitter();
266
+ const team = this.teamId ? loadTeam(this.teamId) : null;
267
+ const teammate = team?.teammates.find(t => t.id === msg.teammateId);
268
+ const teammateName = teammate?.name || msg.teammateId;
269
+ switch (msg.type) {
270
+ case "progress":
271
+ this.emit("teammate_progress", {
272
+ teammateId: msg.teammateId,
273
+ taskId: msg.taskId,
274
+ content: msg.content,
275
+ });
276
+ // Emit to global emitter for UI
277
+ if (this.teamId) {
278
+ emitter.emitTeamProgress(this.teamId, msg.teammateId, teammateName, msg.content, msg.taskId);
279
+ }
280
+ break;
281
+ case "task_started":
282
+ this.emit("task_started", {
283
+ teammateId: msg.teammateId,
284
+ taskId: msg.taskId,
285
+ content: msg.content,
286
+ });
287
+ // Emit to global emitter for UI
288
+ if (this.teamId && msg.taskId) {
289
+ emitter.emitTeamTask(this.teamId, msg.teammateId, msg.taskId, msg.content, "started");
290
+ }
291
+ break;
292
+ case "task_completed":
293
+ this.emit("task_completed", {
294
+ teammateId: msg.teammateId,
295
+ taskId: msg.taskId,
296
+ content: msg.content,
297
+ });
298
+ // Emit to global emitter for UI
299
+ if (this.teamId && msg.taskId) {
300
+ emitter.emitTeamTask(this.teamId, msg.teammateId, msg.taskId, msg.content, "completed", msg.content);
301
+ }
302
+ break;
303
+ case "message_sent":
304
+ this.emit("message_sent", {
305
+ teammateId: msg.teammateId,
306
+ content: msg.content,
307
+ });
308
+ break;
309
+ case "done": {
310
+ this.emit("teammate_done", {
311
+ teammateId: msg.teammateId,
312
+ content: msg.content,
313
+ tokensUsed: msg.tokensUsed,
314
+ });
315
+ // Emit UI event so TeamPanel shows "done" (overrides any prior "failed" from task errors)
316
+ const teammateName = this.teamId
317
+ ? loadTeam(this.teamId)?.teammates.find(t => t.id === msg.teammateId)?.name
318
+ : undefined;
319
+ emitter.emitTeamProgress(this.teamId, msg.teammateId, teammateName || msg.teammateId, "done");
320
+ break;
321
+ }
322
+ case "error":
323
+ this.emit("teammate_error", {
324
+ teammateId: msg.teammateId,
325
+ content: msg.content,
326
+ });
327
+ // Fail any in-progress task when teammate reports error
328
+ this.handleTeammateFailure(msg.teammateId, msg.content);
329
+ break;
330
+ }
331
+ }
332
+ // ============================================================================
333
+ // FAILURE HANDLING
334
+ // ============================================================================
335
+ async handleTeammateFailure(teammateId, errorMessage) {
336
+ if (!this.teamId)
337
+ return;
338
+ const team = loadTeam(this.teamId);
339
+ if (!team)
340
+ return;
341
+ const teammate = team.teammates.find(t => t.id === teammateId);
342
+ if (!teammate)
343
+ return;
344
+ // Find and fail the task this teammate was working on
345
+ const inProgressTask = team.tasks.find(t => t.status === "in_progress" && t.assignedTo === teammateId);
346
+ if (inProgressTask) {
347
+ await failTask(this.teamId, inProgressTask.id, errorMessage);
348
+ this.emit("task_failed", {
349
+ teammateId,
350
+ taskId: inProgressTask.id,
351
+ error: errorMessage,
352
+ });
353
+ // Emit to global emitter for UI
354
+ const emitter = getGlobalEmitter();
355
+ emitter.emitTeamTask(this.teamId, teammateId, inProgressTask.id, inProgressTask.description, "failed", errorMessage);
356
+ }
357
+ // Update teammate status (use "done" since "error" isn't a valid status)
358
+ await updateTeammate(this.teamId, teammateId, {
359
+ status: "done",
360
+ currentTask: undefined,
361
+ });
362
+ }
363
+ // ============================================================================
364
+ // COMPLETION
365
+ // ============================================================================
366
+ checkCompletion() {
367
+ if (this.workers.size === 0 && this.teamId) {
368
+ // Clean up timers
369
+ if (this.teamTimer) {
370
+ clearTimeout(this.teamTimer);
371
+ this.teamTimer = null;
372
+ }
373
+ if (this.stallInterval) {
374
+ clearInterval(this.stallInterval);
375
+ this.stallInterval = null;
376
+ }
377
+ setTeamStatus(this.teamId, "completed");
378
+ this.emit("team_completed", { teamId: this.teamId });
379
+ // Resolve the runTeam() promise
380
+ if (this.resolveTeam) {
381
+ this.resolveTeam(this.buildResult());
382
+ this.resolveTeam = null;
383
+ }
384
+ }
385
+ }
386
+ buildResult() {
387
+ if (!this.teamId) {
388
+ return {
389
+ success: false,
390
+ teamId: "",
391
+ summary: "No team created",
392
+ taskResults: [],
393
+ tokensUsed: { input: 0, output: 0 },
394
+ durationMs: Date.now() - this.startTime,
395
+ };
396
+ }
397
+ const team = loadTeam(this.teamId);
398
+ const progress = getTeamProgress(team);
399
+ // Calculate total tokens
400
+ let totalIn = 0;
401
+ let totalOut = 0;
402
+ for (const teammate of team.teammates) {
403
+ totalIn += teammate.tokensUsed.input;
404
+ totalOut += teammate.tokensUsed.output;
405
+ }
406
+ // Build task results
407
+ const taskResults = team.tasks.map(t => ({
408
+ description: t.description,
409
+ result: t.result || t.error || "Not completed",
410
+ status: t.status === "completed" ? "completed" : "failed",
411
+ }));
412
+ const durationMs = Date.now() - this.startTime;
413
+ const success = progress.blocked === 0 && progress.completed === progress.total;
414
+ // Log team completion
415
+ logSpan({
416
+ action: "team.complete",
417
+ durationMs,
418
+ context: {
419
+ traceId: this.traceId,
420
+ spanId: generateSpanId(),
421
+ parentSpanId: this.spanId,
422
+ source: "claude_code",
423
+ serviceName: "whale-cli",
424
+ serviceVersion: "2.1.0",
425
+ inputTokens: totalIn,
426
+ outputTokens: totalOut,
427
+ },
428
+ storeId: resolveConfig().storeId || undefined,
429
+ details: {
430
+ // Team identification (for blackops detection)
431
+ is_team: true,
432
+ is_team_coordinator: true,
433
+ team_id: this.teamId,
434
+ team_name: team.name,
435
+ // Metrics
436
+ tasks_total: progress.total,
437
+ tasks_completed: progress.completed,
438
+ tasks_failed: progress.blocked,
439
+ teammates: team.teammates.length,
440
+ percent_complete: progress.percentComplete,
441
+ // Display metadata
442
+ display_name: `Team Complete: ${team.name}`,
443
+ display_icon: "checkmark.circle.fill",
444
+ display_color: success ? "#10B981" : "#EF4444",
445
+ },
446
+ });
447
+ // Emit team done to global emitter for UI
448
+ const emitter = getGlobalEmitter();
449
+ emitter.emitTeamDone(this.teamId, success, `${progress.completed}/${progress.total} tasks completed`, progress.completed, progress.total, { input: totalIn, output: totalOut }, durationMs);
450
+ return {
451
+ success,
452
+ teamId: this.teamId,
453
+ summary: `${progress.completed}/${progress.total} tasks completed (${progress.percentComplete}%)`,
454
+ taskResults,
455
+ tokensUsed: { input: totalIn, output: totalOut },
456
+ durationMs,
457
+ };
458
+ }
459
+ // ============================================================================
460
+ // CONTROL METHODS
461
+ // ============================================================================
462
+ async sendToTeammate(teammateId, message) {
463
+ if (!this.teamId)
464
+ return false;
465
+ const result = await sendMessage(this.teamId, "lead", teammateId, message);
466
+ return !!result;
467
+ }
468
+ async broadcast(message) {
469
+ if (!this.teamId)
470
+ return false;
471
+ const result = await sendMessage(this.teamId, "lead", "all", message);
472
+ return !!result;
473
+ }
474
+ async getMessages() {
475
+ if (!this.teamId)
476
+ return [];
477
+ const messages = await getUnreadMessages(this.teamId, "lead");
478
+ if (messages.length > 0) {
479
+ await markMessagesRead(this.teamId, messages.map(m => m.id));
480
+ }
481
+ return messages.map(m => ({ from: m.from, content: m.content }));
482
+ }
483
+ getProgress() {
484
+ if (!this.teamId)
485
+ return null;
486
+ const team = loadTeam(this.teamId);
487
+ if (!team)
488
+ return null;
489
+ return getTeamProgress(team);
490
+ }
491
+ stop() {
492
+ // Terminate all workers
493
+ for (const [id, worker] of this.workers) {
494
+ worker.terminate();
495
+ this.emit("teammate_stopped", { teammateId: id });
496
+ }
497
+ this.workers.clear();
498
+ if (this.teamId) {
499
+ setTeamStatus(this.teamId, "failed");
500
+ }
501
+ }
502
+ }
503
+ // ============================================================================
504
+ // CONVENIENCE FUNCTION
505
+ // ============================================================================
506
+ export async function runAgentTeam(config) {
507
+ const lead = new TeamLead();
508
+ // Events are handled via global emitter -> ChatApp UI
509
+ // No stderr writes here to avoid interfering with Ink rendering
510
+ await lead.createTeam(config);
511
+ return lead.runTeam();
512
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Team State — Shared state management for Agent Teams
3
+ *
4
+ * Following Anthropic's official patterns:
5
+ * - Shared task list with status tracking
6
+ * - File-based locking to prevent conflicts
7
+ * - Inter-agent message queue
8
+ * - Dependencies between tasks
9
+ */
10
+ export type TaskStatus = "pending" | "in_progress" | "completed" | "blocked";
11
+ export interface TeamTask {
12
+ id: string;
13
+ description: string;
14
+ status: TaskStatus;
15
+ assignedTo?: string;
16
+ dependencies?: string[];
17
+ files?: string[];
18
+ result?: string;
19
+ error?: string;
20
+ model?: string;
21
+ createdAt: string;
22
+ updatedAt: string;
23
+ }
24
+ export interface TeamMessage {
25
+ id: string;
26
+ from: string;
27
+ to: string;
28
+ content: string;
29
+ timestamp: string;
30
+ read: boolean;
31
+ }
32
+ export interface TeammateInfo {
33
+ id: string;
34
+ name: string;
35
+ model: string;
36
+ status: "idle" | "working" | "waiting" | "done";
37
+ currentTask?: string;
38
+ tokensUsed: {
39
+ input: number;
40
+ output: number;
41
+ };
42
+ startedAt: string;
43
+ }
44
+ export interface TeamState {
45
+ id: string;
46
+ name: string;
47
+ leadId: string;
48
+ teammates: TeammateInfo[];
49
+ tasks: TeamTask[];
50
+ messages: TeamMessage[];
51
+ createdAt: string;
52
+ updatedAt: string;
53
+ status: "active" | "completed" | "failed";
54
+ }
55
+ export declare function createTeam(name: string, leadId: string): TeamState;
56
+ export declare function loadTeam(teamId: string): TeamState | null;
57
+ export declare function saveTeam(team: TeamState): void;
58
+ export declare function listTeams(): TeamState[];
59
+ export declare function addTeammate(teamId: string, teammate: Omit<TeammateInfo, "tokensUsed" | "startedAt">): Promise<TeammateInfo | null>;
60
+ export declare function updateTeammate(teamId: string, teammateId: string, updates: Partial<TeammateInfo>): Promise<boolean>;
61
+ export declare function addTask(teamId: string, task: Omit<TeamTask, "id" | "status" | "createdAt" | "updatedAt">): Promise<TeamTask | null>;
62
+ export declare function claimTask(teamId: string, taskId: string, teammateId: string): Promise<TeamTask | null>;
63
+ export declare function completeTask(teamId: string, taskId: string, result: string): Promise<boolean>;
64
+ export declare function failTask(teamId: string, taskId: string, error: string): Promise<boolean>;
65
+ export declare function getAvailableTasks(team: TeamState): TeamTask[];
66
+ export declare function sendMessage(teamId: string, from: string, to: string, content: string): Promise<TeamMessage | null>;
67
+ export declare function getUnreadMessages(teamId: string, recipientId: string): Promise<TeamMessage[]>;
68
+ export declare function markMessagesRead(teamId: string, messageIds: string[]): Promise<boolean>;
69
+ export declare function getTeamProgress(team: TeamState): {
70
+ total: number;
71
+ completed: number;
72
+ inProgress: number;
73
+ pending: number;
74
+ blocked: number;
75
+ percentComplete: number;
76
+ };
77
+ export declare function setTeamStatus(teamId: string, status: TeamState["status"]): Promise<boolean>;