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,224 @@
1
+ // server/lib/code-worker-pool.ts — Persistent pre-forked worker pool for code steps
2
+ // Eliminates fork-per-execution overhead. Workers are recycled after MAX_USES executions.
3
+ import { fork } from "node:child_process";
4
+ import { join, dirname } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ const __dirname_pool = dirname(fileURLToPath(import.meta.url));
7
+ const WORKER_PATH = join(__dirname_pool, "code-worker.js");
8
+ const POOL_SIZE = parseInt(process.env.CODE_WORKER_POOL_SIZE || "4", 10);
9
+ const MAX_USES = 50; // Recycle worker after N executions (prevent memory leaks)
10
+ const ACQUIRE_TIMEOUT_MS = 10_000; // Max wait to acquire a worker
11
+ const pool = [];
12
+ const waitQueue = [];
13
+ let initialized = false;
14
+ // Restricted env for worker processes — strip server secrets (API keys, DB URLs, etc.)
15
+ const WORKER_ENV = {
16
+ NODE_ENV: process.env.NODE_ENV || "production",
17
+ PATH: process.env.PATH || "/usr/local/bin:/usr/bin:/bin",
18
+ };
19
+ function spawnWorker() {
20
+ const child = fork(WORKER_PATH, [], {
21
+ stdio: "pipe",
22
+ serialization: "json",
23
+ env: WORKER_ENV,
24
+ });
25
+ const worker = { child, busy: false, uses: 0, ready: false };
26
+ child.on("message", (msg) => {
27
+ if (msg.type === "ready") {
28
+ worker.ready = true;
29
+ }
30
+ });
31
+ child.on("exit", () => {
32
+ // Remove dead worker from pool and replace it
33
+ const idx = pool.indexOf(worker);
34
+ if (idx >= 0) {
35
+ pool.splice(idx, 1);
36
+ try {
37
+ pool.push(spawnWorker());
38
+ }
39
+ catch {
40
+ // Can't respawn — pool shrinks
41
+ }
42
+ }
43
+ });
44
+ child.on("error", () => {
45
+ // Will trigger exit event above
46
+ });
47
+ return worker;
48
+ }
49
+ /** Initialize the worker pool. Safe to call multiple times. */
50
+ export function initWorkerPool() {
51
+ if (initialized)
52
+ return;
53
+ initialized = true;
54
+ for (let i = 0; i < POOL_SIZE; i++) {
55
+ try {
56
+ pool.push(spawnWorker());
57
+ }
58
+ catch {
59
+ console.error(`[worker-pool] Failed to spawn worker ${i + 1}/${POOL_SIZE}`);
60
+ }
61
+ }
62
+ console.log(`[worker-pool] Initialized with ${pool.length} workers`);
63
+ }
64
+ /** Acquire an available worker, or wait in queue. */
65
+ function acquireWorker() {
66
+ // Try to find an idle worker
67
+ const idle = pool.find(w => !w.busy && w.ready);
68
+ if (idle) {
69
+ idle.busy = true;
70
+ return Promise.resolve(idle);
71
+ }
72
+ // No idle worker — wait in queue
73
+ return new Promise((resolve, reject) => {
74
+ const timer = setTimeout(() => {
75
+ const idx = waitQueue.findIndex(q => q.resolve === resolve);
76
+ if (idx >= 0)
77
+ waitQueue.splice(idx, 1);
78
+ reject(new Error("Timed out waiting for available code worker"));
79
+ }, ACQUIRE_TIMEOUT_MS);
80
+ waitQueue.push({ resolve, timer });
81
+ });
82
+ }
83
+ /** Release a worker back to the pool. Recycle if MAX_USES reached. */
84
+ function releaseWorker(worker) {
85
+ worker.busy = false;
86
+ worker.uses++;
87
+ // Recycle if too many uses (prevent memory leaks in vm)
88
+ if (worker.uses >= MAX_USES) {
89
+ const idx = pool.indexOf(worker);
90
+ if (idx >= 0)
91
+ pool.splice(idx, 1);
92
+ try {
93
+ worker.child.kill("SIGTERM");
94
+ }
95
+ catch { /* process already dead */ }
96
+ try {
97
+ pool.push(spawnWorker());
98
+ }
99
+ catch { /* spawn failure handled by pool size checks */ }
100
+ return;
101
+ }
102
+ // Service waiting queue
103
+ if (waitQueue.length > 0) {
104
+ const next = waitQueue.shift();
105
+ clearTimeout(next.timer);
106
+ worker.busy = true;
107
+ next.resolve(worker);
108
+ }
109
+ }
110
+ /**
111
+ * Execute code using the worker pool. Falls back to direct fork if pool is unavailable.
112
+ */
113
+ export async function executeWithPool(req) {
114
+ if (!initialized || pool.length === 0) {
115
+ // Pool not available — fall back to one-shot fork
116
+ return executeDirectFork(req);
117
+ }
118
+ let worker;
119
+ try {
120
+ worker = await acquireWorker();
121
+ }
122
+ catch {
123
+ return executeDirectFork(req);
124
+ }
125
+ return new Promise((resolve) => {
126
+ let resolved = false;
127
+ const finish = (result) => {
128
+ if (resolved)
129
+ return;
130
+ resolved = true;
131
+ clearTimeout(killTimer);
132
+ releaseWorker(worker);
133
+ resolve(result);
134
+ };
135
+ const killTimer = setTimeout(() => {
136
+ finish({ success: false, error: `Code execution timed out after ${req.timeoutMs}ms` });
137
+ // Kill and replace the stuck worker
138
+ const idx = pool.indexOf(worker);
139
+ if (idx >= 0)
140
+ pool.splice(idx, 1);
141
+ try {
142
+ worker.child.kill("SIGKILL");
143
+ }
144
+ catch { /* process already dead */ }
145
+ try {
146
+ pool.push(spawnWorker());
147
+ }
148
+ catch { /* spawn failure handled by pool size checks */ }
149
+ }, req.timeoutMs + 3000);
150
+ // Listen for result (one-shot)
151
+ const onMessage = (msg) => {
152
+ if (msg.type === "result") {
153
+ worker.child.removeListener("message", onMessage);
154
+ finish(msg.success
155
+ ? { success: true, output: msg.output, logs: msg.logs }
156
+ : { success: false, error: msg.error, logs: msg.logs });
157
+ }
158
+ };
159
+ worker.child.on("message", onMessage);
160
+ // Send code
161
+ worker.child.send({
162
+ type: "execute",
163
+ code: req.code,
164
+ context: req.context,
165
+ timeoutMs: req.timeoutMs,
166
+ });
167
+ });
168
+ }
169
+ /** One-shot fork fallback (same as original) */
170
+ function executeDirectFork(req) {
171
+ return new Promise((resolve) => {
172
+ let resolved = false;
173
+ let child;
174
+ const finish = (result) => {
175
+ if (resolved)
176
+ return;
177
+ resolved = true;
178
+ try {
179
+ child?.kill("SIGKILL");
180
+ }
181
+ catch { /* process already dead */ }
182
+ resolve(result);
183
+ };
184
+ try {
185
+ child = fork(WORKER_PATH, [], { timeout: req.timeoutMs + 2000, stdio: "pipe", serialization: "json", env: WORKER_ENV });
186
+ }
187
+ catch {
188
+ return resolve({ success: false, error: "Failed to fork code worker" });
189
+ }
190
+ const killTimer = setTimeout(() => {
191
+ finish({ success: false, error: `Code execution timed out after ${req.timeoutMs}ms` });
192
+ }, req.timeoutMs + 3000);
193
+ child.on("message", (msg) => {
194
+ if (msg.type === "result") {
195
+ clearTimeout(killTimer);
196
+ finish(msg.success ? { success: true, output: msg.output } : { success: false, error: msg.error });
197
+ }
198
+ });
199
+ child.on("error", (err) => { clearTimeout(killTimer); finish({ success: false, error: `Worker error: ${err.message}` }); });
200
+ child.on("exit", (code) => { clearTimeout(killTimer); if (!resolved)
201
+ finish({ success: false, error: `Worker crashed (exit ${code})` }); });
202
+ child.send({ type: "execute", code: req.code, context: req.context, timeoutMs: req.timeoutMs });
203
+ });
204
+ }
205
+ /** Gracefully shutdown all workers */
206
+ export function shutdownPool() {
207
+ for (const w of pool) {
208
+ try {
209
+ w.child.kill("SIGTERM");
210
+ }
211
+ catch { /* process already dead */ }
212
+ }
213
+ pool.length = 0;
214
+ initialized = false;
215
+ }
216
+ /** Pool stats for monitoring */
217
+ export function getPoolStats() {
218
+ return {
219
+ total: pool.length,
220
+ busy: pool.filter(w => w.busy).length,
221
+ idle: pool.filter(w => !w.busy && w.ready).length,
222
+ queue: waitQueue.length,
223
+ };
224
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,188 @@
1
+ // server/lib/code-worker.ts — Isolated code execution worker
2
+ // Runs as a child process via fork(). Receives code + context via IPC,
3
+ // executes in a fresh vm context, returns result. Crash here cannot take down the server.
4
+ import { runInNewContext } from "node:vm";
5
+ import { randomUUID } from "node:crypto";
6
+ /**
7
+ * Create a safe wrapper function that cannot be used to reach the host Function constructor.
8
+ * Wraps a host function in a proxy whose prototype chain is severed — .constructor returns
9
+ * undefined instead of the host Function constructor.
10
+ */
11
+ function safeFunction(fn) {
12
+ // Create a wrapper function whose .constructor and .prototype are severed.
13
+ // We can't make a callable null-prototype object directly, so we override them on a regular function.
14
+ const safe = function (...args) {
15
+ return fn.apply(this, args);
16
+ };
17
+ Object.defineProperty(safe, "constructor", { value: undefined, writable: false, configurable: false });
18
+ Object.defineProperty(safe, "prototype", { value: undefined, writable: false, configurable: false });
19
+ // Freeze to prevent re-assignment of constructor
20
+ Object.freeze(safe);
21
+ return safe;
22
+ }
23
+ /**
24
+ * Create a safe namespace object (like Math, JSON, Buffer) whose properties
25
+ * cannot reach the host Function constructor through any prototype chain.
26
+ * All function-valued properties are wrapped via safeFunction().
27
+ */
28
+ function safeNamespace(source, keys) {
29
+ const ns = Object.create(null);
30
+ for (const key of keys) {
31
+ const val = source[key];
32
+ if (typeof val === "function") {
33
+ ns[key] = safeFunction(val);
34
+ }
35
+ else {
36
+ ns[key] = val;
37
+ }
38
+ }
39
+ return Object.freeze(ns);
40
+ }
41
+ // Only run when forked as a child process
42
+ if (process.send) {
43
+ process.on("message", (msg) => {
44
+ if (msg.type !== "execute")
45
+ return;
46
+ const logs = [];
47
+ // Validate code for dangerous patterns (defense-in-depth — sandbox isolation is primary defense)
48
+ const dangerousPatterns = [
49
+ /constructor\s*\[/i,
50
+ /constructor\s*\(/i,
51
+ /\.constructor/i,
52
+ /__proto__/i,
53
+ /prototype\s*\[/i,
54
+ /\bprocess\b/,
55
+ /\brequire\b/,
56
+ /\bimport\b/,
57
+ /\bglobalThis\b/,
58
+ /\bglobal\b/,
59
+ /\bFunction\b/,
60
+ /\beval\b/,
61
+ /\bReflect\b/,
62
+ /\bProxy\b/,
63
+ /\bSymbol\b/,
64
+ /\bWeakRef\b/,
65
+ ];
66
+ for (const pattern of dangerousPatterns) {
67
+ if (pattern.test(msg.code)) {
68
+ const result = {
69
+ type: "result",
70
+ success: false,
71
+ error: `Code contains blocked pattern: ${pattern.source}`,
72
+ };
73
+ process.send(result);
74
+ return;
75
+ }
76
+ }
77
+ // Build hardened sandbox — NO host constructors or direct function references.
78
+ // vm.runInNewContext provides its own Array, Object, String, Number, Boolean in the
79
+ // sandbox context. We do NOT pass host realm constructors, which would allow
80
+ // escaping via .constructor.constructor('return process')().
81
+ const sandbox = Object.create(null);
82
+ sandbox.steps = JSON.parse(JSON.stringify(msg.context.steps));
83
+ sandbox.trigger = JSON.parse(JSON.stringify(msg.context.trigger));
84
+ sandbox.input = msg.context.input != null ? JSON.parse(JSON.stringify(msg.context.input)) : undefined;
85
+ sandbox.output = undefined;
86
+ // console — safe: null-prototype object with wrapped log function
87
+ const logFn = safeFunction((...args) => logs.push(args.map(String).join(" ")));
88
+ const consoleObj = Object.create(null);
89
+ consoleObj.log = logFn;
90
+ sandbox.console = Object.freeze(consoleObj);
91
+ // JSON — safe namespace with only parse/stringify
92
+ sandbox.JSON = safeNamespace(JSON, ["parse", "stringify"]);
93
+ // Math — safe namespace (all methods wrapped, numeric constants preserved)
94
+ const mathKeys = [
95
+ "abs", "ceil", "floor", "round", "max", "min", "pow", "sqrt", "log", "log2", "log10",
96
+ "random", "sign", "trunc", "cbrt", "hypot", "clz32", "imul", "fround",
97
+ "sin", "cos", "tan", "asin", "acos", "atan", "atan2", "sinh", "cosh", "tanh",
98
+ "PI", "E", "LN2", "LN10", "LOG2E", "LOG10E", "SQRT2", "SQRT1_2",
99
+ ];
100
+ sandbox.Math = safeNamespace(Math, mathKeys);
101
+ // Date — only expose Date.now() as a safe function, not the Date constructor itself.
102
+ // The Date constructor is a host function whose .constructor leads to Function.
103
+ const dateNs = Object.create(null);
104
+ dateNs.now = safeFunction(Date.now);
105
+ sandbox.Date = Object.freeze(dateNs);
106
+ // Utility functions — each wrapped to sever .constructor chain
107
+ sandbox.parseInt = safeFunction(parseInt);
108
+ sandbox.parseFloat = safeFunction(parseFloat);
109
+ sandbox.isNaN = safeFunction(isNaN);
110
+ sandbox.isFinite = safeFunction(isFinite);
111
+ sandbox.encodeURIComponent = safeFunction(encodeURIComponent);
112
+ sandbox.decodeURIComponent = safeFunction(decodeURIComponent);
113
+ // URL — safe namespace with only parse method (no constructor exposure)
114
+ const urlNs = Object.create(null);
115
+ urlNs.parse = safeFunction((input) => {
116
+ try {
117
+ const u = new URL(input);
118
+ return { href: u.href, protocol: u.protocol, host: u.host, hostname: u.hostname,
119
+ port: u.port, pathname: u.pathname, search: u.search, hash: u.hash,
120
+ origin: u.origin, searchParams: Object.fromEntries(u.searchParams) };
121
+ }
122
+ catch {
123
+ return null;
124
+ }
125
+ });
126
+ urlNs.format = safeFunction((parts) => {
127
+ try {
128
+ return new URL(`${parts.protocol || "https:"}//${parts.host || parts.hostname || ""}${parts.pathname || ""}${parts.search || ""}`).href;
129
+ }
130
+ catch {
131
+ return null;
132
+ }
133
+ });
134
+ sandbox.URL = Object.freeze(urlNs);
135
+ // Buffer — safe namespace with wrapped from/alloc
136
+ const bufferNs = Object.create(null);
137
+ bufferNs.from = safeFunction((...args) => Buffer.from(args[0], args[1]));
138
+ bufferNs.alloc = safeFunction((size) => Buffer.alloc(size));
139
+ sandbox.Buffer = Object.freeze(bufferNs);
140
+ // crypto — safe namespace with wrapped randomUUID
141
+ const cryptoNs = Object.create(null);
142
+ cryptoNs.randomUUID = safeFunction(randomUUID);
143
+ sandbox.crypto = Object.freeze(cryptoNs);
144
+ // DO NOT expose host constructors: Array, Object, String, Number, Boolean.
145
+ // vm.runInNewContext creates its own versions of these in the sandbox context.
146
+ // Passing host constructors would allow: [].constructor.constructor('return process')()
147
+ // Block dangerous globals explicitly
148
+ sandbox.process = undefined;
149
+ sandbox.require = undefined;
150
+ sandbox.global = undefined;
151
+ sandbox.globalThis = undefined;
152
+ sandbox.Function = undefined;
153
+ sandbox.eval = undefined;
154
+ sandbox.setTimeout = undefined;
155
+ sandbox.setInterval = undefined;
156
+ sandbox.Reflect = undefined;
157
+ sandbox.Proxy = undefined;
158
+ sandbox.Symbol = undefined;
159
+ sandbox.WeakRef = undefined;
160
+ try {
161
+ const result = runInNewContext(msg.code, sandbox, {
162
+ timeout: msg.timeoutMs,
163
+ filename: "workflow-code-step",
164
+ breakOnSigint: true,
165
+ microtaskMode: "afterEvaluate",
166
+ });
167
+ const workerResult = {
168
+ type: "result",
169
+ success: true,
170
+ output: { result: sandbox.output ?? result, logs },
171
+ };
172
+ process.send(workerResult);
173
+ }
174
+ catch (err) {
175
+ const workerResult = {
176
+ type: "result",
177
+ success: false,
178
+ error: err.code === "ERR_SCRIPT_EXECUTION_TIMEOUT"
179
+ ? `Code execution timed out after ${msg.timeoutMs}ms`
180
+ : `Code error: ${err.message}`,
181
+ logs,
182
+ };
183
+ process.send(workerResult);
184
+ }
185
+ });
186
+ // Signal ready
187
+ process.send({ type: "ready" });
188
+ }
@@ -0,0 +1,32 @@
1
+ import Anthropic from "@anthropic-ai/sdk";
2
+ export interface CompactionResult {
3
+ success: boolean;
4
+ content?: string;
5
+ error?: string;
6
+ }
7
+ export interface PreCompactResult {
8
+ messages: Array<Record<string, unknown>>;
9
+ bytesRemoved: number;
10
+ }
11
+ /**
12
+ * Pre-compaction pass: strip redundant data before calling LLM summarizer.
13
+ * Mechanically removes large tool results already processed by the agent
14
+ * and thinking blocks from older turns. Returns modified messages array
15
+ * with reduced content so the LLM summarizer (or API context_management)
16
+ * has less to work with.
17
+ */
18
+ export declare function preCompact(messages: Array<Record<string, unknown>>): PreCompactResult;
19
+ /**
20
+ * Generate a compaction summary using Haiku.
21
+ *
22
+ * Takes the full conversation history and produces a concise summary
23
+ * that preserves all state needed to continue the conversation.
24
+ * Falls back gracefully — returns null content on failure so the
25
+ * caller can keep the unsummarized history.
26
+ */
27
+ export declare function generateCompaction(opts: {
28
+ anthropic: Anthropic;
29
+ messages: Array<Record<string, unknown>>;
30
+ systemPrompt: string;
31
+ instructions?: string;
32
+ }): Promise<CompactionResult>;
@@ -0,0 +1,162 @@
1
+ // server/lib/compaction-service.ts — Haiku-based conversation compaction
2
+ //
3
+ // Used for non-Anthropic providers (OpenAI, Gemini) that lack native
4
+ // server-side compaction. Calls Haiku to produce a transparent summary
5
+ // using the same instructions as compact_20260112.
6
+ const COMPACTION_MODEL = "claude-haiku-4-5-20251001";
7
+ const COMPACTION_MAX_TOKENS = 4096;
8
+ const COMPACTION_TIMEOUT_MS = 30_000;
9
+ const COMPACTION_INSTRUCTIONS = "Summarize the conversation preserving: (1) task goals and constraints, " +
10
+ "(2) files created/modified with paths, (3) decisions made and rationale, " +
11
+ "(4) errors encountered and resolutions, (5) exact next steps. " +
12
+ "Be concise but preserve all state needed to continue work without repeating mistakes.";
13
+ /**
14
+ * Pre-compaction pass: strip redundant data before calling LLM summarizer.
15
+ * Mechanically removes large tool results already processed by the agent
16
+ * and thinking blocks from older turns. Returns modified messages array
17
+ * with reduced content so the LLM summarizer (or API context_management)
18
+ * has less to work with.
19
+ */
20
+ export function preCompact(messages) {
21
+ let bytesRemoved = 0;
22
+ const compacted = messages.map((msg, idx) => {
23
+ if (msg.role !== "user")
24
+ return msg;
25
+ const content = msg.content;
26
+ if (!Array.isArray(content))
27
+ return msg;
28
+ const newContent = content.map((block) => {
29
+ // 1. Replace large tool results (>2KB) that the agent already acted on
30
+ if (block.type === "tool_result") {
31
+ const rc = typeof block.content === "string"
32
+ ? block.content
33
+ : JSON.stringify(block.content);
34
+ if (rc.length > 2048) {
35
+ // Check if there's a subsequent assistant message (meaning agent processed this)
36
+ const hasFollowup = messages
37
+ .slice(idx + 1)
38
+ .some((m) => m.role === "assistant");
39
+ if (hasFollowup) {
40
+ const originalSize = rc.length;
41
+ bytesRemoved += originalSize - 100;
42
+ return {
43
+ ...block,
44
+ content: `[Tool result: ${block.tool_use_id || "unknown"} returned ${Math.round(originalSize / 1024)}KB — agent processed and continued]`,
45
+ };
46
+ }
47
+ }
48
+ }
49
+ return block;
50
+ });
51
+ return { ...msg, content: newContent };
52
+ });
53
+ // 2. Remove thinking blocks older than 3 turns from the end
54
+ const thinkingCutoff = Math.max(0, compacted.length - 6); // 3 turns = ~6 messages
55
+ const result = compacted.map((msg, idx) => {
56
+ if (idx >= thinkingCutoff)
57
+ return msg;
58
+ if (msg.role !== "assistant")
59
+ return msg;
60
+ const content = msg.content;
61
+ if (!Array.isArray(content))
62
+ return msg;
63
+ const filtered = content.filter((block) => {
64
+ if (block.type === "thinking") {
65
+ bytesRemoved += String(block.thinking || "").length;
66
+ return false;
67
+ }
68
+ return true;
69
+ });
70
+ if (filtered.length === 0)
71
+ return msg; // Keep at least something
72
+ return { ...msg, content: filtered };
73
+ });
74
+ return { messages: result, bytesRemoved };
75
+ }
76
+ /**
77
+ * Generate a compaction summary using Haiku.
78
+ *
79
+ * Takes the full conversation history and produces a concise summary
80
+ * that preserves all state needed to continue the conversation.
81
+ * Falls back gracefully — returns null content on failure so the
82
+ * caller can keep the unsummarized history.
83
+ */
84
+ export async function generateCompaction(opts) {
85
+ const { anthropic, messages, systemPrompt, instructions } = opts;
86
+ // Build a text representation of the conversation for summarization
87
+ const conversationText = messages
88
+ .map((msg) => {
89
+ const role = msg.role;
90
+ let content;
91
+ if (typeof msg.content === "string") {
92
+ content = msg.content;
93
+ }
94
+ else if (Array.isArray(msg.content)) {
95
+ content = msg.content
96
+ .map((block) => {
97
+ if (block.type === "text")
98
+ return block.text;
99
+ if (block.type === "tool_use")
100
+ return `[Tool: ${block.name}]`;
101
+ if (block.type === "tool_result") {
102
+ const rc = block.content;
103
+ if (typeof rc === "string")
104
+ return rc.slice(0, 500);
105
+ if (Array.isArray(rc)) {
106
+ return rc
107
+ .map((b) => (b.type === "text" ? b.text.slice(0, 500) : `[${b.type}]`))
108
+ .join(" ");
109
+ }
110
+ return "[tool_result]";
111
+ }
112
+ if (block.type === "compaction")
113
+ return `[Previous summary: ${block.content.slice(0, 200)}...]`;
114
+ return `[${block.type}]`;
115
+ })
116
+ .join("\n");
117
+ }
118
+ else {
119
+ content = String(msg.content);
120
+ }
121
+ return `${role.toUpperCase()}:\n${content}`;
122
+ })
123
+ .join("\n\n---\n\n");
124
+ // Truncate if conversation is very long (Haiku has 200K context)
125
+ const maxInputChars = 400_000; // ~100K tokens
126
+ const truncatedConversation = conversationText.length > maxInputChars
127
+ ? conversationText.slice(-maxInputChars) + "\n\n... (earlier messages truncated)"
128
+ : conversationText;
129
+ const summaryInstructions = instructions || COMPACTION_INSTRUCTIONS;
130
+ try {
131
+ const controller = new AbortController();
132
+ const timeout = setTimeout(() => controller.abort(), COMPACTION_TIMEOUT_MS);
133
+ try {
134
+ const response = await anthropic.messages.create({
135
+ model: COMPACTION_MODEL,
136
+ max_tokens: COMPACTION_MAX_TOKENS,
137
+ system: `You are summarizing an AI agent conversation for context compaction. ${summaryInstructions}`,
138
+ messages: [
139
+ {
140
+ role: "user",
141
+ content: `Summarize this conversation. The agent's system prompt was:\n\n${systemPrompt.slice(0, 2000)}\n\n---\n\nConversation:\n\n${truncatedConversation}`,
142
+ },
143
+ ],
144
+ }, { signal: controller.signal });
145
+ clearTimeout(timeout);
146
+ const textBlock = response.content.find((b) => b.type === "text");
147
+ if (!textBlock || textBlock.type !== "text") {
148
+ return { success: false, error: "No text in compaction response" };
149
+ }
150
+ return { success: true, content: textBlock.text };
151
+ }
152
+ finally {
153
+ clearTimeout(timeout);
154
+ }
155
+ }
156
+ catch (err) {
157
+ return {
158
+ success: false,
159
+ error: `Compaction failed: ${err.message}`,
160
+ };
161
+ }
162
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Structured logging with pino — Phase 6.1
3
+ *
4
+ * JSON output in production, optional pino-pretty in dev.
5
+ * Provides child loggers with context and correlation IDs.
6
+ */
7
+ import pino from "pino";
8
+ declare const logger: pino.Logger<never, boolean>;
9
+ /**
10
+ * Create a child logger bound to a specific context (module name).
11
+ * Usage: const log = createLogger('agent-loop');
12
+ */
13
+ export declare function createLogger(context: string): pino.Logger;
14
+ /**
15
+ * Create a child logger with correlation IDs for request tracing.
16
+ * Usage: const log = withTrace(traceId, conversationId);
17
+ */
18
+ export declare function withTrace(traceId: string, conversationId?: string): pino.Logger;
19
+ export default logger;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Structured logging with pino — Phase 6.1
3
+ *
4
+ * JSON output in production, optional pino-pretty in dev.
5
+ * Provides child loggers with context and correlation IDs.
6
+ */
7
+ import pino from "pino";
8
+ const logger = pino({
9
+ level: process.env.LOG_LEVEL || "info",
10
+ formatters: {
11
+ level(label) {
12
+ return { level: label };
13
+ },
14
+ },
15
+ // Pretty print in dev if pino-pretty is installed, JSON in production
16
+ transport: process.env.NODE_ENV !== "production"
17
+ ? (() => {
18
+ try {
19
+ // pino-pretty is optional — if not installed, fall back to JSON
20
+ require.resolve("pino-pretty");
21
+ return { target: "pino-pretty", options: { colorize: true } };
22
+ }
23
+ catch {
24
+ return undefined;
25
+ }
26
+ })()
27
+ : undefined,
28
+ });
29
+ /**
30
+ * Create a child logger bound to a specific context (module name).
31
+ * Usage: const log = createLogger('agent-loop');
32
+ */
33
+ export function createLogger(context) {
34
+ return logger.child({ context });
35
+ }
36
+ /**
37
+ * Create a child logger with correlation IDs for request tracing.
38
+ * Usage: const log = withTrace(traceId, conversationId);
39
+ */
40
+ export function withTrace(traceId, conversationId) {
41
+ return logger.child({
42
+ traceId,
43
+ ...(conversationId ? { conversationId } : {}),
44
+ });
45
+ }
46
+ export default logger;