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,636 @@
1
+ /**
2
+ * Serve Mode — WebSocket agent server for WhaleChat desktop app
3
+ *
4
+ * Speaks the exact bidirectional JSON protocol that AgentClient.swift expects.
5
+ * Runs the full agent loop with all local + server tools.
6
+ *
7
+ * Usage:
8
+ * whale serve Start on default port 3847
9
+ * whale serve --port 6090 Custom port
10
+ * whale serve --host 0.0.0.0 Bind to all interfaces
11
+ *
12
+ * Protocol: WebSocket (ws://host:port)
13
+ *
14
+ * Client → Server messages:
15
+ * query, abort, ping, get_tools, new_conversation, load_conversation, get_conversations
16
+ *
17
+ * Server → Client messages:
18
+ * ready, started, text, tool_start, tool_result, done, error, aborted,
19
+ * pong, tools, debug, conversation_created, conversations, conversation_loaded,
20
+ * subagent_start, subagent_progress, subagent_done, subagent_tool_start, subagent_tool_end,
21
+ * team_start, team_progress, team_task, team_done
22
+ */
23
+ import { createServer } from "http";
24
+ import { WebSocketServer, WebSocket } from "ws";
25
+ import { runAgentLoop, setModel, setPermissionMode, resetSessionState, getModel, estimateCostUsd, AgentEventEmitter, } from "./services/agent-loop.js";
26
+ import { resolveConfig } from "./services/config-store.js";
27
+ import { createClient } from "@supabase/supabase-js";
28
+ import { getValidToken, createAuthenticatedClient } from "./services/auth-service.js";
29
+ import { loadServerToolDefinitions } from "./services/server-tools.js";
30
+ import { LOCAL_TOOL_DEFINITIONS } from "./services/local-tools.js";
31
+ import { createRequire } from "module";
32
+ const require = createRequire(import.meta.url);
33
+ const PKG_VERSION = require("../../package.json").version;
34
+ // ============================================================================
35
+ // SUPABASE CLIENT (for conversation persistence)
36
+ // ============================================================================
37
+ let supabase = null;
38
+ let supabaseCreatedAt = 0;
39
+ const SUPABASE_TTL_MS = 45 * 60 * 1000; // Refresh client every 45 min (JWT expires ~60 min)
40
+ async function getSupabase() {
41
+ const now = Date.now();
42
+ // Return cached client if still fresh
43
+ if (supabase && (now - supabaseCreatedAt) < SUPABASE_TTL_MS)
44
+ return supabase;
45
+ // Invalidate stale client
46
+ supabase = null;
47
+ const config = resolveConfig();
48
+ // Tier 1: Service role key (never expires)
49
+ if (config.supabaseUrl && config.supabaseKey && process.env.SUPABASE_SERVICE_ROLE_KEY) {
50
+ supabase = createClient(config.supabaseUrl, config.supabaseKey, {
51
+ auth: { autoRefreshToken: false, persistSession: false },
52
+ });
53
+ supabaseCreatedAt = now;
54
+ return supabase;
55
+ }
56
+ // Tier 2: User JWT — always get a fresh token
57
+ try {
58
+ const token = await getValidToken();
59
+ if (token) {
60
+ supabase = createAuthenticatedClient(token);
61
+ supabaseCreatedAt = now;
62
+ return supabase;
63
+ }
64
+ }
65
+ catch {
66
+ // Fall through
67
+ }
68
+ return null;
69
+ }
70
+ // ============================================================================
71
+ // CONVERSATION PERSISTENCE
72
+ // ============================================================================
73
+ async function createConversation(storeId, title, agentName) {
74
+ const db = await getSupabase();
75
+ if (!db)
76
+ return null;
77
+ try {
78
+ const { data, error } = await db
79
+ .from("ai_conversations")
80
+ .insert({
81
+ store_id: storeId || null,
82
+ title: title.substring(0, 100),
83
+ metadata: {
84
+ source: "whale-serve",
85
+ agentName: agentName || "whale",
86
+ },
87
+ status: "active",
88
+ })
89
+ .select("id")
90
+ .single();
91
+ if (error) {
92
+ console.error("[serve] Failed to create conversation:", error.message);
93
+ return null;
94
+ }
95
+ return data?.id || null;
96
+ }
97
+ catch (err) {
98
+ console.error("[serve] Conversation create error:", err);
99
+ return null;
100
+ }
101
+ }
102
+ async function appendMessage(conversationId, role, content, tokenCount, toolNames) {
103
+ const db = await getSupabase();
104
+ if (!db || !conversationId)
105
+ return;
106
+ try {
107
+ // Insert into ai_messages for individual message tracking
108
+ await db.from("ai_messages").insert({
109
+ conversation_id: conversationId,
110
+ role,
111
+ content: [{ type: "text", text: content }],
112
+ is_tool_use: (toolNames && toolNames.length > 0) || false,
113
+ tool_names: toolNames || [],
114
+ token_count: tokenCount || 0,
115
+ });
116
+ }
117
+ catch {
118
+ // Non-fatal — persistence is best-effort
119
+ }
120
+ }
121
+ async function updateConversationMetadata(conversationId, metadata) {
122
+ const db = await getSupabase();
123
+ if (!db || !conversationId)
124
+ return;
125
+ try {
126
+ // Fetch current metadata, merge, and update
127
+ const { data } = await db
128
+ .from("ai_conversations")
129
+ .select("metadata")
130
+ .eq("id", conversationId)
131
+ .single();
132
+ const existing = data?.metadata || {};
133
+ await db
134
+ .from("ai_conversations")
135
+ .update({ metadata: { ...existing, ...metadata }, updated_at: new Date().toISOString() })
136
+ .eq("id", conversationId);
137
+ }
138
+ catch {
139
+ // Non-fatal
140
+ }
141
+ }
142
+ async function loadConversation(conversationId) {
143
+ const db = await getSupabase();
144
+ if (!db)
145
+ return null;
146
+ try {
147
+ const { data, error } = await db
148
+ .from("ai_conversations")
149
+ .select("id, title, messages")
150
+ .eq("id", conversationId)
151
+ .single();
152
+ if (error || !data)
153
+ return null;
154
+ return {
155
+ id: data.id,
156
+ title: data.title || "Untitled",
157
+ messages: data.messages || [],
158
+ };
159
+ }
160
+ catch {
161
+ return null;
162
+ }
163
+ }
164
+ async function listConversations(storeId, limit = 20) {
165
+ const db = await getSupabase();
166
+ if (!db)
167
+ return [];
168
+ try {
169
+ const { data, error } = await db
170
+ .from("ai_conversations")
171
+ .select("id, title, agent_id, metadata, turn_count, created_at, updated_at")
172
+ .eq("store_id", storeId)
173
+ .order("updated_at", { ascending: false })
174
+ .limit(limit);
175
+ if (error || !data)
176
+ return [];
177
+ return data.map((c) => ({
178
+ id: c.id,
179
+ title: c.title || "Untitled",
180
+ agentId: c.agent_id || null,
181
+ agentName: c.metadata?.agentName || null,
182
+ messageCount: c.turn_count || 0,
183
+ createdAt: c.created_at,
184
+ updatedAt: c.updated_at,
185
+ }));
186
+ }
187
+ catch {
188
+ return [];
189
+ }
190
+ }
191
+ // ============================================================================
192
+ // TOOL LIST (for ready/tools messages)
193
+ // ============================================================================
194
+ async function getToolList() {
195
+ const tools = [];
196
+ // Local tools
197
+ for (const t of LOCAL_TOOL_DEFINITIONS) {
198
+ tools.push({
199
+ id: `local-${t.name}`,
200
+ name: t.name,
201
+ description: t.description,
202
+ category: "local",
203
+ });
204
+ }
205
+ // Server tools
206
+ try {
207
+ const serverTools = await loadServerToolDefinitions();
208
+ for (const t of serverTools) {
209
+ if (!tools.some((existing) => existing.name === t.name)) {
210
+ tools.push({
211
+ id: `server-${t.name}`,
212
+ name: t.name,
213
+ description: t.description || "",
214
+ category: "server",
215
+ });
216
+ }
217
+ }
218
+ }
219
+ catch {
220
+ // Server tools unavailable — local-only mode
221
+ }
222
+ return tools;
223
+ }
224
+ // ============================================================================
225
+ // WEBSOCKET MESSAGE HELPERS
226
+ // ============================================================================
227
+ function send(ws, message) {
228
+ if (ws.readyState === WebSocket.OPEN) {
229
+ ws.send(JSON.stringify(message));
230
+ }
231
+ }
232
+ function sendDebug(ws, level, message, data) {
233
+ send(ws, { type: "debug", level, message, data });
234
+ }
235
+ // ============================================================================
236
+ // QUERY HANDLER — runs the full agent loop
237
+ // ============================================================================
238
+ async function handleQuery(session, msg, opts) {
239
+ const { ws } = session;
240
+ const prompt = msg.prompt;
241
+ if (!prompt) {
242
+ send(ws, { type: "error", error: "Missing 'prompt' field" });
243
+ return;
244
+ }
245
+ // Extract image attachments sent by WhaleChat
246
+ const rawAttachments = msg.attachments || [];
247
+ const images = rawAttachments
248
+ .filter(att => att.type === "image" && att.media_type && att.data)
249
+ .map(att => ({ base64: att.data, mediaType: att.media_type }));
250
+ const storeId = msg.storeId || resolveConfig().storeId || null;
251
+ const config = msg.config || {};
252
+ const requestedConvId = msg.conversationId || session.conversationId;
253
+ // Restore conversation history from client if session is fresh (new WS connection)
254
+ const clientHistory = msg.conversationHistory;
255
+ if (session.conversationHistory.length === 0 && clientHistory && clientHistory.length > 0) {
256
+ session.conversationHistory = clientHistory
257
+ .filter((m) => m.content && m.content.trim().length > 0)
258
+ .map((m) => ({
259
+ role: m.role,
260
+ content: m.content,
261
+ }));
262
+ }
263
+ // Apply model from config or server defaults
264
+ const model = config.model || opts.model;
265
+ if (model)
266
+ setModel(model);
267
+ // Create or continue conversation
268
+ let conversationId = requestedConvId;
269
+ if (!conversationId) {
270
+ conversationId = await createConversation(storeId, prompt, config.agentName);
271
+ if (conversationId) {
272
+ send(ws, {
273
+ type: "conversation_created",
274
+ conversation: {
275
+ id: conversationId,
276
+ title: prompt.substring(0, 100),
277
+ agentId: config.agentId || null,
278
+ agentName: config.agentName || "whale",
279
+ messageCount: 0,
280
+ createdAt: new Date().toISOString(),
281
+ updatedAt: new Date().toISOString(),
282
+ },
283
+ });
284
+ }
285
+ }
286
+ session.conversationId = conversationId;
287
+ // Persist user message
288
+ if (conversationId) {
289
+ appendMessage(conversationId, "user", prompt);
290
+ }
291
+ // Signal started
292
+ send(ws, {
293
+ type: "started",
294
+ model: getModel(),
295
+ conversationId,
296
+ });
297
+ // Set up abort controller
298
+ const abortController = new AbortController();
299
+ session.abortController = abortController;
300
+ let totalIn = 0;
301
+ let totalOut = 0;
302
+ let lastCostUsd = 0;
303
+ let fullText = "";
304
+ const toolsUsed = [];
305
+ const startTime = Date.now();
306
+ // Create event emitter for subagent/team events → forward to WebSocket
307
+ const emitter = new AgentEventEmitter();
308
+ const forwardTypes = new Set([
309
+ "subagent_start", "subagent_progress", "subagent_done",
310
+ "subagent_tool_start", "subagent_tool_end",
311
+ "team_start", "team_progress", "team_task", "team_done",
312
+ ]);
313
+ const eventHandler = (event) => {
314
+ if (event.type === "text") {
315
+ // Emitter captures text events — forward them to WebSocket + accumulate
316
+ const text = event.text;
317
+ if (text) {
318
+ fullText += text;
319
+ send(ws, { type: "text", text });
320
+ }
321
+ }
322
+ else if (forwardTypes.has(event.type)) {
323
+ send(ws, event);
324
+ }
325
+ };
326
+ emitter.on("event", eventHandler);
327
+ try {
328
+ const callbacks = {
329
+ onText: (text) => {
330
+ fullText += text;
331
+ send(ws, { type: "text", text });
332
+ },
333
+ onToolStart: (name, input) => {
334
+ if (!toolsUsed.includes(name))
335
+ toolsUsed.push(name);
336
+ send(ws, { type: "tool_start", tool: name, input: input || {} });
337
+ },
338
+ onToolResult: (name, success, result, _input, durationMs) => {
339
+ // Truncate large results for the wire
340
+ let wireResult = result;
341
+ if (typeof result === "string" && result.length > 10000) {
342
+ wireResult = result.substring(0, 10000) + "\n... (truncated)";
343
+ }
344
+ send(ws, {
345
+ type: "tool_result",
346
+ tool: name,
347
+ success,
348
+ result: wireResult,
349
+ error: success ? undefined : (typeof result === "string" ? result : "Tool failed"),
350
+ duration_ms: durationMs,
351
+ });
352
+ },
353
+ onUsage: (input_tokens, output_tokens, _thinking_tokens, _model, costUsd) => {
354
+ totalIn += input_tokens;
355
+ totalOut += output_tokens;
356
+ if (costUsd != null)
357
+ lastCostUsd = costUsd;
358
+ },
359
+ onDone: (finalMessages) => {
360
+ session.conversationHistory = finalMessages;
361
+ },
362
+ onError: (error) => {
363
+ send(ws, { type: "error", error });
364
+ },
365
+ onAutoCompact: (before, after, tokensSaved) => {
366
+ sendDebug(ws, "info", `Context compacted: ${before} → ${after} messages (saved ~${tokensSaved} tokens)`);
367
+ },
368
+ };
369
+ const loopOpts = {
370
+ message: prompt,
371
+ images: images.length > 0 ? images : undefined,
372
+ conversationHistory: session.conversationHistory,
373
+ callbacks,
374
+ abortSignal: abortController.signal,
375
+ emitter,
376
+ maxTurns: config.maxTurns || opts.maxTurns,
377
+ maxBudgetUsd: opts.maxBudgetUsd,
378
+ effort: (opts.effort || "medium"),
379
+ allowedTools: config.enabledTools || opts.allowedTools,
380
+ disallowedTools: opts.disallowedTools,
381
+ fallbackModel: opts.fallbackModel,
382
+ };
383
+ await runAgentLoop(loopOpts);
384
+ // Calculate cost
385
+ session.totalInputTokens += totalIn;
386
+ session.totalOutputTokens += totalOut;
387
+ const totalCost = lastCostUsd || estimateCostUsd(session.totalInputTokens, session.totalOutputTokens);
388
+ const durationMs = Date.now() - startTime;
389
+ // Persist assistant response
390
+ if (conversationId && fullText) {
391
+ appendMessage(conversationId, "assistant", fullText, totalOut, toolsUsed);
392
+ }
393
+ // Update conversation metadata
394
+ if (conversationId) {
395
+ updateConversationMetadata(conversationId, {
396
+ lastTurnTokens: { input: totalIn, output: totalOut },
397
+ lastToolCalls: toolsUsed,
398
+ lastDurationMs: durationMs,
399
+ model: getModel(),
400
+ });
401
+ }
402
+ // Send done
403
+ send(ws, {
404
+ type: "done",
405
+ status: "complete",
406
+ conversationId: conversationId || "",
407
+ usage: {
408
+ inputTokens: session.totalInputTokens,
409
+ outputTokens: session.totalOutputTokens,
410
+ totalCost,
411
+ },
412
+ });
413
+ }
414
+ catch (err) {
415
+ const errorMsg = err instanceof Error ? err.message : String(err);
416
+ if (abortController.signal.aborted) {
417
+ send(ws, { type: "aborted" });
418
+ }
419
+ else {
420
+ send(ws, { type: "error", error: errorMsg });
421
+ }
422
+ }
423
+ finally {
424
+ emitter.off("event", eventHandler);
425
+ session.abortController = null;
426
+ }
427
+ }
428
+ // ============================================================================
429
+ // CONNECTION HANDLER
430
+ // ============================================================================
431
+ async function handleConnection(ws, opts) {
432
+ const session = {
433
+ ws,
434
+ abortController: null,
435
+ conversationHistory: [],
436
+ conversationId: null,
437
+ totalInputTokens: 0,
438
+ totalOutputTokens: 0,
439
+ };
440
+ // Send ready with tool list
441
+ const tools = await getToolList();
442
+ send(ws, {
443
+ type: "ready",
444
+ version: PKG_VERSION,
445
+ tools,
446
+ });
447
+ if (opts.verbose) {
448
+ console.log("[serve] Client connected, sent ready with", tools.length, "tools");
449
+ }
450
+ ws.on("message", async (data) => {
451
+ let msg;
452
+ try {
453
+ const text = typeof data === "string" ? data : data.toString("utf-8");
454
+ msg = JSON.parse(text);
455
+ }
456
+ catch {
457
+ send(ws, { type: "error", error: "Invalid JSON" });
458
+ return;
459
+ }
460
+ const type = msg.type;
461
+ if (!type) {
462
+ send(ws, { type: "error", error: "Missing 'type' field" });
463
+ return;
464
+ }
465
+ switch (type) {
466
+ case "query":
467
+ await handleQuery(session, msg, opts);
468
+ break;
469
+ case "abort":
470
+ if (session.abortController) {
471
+ session.abortController.abort();
472
+ // aborted message is sent by handleQuery's catch block
473
+ }
474
+ break;
475
+ case "ping":
476
+ send(ws, { type: "pong" });
477
+ break;
478
+ case "get_tools": {
479
+ const toolList = await getToolList();
480
+ send(ws, { type: "tools", tools: toolList });
481
+ break;
482
+ }
483
+ case "new_conversation":
484
+ session.conversationHistory = [];
485
+ session.conversationId = null;
486
+ session.totalInputTokens = 0;
487
+ session.totalOutputTokens = 0;
488
+ resetSessionState(); // Reset module-level token counters, caches
489
+ if (opts.verbose) {
490
+ console.log("[serve] New conversation started");
491
+ }
492
+ break;
493
+ case "get_conversations": {
494
+ const storeId = msg.storeId;
495
+ const limit = msg.limit || 20;
496
+ if (!storeId) {
497
+ send(ws, { type: "conversations", conversations: [] });
498
+ break;
499
+ }
500
+ const convs = await listConversations(storeId, limit);
501
+ send(ws, { type: "conversations", conversations: convs });
502
+ break;
503
+ }
504
+ case "load_conversation": {
505
+ const convId = msg.conversationId;
506
+ if (!convId) {
507
+ send(ws, { type: "error", error: "Missing conversationId" });
508
+ break;
509
+ }
510
+ const conv = await loadConversation(convId);
511
+ if (conv) {
512
+ session.conversationId = conv.id;
513
+ send(ws, {
514
+ type: "conversation_loaded",
515
+ conversationId: conv.id,
516
+ title: conv.title,
517
+ messages: conv.messages,
518
+ });
519
+ }
520
+ else {
521
+ send(ws, { type: "error", error: `Conversation not found: ${convId}` });
522
+ }
523
+ break;
524
+ }
525
+ default:
526
+ if (opts.verbose) {
527
+ console.log("[serve] Unknown message type:", type);
528
+ }
529
+ break;
530
+ }
531
+ });
532
+ ws.on("close", () => {
533
+ // Abort any running query when client disconnects
534
+ if (session.abortController) {
535
+ session.abortController.abort();
536
+ }
537
+ if (opts.verbose) {
538
+ console.log("[serve] Client disconnected");
539
+ }
540
+ });
541
+ ws.on("error", (err) => {
542
+ console.error("[serve] WebSocket error:", err.message);
543
+ if (session.abortController) {
544
+ session.abortController.abort();
545
+ }
546
+ });
547
+ }
548
+ // ============================================================================
549
+ // SERVER STARTUP
550
+ // ============================================================================
551
+ export async function runServeMode(opts) {
552
+ // Apply global settings
553
+ if (opts.model)
554
+ setModel(opts.model);
555
+ setPermissionMode(opts.permissionMode || "yolo");
556
+ const port = opts.port;
557
+ const host = opts.host;
558
+ // HTTP server for health check + WebSocket upgrade
559
+ const server = createServer((req, res) => {
560
+ // Health check endpoint
561
+ if (req.method === "GET" && (req.url === "/health" || req.url === "/")) {
562
+ res.writeHead(200, { "Content-Type": "application/json" });
563
+ res.end(JSON.stringify({
564
+ status: "ok",
565
+ version: PKG_VERSION,
566
+ model: getModel(),
567
+ port,
568
+ uptime: Math.floor(process.uptime()),
569
+ }));
570
+ return;
571
+ }
572
+ // Everything else: 404
573
+ res.writeHead(404, { "Content-Type": "application/json" });
574
+ res.end(JSON.stringify({ error: "Not found. Connect via WebSocket." }));
575
+ });
576
+ // WebSocket server
577
+ const wss = new WebSocketServer({ server });
578
+ wss.on("connection", (ws) => {
579
+ handleConnection(ws, opts);
580
+ });
581
+ // Graceful shutdown
582
+ const shutdown = () => {
583
+ console.log("\n[serve] Shutting down...");
584
+ wss.clients.forEach((client) => {
585
+ if (client.readyState === WebSocket.OPEN) {
586
+ client.close(1001, "Server shutting down");
587
+ }
588
+ });
589
+ server.close(() => {
590
+ console.log("[serve] Server stopped.");
591
+ process.exit(0);
592
+ });
593
+ // Force exit after 3s
594
+ setTimeout(() => process.exit(0), 3000);
595
+ };
596
+ process.on("SIGINT", shutdown);
597
+ process.on("SIGTERM", shutdown);
598
+ // Verify Supabase connectivity (non-blocking)
599
+ getSupabase().then((db) => {
600
+ if (db) {
601
+ console.log("[serve] Supabase connected — conversation persistence enabled");
602
+ }
603
+ else {
604
+ console.log("[serve] Supabase not configured — conversations are session-only");
605
+ }
606
+ });
607
+ // Start listening
608
+ return new Promise((resolve, reject) => {
609
+ server.on("error", (err) => {
610
+ if (err.code === "EADDRINUSE") {
611
+ console.error(`[serve] Port ${port} is already in use.`);
612
+ console.error(`[serve] Try: whale serve --port ${port + 1}`);
613
+ process.exit(1);
614
+ }
615
+ reject(err);
616
+ });
617
+ server.listen(port, host, () => {
618
+ const d = "\x1b[2m";
619
+ const B = "\x1b[1m";
620
+ const r = "\x1b[0m";
621
+ const c = "\x1b[38;2;99;102;241m";
622
+ console.log();
623
+ console.log(` ${c}${B}◆ whale serve${r} ${d}v${PKG_VERSION}${r}`);
624
+ console.log();
625
+ console.log(` ${B}WebSocket${r} ${d}ws://${host}:${port}${r}`);
626
+ console.log(` ${B}Health${r} ${d}http://${host}:${port}/health${r}`);
627
+ console.log(` ${B}Model${r} ${d}${getModel()}${r}`);
628
+ console.log(` ${B}Mode${r} ${d}yolo (local server trusts local client)${r}`);
629
+ console.log();
630
+ console.log(` ${d}WhaleChat will auto-connect on port 3847${r}`);
631
+ console.log(` ${d}Press Ctrl+C to stop${r}`);
632
+ console.log();
633
+ resolve();
634
+ });
635
+ });
636
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Agent Definitions — custom agent types from markdown files
3
+ *
4
+ * Matches Claude Code's .claude/agents/ pattern:
5
+ *
6
+ * .whale/agents/reviewer.md:
7
+ * ---
8
+ * description: Reviews code for security issues
9
+ * tools: read_file, glob, grep
10
+ * ---
11
+ * You are a security review agent...
12
+ *
13
+ * Load order:
14
+ * 1. ~/.swagmanager/agents/*.md (global)
15
+ * 2. .whale/agents/*.md (local, overrides global)
16
+ */
17
+ export interface AgentDefinition {
18
+ name: string;
19
+ prompt: string;
20
+ description?: string;
21
+ tools?: string[];
22
+ source: "global" | "local";
23
+ }
24
+ export declare function loadAgentDefinitions(): AgentDefinition[];
25
+ export declare function getAgentDefinition(name: string): AgentDefinition | undefined;