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,543 @@
1
+ /**
2
+ * Server Tools — loaded dynamically from ai_tool_registry
3
+ *
4
+ * Single source of truth: the database. Same as the MCP server (index.ts).
5
+ * No hardcoded definitions. Tools are cached for 60s after first load.
6
+ *
7
+ * Execution: proxied to the Fly.io server or Supabase edge function (mode: "tool").
8
+ * All business logic lives server-side — CLI is a thin client.
9
+ * Claude formats the JSON results for the user (no client-side formatter).
10
+ */
11
+ import { createClient } from "@supabase/supabase-js";
12
+ import { writeFileSync, readFileSync, mkdirSync } from "node:fs";
13
+ import { join } from "node:path";
14
+ import { tmpdir } from "node:os";
15
+ // Note: extractMediaToFiles below is a fallback for any tools that still return
16
+ // base64 media. The voice handler now uploads to Supabase Storage and returns
17
+ // file_url instead, but this remains for backwards compatibility.
18
+ import { resolveConfig } from "./config-store.js";
19
+ import { getValidToken, createAuthenticatedClient } from "./auth-service.js";
20
+ import { formatServerResponse } from "./format-server-response.js";
21
+ let executionContext = {};
22
+ /**
23
+ * Set the execution context for server tool calls.
24
+ * Called by agent-loop.ts before each turn so tool calls carry trace/user identity.
25
+ */
26
+ export function setServerToolContext(ctx) {
27
+ executionContext = ctx;
28
+ }
29
+ // ============================================================================
30
+ // SUPABASE CLIENT (tiered: service role > user JWT)
31
+ // Used only for loading tool definitions from ai_tool_registry.
32
+ // Tool execution goes through the edge function.
33
+ // ============================================================================
34
+ let cachedClient = null;
35
+ let cachedStoreId = "";
36
+ let cachedAuthMethod = "none";
37
+ let cachedToken = "";
38
+ async function getSupabaseClient() {
39
+ const config = resolveConfig();
40
+ // Tier 1: Service role key (full access, MCP server mode) — never expires
41
+ if (config.supabaseUrl && config.supabaseKey) {
42
+ if (cachedClient && cachedAuthMethod === "service_role") {
43
+ return { client: cachedClient, storeId: cachedStoreId };
44
+ }
45
+ cachedClient = createClient(config.supabaseUrl, config.supabaseKey, {
46
+ auth: { persistSession: false, autoRefreshToken: false },
47
+ });
48
+ cachedStoreId = config.storeId || "";
49
+ cachedAuthMethod = "service_role";
50
+ return { client: cachedClient, storeId: cachedStoreId };
51
+ }
52
+ // Tier 2: User JWT (CLI login) — recreate client when token refreshes
53
+ const token = await getValidToken();
54
+ if (token) {
55
+ if (cachedClient && cachedToken === token) {
56
+ cachedStoreId = config.storeId || "";
57
+ return { client: cachedClient, storeId: cachedStoreId };
58
+ }
59
+ cachedClient = createAuthenticatedClient(token);
60
+ cachedToken = token;
61
+ cachedStoreId = config.storeId || "";
62
+ cachedAuthMethod = "jwt";
63
+ return { client: cachedClient, storeId: cachedStoreId };
64
+ }
65
+ cachedClient = null;
66
+ cachedToken = "";
67
+ cachedAuthMethod = "none";
68
+ return null;
69
+ }
70
+ export function resetServerToolClient() {
71
+ cachedClient = null;
72
+ cachedStoreId = "";
73
+ cachedToken = "";
74
+ cachedAuthMethod = "none";
75
+ connectionVerified = false;
76
+ // Also clear tool cache so next load fetches fresh
77
+ loadedTools = [];
78
+ loadedToolNames.clear();
79
+ toolsLoadedAt = 0;
80
+ }
81
+ // ============================================================================
82
+ // CONNECTION CHECK
83
+ // ============================================================================
84
+ let connectionVerified = false;
85
+ export async function checkConnection() {
86
+ if (connectionVerified)
87
+ return true;
88
+ const conn = await getSupabaseClient();
89
+ if (!conn)
90
+ return false;
91
+ try {
92
+ // Quick health check — query a small table
93
+ const { error } = await conn.client.from("stores").select("id").limit(1);
94
+ connectionVerified = !error;
95
+ return connectionVerified;
96
+ }
97
+ catch {
98
+ return false;
99
+ }
100
+ }
101
+ // ============================================================================
102
+ // TOOL DEFINITIONS — loaded from ai_tool_registry (single source of truth)
103
+ // ============================================================================
104
+ let loadedTools = [];
105
+ let loadedToolNames = new Set();
106
+ let toolsLoadedAt = 0;
107
+ const TOOL_CACHE_TTL = 60_000; // 1 minute
108
+ /**
109
+ * Load server tool definitions from ai_tool_registry.
110
+ * Same query as the MCP server (index.ts). Cached for 60s.
111
+ * Filters out tool_mode='code' (those are local CLI tools).
112
+ */
113
+ export async function loadServerToolDefinitions(force = false) {
114
+ // Return cache if fresh
115
+ if (!force && loadedTools.length > 0 && Date.now() - toolsLoadedAt < TOOL_CACHE_TTL) {
116
+ return loadedTools;
117
+ }
118
+ const conn = await getSupabaseClient();
119
+ if (!conn)
120
+ return [];
121
+ try {
122
+ const { data, error } = await conn.client
123
+ .from("ai_tool_registry")
124
+ .select("name, description, definition")
125
+ .eq("is_active", true)
126
+ .neq("tool_mode", "code");
127
+ if (error) {
128
+ console.error("[server-tools] Failed to load from ai_tool_registry:", error.message);
129
+ return loadedTools; // Return stale cache on error
130
+ }
131
+ loadedTools = (data || []).map(t => ({
132
+ name: t.name,
133
+ description: t.description || t.definition?.description || `Execute ${t.name}`,
134
+ input_schema: t.definition?.input_schema || { type: "object", properties: {} },
135
+ }));
136
+ // Rebuild the name set
137
+ loadedToolNames.clear();
138
+ for (const tool of loadedTools) {
139
+ loadedToolNames.add(tool.name);
140
+ }
141
+ toolsLoadedAt = Date.now();
142
+ connectionVerified = true;
143
+ return loadedTools;
144
+ }
145
+ catch (err) {
146
+ console.error("[server-tools] Error loading tool definitions:", err);
147
+ return loadedTools;
148
+ }
149
+ }
150
+ /**
151
+ * Check if a tool name is a server tool.
152
+ * After first load, checks against the dynamically loaded set.
153
+ */
154
+ export function isServerTool(name) {
155
+ return loadedToolNames.has(name);
156
+ }
157
+ /**
158
+ * Get currently loaded definitions (for /tools listing).
159
+ * Returns whatever is cached — call loadServerToolDefinitions() first to populate.
160
+ */
161
+ export function getAllServerToolDefinitions() {
162
+ return loadedTools;
163
+ }
164
+ // ============================================================================
165
+ // SERVER STATUS
166
+ // ============================================================================
167
+ export async function getServerStatus() {
168
+ const { loadConfig } = await import("./config-store.js");
169
+ const config = loadConfig();
170
+ // Loading tools also verifies connection
171
+ const tools = await loadServerToolDefinitions();
172
+ return {
173
+ connected: tools.length > 0,
174
+ storeId: config.store_id || "",
175
+ storeName: config.store_name || "",
176
+ toolCount: tools.length,
177
+ authMethod: cachedAuthMethod,
178
+ };
179
+ }
180
+ // ============================================================================
181
+ // EXECUTE SERVER TOOL — proxied to edge function
182
+ // ============================================================================
183
+ /** Media fields that contain base64-encoded binary data */
184
+ const MEDIA_FIELDS = {
185
+ audio_base64: { ext: "mp3", label: "audio" },
186
+ stems_zip_base64: { ext: "zip", label: "stems" },
187
+ };
188
+ /** Tools that produce downloadable media files via file_url */
189
+ const MEDIA_TOOLS = new Set(["voice", "image_gen", "video_gen"]);
190
+ /**
191
+ * Download a single file_url to a local path, replacing the URL in the data.
192
+ * inputArgs is the original tool call args — used to derive descriptive filenames
193
+ * when the result data doesn't include the text/prompt.
194
+ */
195
+ async function downloadSingleMedia(data, toolName, outDir, inputArgs) {
196
+ const fileUrl = data.file_url;
197
+ if (typeof fileUrl !== "string" || !fileUrl.startsWith("http"))
198
+ return;
199
+ // Derive a descriptive filename
200
+ // Check result data first, then fall back to input args for text/prompt
201
+ let label = "";
202
+ const text = (data.prompt || data.text || inputArgs?.text || inputArgs?.prompt);
203
+ if (text) {
204
+ label = text.substring(0, 40).replace(/[^a-zA-Z0-9]+/g, "-").replace(/-+$/, "").toLowerCase();
205
+ }
206
+ const action = data.action || inputArgs?.action || toolName;
207
+ // Detect format: explicit field > file extension from URL > tool-based default
208
+ let format = data.format;
209
+ if (!format) {
210
+ try {
211
+ const urlPath = new URL(fileUrl).pathname;
212
+ const urlExt = urlPath.split(".").pop();
213
+ if (urlExt && urlExt.length <= 4)
214
+ format = urlExt;
215
+ }
216
+ catch { /* invalid URL — use default */ }
217
+ }
218
+ if (!format)
219
+ format = toolName === "image_gen" ? "png" : toolName === "video_gen" ? "mp4" : "mp3";
220
+ const ts = Date.now();
221
+ const nameParts = [action, label, String(ts)].filter(Boolean);
222
+ const filename = `${nameParts.join("-")}.${format}`;
223
+ const localPath = join(outDir, filename);
224
+ try {
225
+ const resp = await fetch(fileUrl);
226
+ if (!resp.ok) {
227
+ data.download_error = `Failed to download: HTTP ${resp.status}`;
228
+ return;
229
+ }
230
+ const buffer = Buffer.from(await resp.arrayBuffer());
231
+ writeFileSync(localPath, buffer);
232
+ // Replace remote URL with local path — the LLM reports this, no URL needed
233
+ data.local_file = localPath;
234
+ data.file_size = buffer.length;
235
+ // Remove fields that tempt the LLM to fabricate URLs
236
+ delete data.file_url;
237
+ delete data.download;
238
+ }
239
+ catch (err) {
240
+ data.download_error = `Download failed: ${err.message || err}`;
241
+ }
242
+ }
243
+ /**
244
+ * Download remote media (file_url) to the user's working directory.
245
+ * This ensures the LLM never needs to handle URLs — it just reports local paths.
246
+ * Prevents the hallucinated-URL problem where models fabricate wrong download URLs.
247
+ *
248
+ * Handles both single results (file_url at top level) and batch results
249
+ * (file_url inside images[] or previews[] arrays).
250
+ */
251
+ async function downloadRemoteMedia(data, toolName, inputArgs) {
252
+ // Save to cwd — the user's working directory, not a hidden temp folder
253
+ const outDir = process.cwd();
254
+ // Single file at top level (voice speak, image_gen generate, etc.)
255
+ if (typeof data.file_url === "string" && data.file_url.startsWith("http")) {
256
+ await downloadSingleMedia(data, toolName, outDir, inputArgs);
257
+ }
258
+ // Batch arrays — image_gen batch returns images[], voice_design returns previews[]
259
+ if (Array.isArray(data.images)) {
260
+ for (const img of data.images) {
261
+ if (img && typeof img === "object" && typeof img.file_url === "string") {
262
+ await downloadSingleMedia(img, toolName, outDir);
263
+ }
264
+ }
265
+ }
266
+ if (Array.isArray(data.previews)) {
267
+ for (const preview of data.previews) {
268
+ if (preview && typeof preview === "object" && typeof preview.file_url === "string") {
269
+ await downloadSingleMedia(preview, toolName, outDir);
270
+ }
271
+ }
272
+ }
273
+ }
274
+ /**
275
+ * Extract base64 media from tool results, save to temp files, and replace
276
+ * with file paths. This prevents truncation from destroying binary data
277
+ * that the model needs to save for the user.
278
+ */
279
+ function extractMediaToFiles(data, toolName) {
280
+ const outDir = join(tmpdir(), "whale-audio");
281
+ let dirCreated = false;
282
+ for (const [field, { ext, label }] of Object.entries(MEDIA_FIELDS)) {
283
+ const b64 = data[field];
284
+ if (typeof b64 !== "string" || b64.length < 100)
285
+ continue;
286
+ if (!dirCreated) {
287
+ mkdirSync(outDir, { recursive: true });
288
+ dirCreated = true;
289
+ }
290
+ const ts = Date.now();
291
+ const filename = `${toolName}-${label}-${ts}.${ext}`;
292
+ const filePath = join(outDir, filename);
293
+ try {
294
+ writeFileSync(filePath, Buffer.from(b64, "base64"));
295
+ // Replace base64 with file path so model can reference it
296
+ data[field] = `(saved to ${filePath})`;
297
+ data[`${label}_file`] = filePath;
298
+ }
299
+ catch {
300
+ // Leave original data if write fails
301
+ }
302
+ }
303
+ // Handle voice_design previews array
304
+ if (Array.isArray(data.previews)) {
305
+ if (!dirCreated) {
306
+ mkdirSync(outDir, { recursive: true });
307
+ }
308
+ for (let i = 0; i < data.previews.length; i++) {
309
+ const preview = data.previews[i];
310
+ if (!preview?.audio_base64 || typeof preview.audio_base64 !== "string")
311
+ continue;
312
+ const ts = Date.now();
313
+ const filePath = join(outDir, `${toolName}-preview-${i}-${ts}.mp3`);
314
+ try {
315
+ writeFileSync(filePath, Buffer.from(preview.audio_base64, "base64"));
316
+ preview.audio_base64 = `(saved to ${filePath})`;
317
+ preview.audio_file = filePath;
318
+ }
319
+ catch {
320
+ // Leave original
321
+ }
322
+ }
323
+ }
324
+ }
325
+ /**
326
+ * Execute a server tool via the Fly.io server or Supabase edge function (mode: "tool").
327
+ * Returns the raw JSON — Claude formats it for the user.
328
+ * No client-side formatting: the model is the presentation layer.
329
+ *
330
+ * For tools that support live progress (kali exec), uses `mode: "tool_stream"` with NDJSON
331
+ * and emits `tool_output` events via the optional emitter for real-time CLI rendering.
332
+ */
333
+ export async function executeServerTool(name, input, emitter) {
334
+ const config = resolveConfig();
335
+ if (!config.serverUrl) {
336
+ return { success: false, output: "No server URL configured — server tools unavailable." };
337
+ }
338
+ // Auth token: service role key preferred, user JWT fallback
339
+ let authToken = config.supabaseKey;
340
+ if (!authToken) {
341
+ authToken = await getValidToken() || "";
342
+ }
343
+ if (!authToken) {
344
+ return { success: false, output: "No auth token — server tools unavailable. Run: whale login" };
345
+ }
346
+ // ── Pre-process file_paths for voice tool ──
347
+ // Reads local files and base64-encodes them so the LLM never handles binary data.
348
+ // CRITICAL: We must NOT mutate `input` — the agent loop keeps a reference to it
349
+ // in the conversation history. If we inject base64 into `input`, it leaks into
350
+ // the LLM context and blows the 200K token limit.
351
+ let serverArgs = input;
352
+ if (name === "voice" && (input.file_paths || input.file_path)) {
353
+ let paths = [];
354
+ if (Array.isArray(input.file_paths)) {
355
+ paths = input.file_paths;
356
+ }
357
+ else if (typeof input.file_path === "string") {
358
+ paths = [input.file_path];
359
+ }
360
+ const MAX_TOTAL_BYTES = 7_000_000; // 7MB raw = ~9.3MB base64, under 10MB server limit
361
+ const MAX_SINGLE_FILE = 2_000_000; // 2MB per file (~2 min at 128kbps) — enough for good clone
362
+ const samples = [];
363
+ let totalBytes = 0;
364
+ for (const p of paths) {
365
+ try {
366
+ let buf = readFileSync(p);
367
+ // IVC: trim to 2MB per file. PVC: no trim (needs 30+ min for best quality)
368
+ const isPVC = input.action === "pvc_upload";
369
+ if (!isPVC && buf.length > MAX_SINGLE_FILE) {
370
+ buf = buf.subarray(0, MAX_SINGLE_FILE);
371
+ }
372
+ totalBytes += buf.length;
373
+ const maxTotal = isPVC ? 25_000_000 : MAX_TOTAL_BYTES;
374
+ if (totalBytes > maxTotal)
375
+ break; // use what we have
376
+ samples.push(buf.toString("base64"));
377
+ }
378
+ catch (err) {
379
+ // Skip unreadable files instead of killing the entire batch
380
+ continue;
381
+ }
382
+ }
383
+ if (samples.length === 0) {
384
+ return { success: false, output: "No valid audio files found in the provided paths." };
385
+ }
386
+ // Build a SEPARATE args object for the server — never touch `input`
387
+ const { file_paths: _fp, file_path: _f, ...rest } = input;
388
+ serverArgs = { ...rest };
389
+ if (input.action === "clone_voice" || input.action === "pvc_upload") {
390
+ serverArgs.audio_samples = samples;
391
+ }
392
+ else if (input.action === "voice_design") {
393
+ serverArgs.reference_audio_base64 = samples[0];
394
+ }
395
+ else {
396
+ serverArgs.audio_base64 = samples[0];
397
+ }
398
+ }
399
+ // ── Streaming path for kali exec actions ──
400
+ // Uses NDJSON streaming to show live stdout/stderr in the CLI while the command runs.
401
+ const isStreamable = name === "kali" && emitter &&
402
+ (input.action === "exec" || input.action === "exec_stream");
403
+ if (isStreamable) {
404
+ try {
405
+ const serverUrl = config.serverUrl;
406
+ const response = await fetch(serverUrl, {
407
+ method: "POST",
408
+ headers: {
409
+ "Content-Type": "application/json",
410
+ "Authorization": `Bearer ${authToken}`,
411
+ },
412
+ body: JSON.stringify({
413
+ mode: "tool_stream",
414
+ tool_name: name,
415
+ args: serverArgs,
416
+ store_id: config.storeId || undefined,
417
+ trace_id: executionContext.traceId || undefined,
418
+ conversation_id: executionContext.conversationId || undefined,
419
+ userId: executionContext.userId || undefined,
420
+ userEmail: executionContext.userEmail || undefined,
421
+ source: executionContext.source || "whale-code",
422
+ }),
423
+ });
424
+ if (!response.ok) {
425
+ const text = await response.text().catch(() => "");
426
+ return { success: false, output: `Server tool stream error: HTTP ${response.status}: ${text.substring(0, 500)}` };
427
+ }
428
+ if (!response.body) {
429
+ return { success: false, output: "Server returned no response body for tool_stream" };
430
+ }
431
+ // Read NDJSON stream — emit progress events, collect final result
432
+ const reader = response.body.getReader();
433
+ const decoder = new TextDecoder();
434
+ let buffer = "";
435
+ let finalResult = null;
436
+ while (true) {
437
+ const { done, value } = await reader.read();
438
+ if (done)
439
+ break;
440
+ buffer += decoder.decode(value, { stream: true });
441
+ let newlineIdx;
442
+ while ((newlineIdx = buffer.indexOf("\n")) !== -1) {
443
+ const line = buffer.substring(0, newlineIdx).trim();
444
+ buffer = buffer.substring(newlineIdx + 1);
445
+ if (!line)
446
+ continue;
447
+ try {
448
+ const parsed = JSON.parse(line);
449
+ if (parsed.type === "progress" && parsed.progress) {
450
+ const p = parsed.progress;
451
+ if ((p.type === "stdout" || p.type === "stderr") && p.data) {
452
+ emitter.emitToolOutput(name, p.data);
453
+ }
454
+ }
455
+ else if (parsed.type === "result") {
456
+ finalResult = parsed;
457
+ }
458
+ }
459
+ catch { /* skip malformed lines */ }
460
+ }
461
+ }
462
+ // Process remaining buffer
463
+ const remaining = buffer.trim();
464
+ if (remaining) {
465
+ try {
466
+ const parsed = JSON.parse(remaining);
467
+ if (parsed.type === "result")
468
+ finalResult = parsed;
469
+ }
470
+ catch { /* skip */ }
471
+ }
472
+ if (finalResult) {
473
+ if (finalResult.success && finalResult.data) {
474
+ const output = typeof finalResult.data === "string"
475
+ ? finalResult.data
476
+ : formatServerResponse(finalResult.data, name);
477
+ return { success: true, output };
478
+ }
479
+ return { success: false, output: finalResult.error || `Server tool "${name}" returned success=false` };
480
+ }
481
+ return { success: false, output: "No result received from streaming tool execution" };
482
+ }
483
+ catch (err) {
484
+ return { success: false, output: `Server tool stream error: ${err.message || err}` };
485
+ }
486
+ }
487
+ // ── Standard path: non-streaming tools ──
488
+ try {
489
+ const serverUrl = config.serverUrl;
490
+ const response = await fetch(serverUrl, {
491
+ method: "POST",
492
+ headers: {
493
+ "Content-Type": "application/json",
494
+ "Authorization": `Bearer ${authToken}`,
495
+ },
496
+ body: JSON.stringify({
497
+ mode: "tool",
498
+ tool_name: name,
499
+ args: serverArgs,
500
+ store_id: config.storeId || undefined,
501
+ trace_id: executionContext.traceId || undefined,
502
+ conversation_id: executionContext.conversationId || undefined,
503
+ userId: executionContext.userId || undefined,
504
+ userEmail: executionContext.userEmail || undefined,
505
+ source: executionContext.source || "whale-code",
506
+ }),
507
+ });
508
+ const result = await response.json();
509
+ if (result.success) {
510
+ if (result.data && typeof result.data === "object") {
511
+ const dataObj = result.data;
512
+ // Auto-download remote media files to local temp paths
513
+ if (MEDIA_TOOLS.has(name)) {
514
+ await downloadRemoteMedia(dataObj, name, input);
515
+ }
516
+ // Legacy: extract base64 media to temp files
517
+ extractMediaToFiles(dataObj, name);
518
+ }
519
+ let output = typeof result.data === "string"
520
+ ? result.data
521
+ : formatServerResponse(result.data, name);
522
+ // Safety cap only — Anthropic context_management handles normal limits.
523
+ // Old limit was 30K which caused constant truncation and extra tool calls.
524
+ const SAFETY_MAX_SERVER_CHARS = 500_000;
525
+ if (output.length > SAFETY_MAX_SERVER_CHARS) {
526
+ output = output.slice(0, SAFETY_MAX_SERVER_CHARS)
527
+ + `\n\n... (safety truncated — ${output.length.toLocaleString()} chars total)`;
528
+ }
529
+ return { success: true, output };
530
+ }
531
+ // Extract error from nested data if top-level error is missing
532
+ let errorMsg = result.error;
533
+ if (!errorMsg && result.data && typeof result.data === "object") {
534
+ const nested = result.data.error;
535
+ if (typeof nested === "string")
536
+ errorMsg = nested;
537
+ }
538
+ return { success: false, output: errorMsg || `Server tool "${name}" returned success=false with no error message` };
539
+ }
540
+ catch (err) {
541
+ return { success: false, output: `Server tool error: ${err.message || err}` };
542
+ }
543
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Session Persistence — save/load conversations to disk
3
+ *
4
+ * Extracted from agent-loop.ts for single-responsibility.
5
+ * All consumers should import from agent-loop.ts (re-export facade).
6
+ */
7
+ import type Anthropic from "@anthropic-ai/sdk";
8
+ export interface SessionMeta {
9
+ id: string;
10
+ title: string;
11
+ model: string;
12
+ messageCount: number;
13
+ createdAt: string;
14
+ updatedAt: string;
15
+ cwd?: string;
16
+ }
17
+ export declare function saveSession(messages: Anthropic.MessageParam[], sessionId?: string): string;
18
+ export declare function loadSession(sessionId: string): {
19
+ meta: SessionMeta;
20
+ messages: Anthropic.MessageParam[];
21
+ } | null;
22
+ export declare function listSessions(limit?: number): SessionMeta[];
23
+ export declare function findLatestSessionForCwd(): SessionMeta | null;
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Session Persistence — save/load conversations to disk
3
+ *
4
+ * Extracted from agent-loop.ts for single-responsibility.
5
+ * All consumers should import from agent-loop.ts (re-export facade).
6
+ */
7
+ import { readFileSync, existsSync, mkdirSync, writeFileSync, readdirSync, appendFileSync } from "fs";
8
+ import { join } from "path";
9
+ import { homedir } from "os";
10
+ import { getModel } from "./model-manager.js";
11
+ const SESSIONS_DIR = join(homedir(), ".swagmanager", "sessions");
12
+ function ensureSessionsDir() {
13
+ if (!existsSync(SESSIONS_DIR))
14
+ mkdirSync(SESSIONS_DIR, { recursive: true });
15
+ }
16
+ export function saveSession(messages, sessionId) {
17
+ ensureSessionsDir();
18
+ const id = sessionId || `session-${Date.now()}`;
19
+ const meta = {
20
+ id,
21
+ title: extractSessionTitle(messages),
22
+ model: getModel(),
23
+ messageCount: messages.length,
24
+ createdAt: new Date().toISOString(),
25
+ updatedAt: new Date().toISOString(),
26
+ cwd: process.cwd(),
27
+ };
28
+ const data = JSON.stringify({ meta, messages }, null, 2);
29
+ writeFileSync(join(SESSIONS_DIR, `${id}.json`), data, "utf-8");
30
+ logSessionHistory(meta);
31
+ return id;
32
+ }
33
+ export function loadSession(sessionId) {
34
+ const path = join(SESSIONS_DIR, `${sessionId}.json`);
35
+ if (!existsSync(path))
36
+ return null;
37
+ try {
38
+ return JSON.parse(readFileSync(path, "utf-8"));
39
+ }
40
+ catch {
41
+ return null;
42
+ }
43
+ }
44
+ export function listSessions(limit = 20) {
45
+ ensureSessionsDir();
46
+ const files = readdirSync(SESSIONS_DIR)
47
+ .filter((f) => f.endsWith(".json"))
48
+ .sort()
49
+ .reverse()
50
+ .slice(0, limit);
51
+ const sessions = [];
52
+ for (const f of files) {
53
+ try {
54
+ const data = JSON.parse(readFileSync(join(SESSIONS_DIR, f), "utf-8"));
55
+ if (data.meta)
56
+ sessions.push(data.meta);
57
+ }
58
+ catch { /* skip corrupted */ }
59
+ }
60
+ return sessions;
61
+ }
62
+ const HISTORY_FILE = join(homedir(), ".swagmanager", "history.jsonl");
63
+ function logSessionHistory(meta) {
64
+ try {
65
+ const dir = join(homedir(), ".swagmanager");
66
+ if (!existsSync(dir))
67
+ mkdirSync(dir, { recursive: true });
68
+ const entry = {
69
+ display: meta.title,
70
+ project: meta.cwd || process.cwd(),
71
+ timestamp: meta.updatedAt,
72
+ sessionId: meta.id,
73
+ model: meta.model,
74
+ };
75
+ appendFileSync(HISTORY_FILE, JSON.stringify(entry) + "\n");
76
+ }
77
+ catch { /* best effort */ }
78
+ }
79
+ export function findLatestSessionForCwd() {
80
+ const cwd = process.cwd();
81
+ const sessions = listSessions(100);
82
+ return sessions.find(s => s.cwd === cwd) || null;
83
+ }
84
+ function extractSessionTitle(messages) {
85
+ // Use first user message as title (truncated)
86
+ for (const m of messages) {
87
+ if (m.role === "user" && typeof m.content === "string") {
88
+ return m.content.slice(0, 60) + (m.content.length > 60 ? "..." : "");
89
+ }
90
+ if (m.role === "user" && Array.isArray(m.content)) {
91
+ for (const block of m.content) {
92
+ if ("text" in block && typeof block.text === "string") {
93
+ return block.text.slice(0, 60) + (block.text.length > 60 ? "..." : "");
94
+ }
95
+ }
96
+ }
97
+ }
98
+ return "Untitled session";
99
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Subagent Worker — runs in separate thread to not block UI
3
+ *
4
+ * Usage: Spawned by parallel_tasks via worker_threads
5
+ *
6
+ * This worker runs `runSubagent()` in isolation, keeping the main thread
7
+ * (and Ink UI) responsive. Results are posted back via parentPort.
8
+ */
9
+ import { type SubagentOptions, type SubagentResult } from "./subagent.js";
10
+ export interface WorkerData {
11
+ options: SubagentOptions;
12
+ index: number;
13
+ }
14
+ export interface WorkerResult {
15
+ success: boolean;
16
+ index: number;
17
+ result?: SubagentResult;
18
+ error?: string;
19
+ }