profclaw 0.0.1-beta.3 → 2.3.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 (535) hide show
  1. package/.env.example +14 -2
  2. package/CHANGELOG.md +54 -34
  3. package/CONTRIBUTING.md +23 -5
  4. package/Dockerfile +39 -23
  5. package/README.md +271 -72
  6. package/dist/agents/agent-summary.d.ts +44 -0
  7. package/dist/agents/agent-summary.js +176 -0
  8. package/dist/agents/auto-memory.d.ts +59 -0
  9. package/dist/agents/auto-memory.js +310 -0
  10. package/dist/agents/checkpoint-manager.d.ts +76 -0
  11. package/dist/agents/checkpoint-manager.js +156 -0
  12. package/dist/agents/circuit-breaker.d.ts +54 -0
  13. package/dist/agents/circuit-breaker.js +153 -0
  14. package/dist/agents/context-compactor.d.ts +70 -0
  15. package/dist/agents/context-compactor.js +361 -0
  16. package/dist/agents/conversation-branch.d.ts +55 -0
  17. package/dist/agents/conversation-branch.js +115 -0
  18. package/dist/agents/error-recovery.d.ts +44 -0
  19. package/dist/agents/error-recovery.js +197 -0
  20. package/dist/agents/events.d.ts +76 -0
  21. package/dist/agents/events.js +8 -0
  22. package/dist/agents/executor.d.ts +35 -1
  23. package/dist/agents/executor.js +573 -124
  24. package/dist/agents/file-snapshots.d.ts +72 -0
  25. package/dist/agents/file-snapshots.js +182 -0
  26. package/dist/agents/index.d.ts +2 -0
  27. package/dist/agents/orchestrator.d.ts +183 -0
  28. package/dist/agents/orchestrator.js +495 -0
  29. package/dist/agents/permissions.d.ts +63 -0
  30. package/dist/agents/permissions.js +138 -0
  31. package/dist/agents/plan-mode.d.ts +71 -0
  32. package/dist/agents/plan-mode.js +186 -0
  33. package/dist/agents/prompt-suggestions.d.ts +33 -0
  34. package/dist/agents/prompt-suggestions.js +208 -0
  35. package/dist/agents/rate-limit-monitor.d.ts +56 -0
  36. package/dist/agents/rate-limit-monitor.js +224 -0
  37. package/dist/agents/result-store.d.ts +37 -0
  38. package/dist/agents/result-store.js +104 -0
  39. package/dist/agents/session-diff.d.ts +43 -0
  40. package/dist/agents/session-diff.js +258 -0
  41. package/dist/agents/tool-loader.d.ts +65 -0
  42. package/dist/agents/tool-loader.js +273 -0
  43. package/dist/agents/transcript.d.ts +96 -0
  44. package/dist/agents/transcript.js +267 -0
  45. package/dist/agents/worktree-manager.d.ts +80 -0
  46. package/dist/agents/worktree-manager.js +203 -0
  47. package/dist/auth/middleware.js +14 -0
  48. package/dist/chat/agentic-executor.d.ts +7 -0
  49. package/dist/chat/agentic-executor.js +253 -30
  50. package/dist/chat/conversations.d.ts +2 -0
  51. package/dist/chat/conversations.js +8 -0
  52. package/dist/chat/execution/executor.d.ts +8 -0
  53. package/dist/chat/execution/executor.js +157 -103
  54. package/dist/chat/execution/index.js +5 -1
  55. package/dist/chat/execution/tool-router.js +40 -14
  56. package/dist/chat/execution/tools/canvas.d.ts +2 -2
  57. package/dist/chat/execution/tools/code-tools.d.ts +66 -0
  58. package/dist/chat/execution/tools/code-tools.js +353 -0
  59. package/dist/chat/execution/tools/complete-task.d.ts +2 -2
  60. package/dist/chat/execution/tools/cron-tool.d.ts +26 -10
  61. package/dist/chat/execution/tools/cron-tool.js +48 -3
  62. package/dist/chat/execution/tools/decompose-task.d.ts +58 -0
  63. package/dist/chat/execution/tools/decompose-task.js +166 -0
  64. package/dist/chat/execution/tools/exec.d.ts +5 -2
  65. package/dist/chat/execution/tools/exec.js +294 -155
  66. package/dist/chat/execution/tools/feed-tools.d.ts +77 -0
  67. package/dist/chat/execution/tools/feed-tools.js +142 -0
  68. package/dist/chat/execution/tools/file-ops.d.ts +1 -1
  69. package/dist/chat/execution/tools/github.d.ts +2 -2
  70. package/dist/chat/execution/tools/index.d.ts +15 -3
  71. package/dist/chat/execution/tools/index.js +125 -12
  72. package/dist/chat/execution/tools/integrations.d.ts +8 -8
  73. package/dist/chat/execution/tools/multi-patch.d.ts +53 -0
  74. package/dist/chat/execution/tools/multi-patch.js +373 -0
  75. package/dist/chat/execution/tools/openai-image-gen.d.ts +2 -2
  76. package/dist/chat/execution/tools/plan-tools.d.ts +80 -0
  77. package/dist/chat/execution/tools/plan-tools.js +134 -0
  78. package/dist/chat/execution/tools/profclaw-ops.d.ts +6 -6
  79. package/dist/chat/execution/tools/repl-tool.d.ts +46 -0
  80. package/dist/chat/execution/tools/repl-tool.js +207 -0
  81. package/dist/chat/execution/tools/sessions-send.d.ts +2 -2
  82. package/dist/chat/execution/tools/slack-actions.d.ts +2 -2
  83. package/dist/chat/execution/tools/system.d.ts +4 -4
  84. package/dist/chat/execution/tools/todo-tool.d.ts +72 -0
  85. package/dist/chat/execution/tools/todo-tool.js +152 -0
  86. package/dist/chat/execution/tools/tool-search.d.ts +50 -0
  87. package/dist/chat/execution/tools/tool-search.js +155 -0
  88. package/dist/chat/execution/types.d.ts +21 -14
  89. package/dist/chat/execution/workflows/executor.d.ts +11 -0
  90. package/dist/chat/execution/workflows/executor.js +99 -1
  91. package/dist/chat/execution/workflows/types.d.ts +35 -2
  92. package/dist/chat/format/index.d.ts +1 -0
  93. package/dist/chat/format/index.js +1 -0
  94. package/dist/chat/format/markdown-convert.d.ts +23 -0
  95. package/dist/chat/format/markdown-convert.js +149 -0
  96. package/dist/chat/index.d.ts +1 -1
  97. package/dist/chat/index.js +1 -1
  98. package/dist/chat/message-handler.js +58 -6
  99. package/dist/chat/proactive/index.d.ts +62 -0
  100. package/dist/chat/proactive/index.js +147 -0
  101. package/dist/chat/prompt-adapter.js +2 -0
  102. package/dist/chat/providers/irc/index.d.ts +2 -2
  103. package/dist/chat/providers/slack/index.d.ts +2 -2
  104. package/dist/chat/providers/telegram/index.d.ts +2 -0
  105. package/dist/chat/providers/telegram/index.js +21 -2
  106. package/dist/chat/system-prompts.d.ts +3 -3
  107. package/dist/chat/system-prompts.js +116 -61
  108. package/dist/chat/tools.d.ts +6 -6
  109. package/dist/cli/commands/auth.js +64 -1
  110. package/dist/cli/commands/chat.js +1327 -138
  111. package/dist/cli/commands/doctor.js +222 -16
  112. package/dist/cli/commands/history.d.ts +3 -0
  113. package/dist/cli/commands/history.js +138 -0
  114. package/dist/cli/commands/import.d.ts +44 -0
  115. package/dist/cli/commands/import.js +526 -0
  116. package/dist/cli/commands/init.d.ts +13 -0
  117. package/dist/cli/commands/init.js +381 -0
  118. package/dist/cli/commands/models.js +39 -26
  119. package/dist/cli/commands/onboard.js +263 -98
  120. package/dist/cli/commands/plan.d.ts +13 -0
  121. package/dist/cli/commands/plan.js +184 -0
  122. package/dist/cli/commands/serve.d.ts +7 -0
  123. package/dist/cli/commands/serve.js +77 -18
  124. package/dist/cli/commands/setup.js +375 -91
  125. package/dist/cli/commands/status.js +54 -0
  126. package/dist/cli/commands/tools.js +13 -13
  127. package/dist/cli/commands/tui.js +26 -1
  128. package/dist/cli/index.js +19 -6
  129. package/dist/cli/ink/App.d.ts +13 -0
  130. package/dist/cli/ink/App.js +6 -0
  131. package/dist/cli/ink/ChatApp.d.ts +60 -0
  132. package/dist/cli/ink/ChatApp.js +325 -0
  133. package/dist/cli/ink/DashboardApp.d.ts +3 -0
  134. package/dist/cli/ink/DashboardApp.js +143 -0
  135. package/dist/cli/ink/components/AgentStatus.d.ts +17 -0
  136. package/dist/cli/ink/components/AgentStatus.js +33 -0
  137. package/dist/cli/ink/components/ChannelList.d.ts +17 -0
  138. package/dist/cli/ink/components/ChannelList.js +33 -0
  139. package/dist/cli/ink/components/ConnectionStatus.d.ts +19 -0
  140. package/dist/cli/ink/components/ConnectionStatus.js +50 -0
  141. package/dist/cli/ink/components/CostBar.d.ts +19 -0
  142. package/dist/cli/ink/components/CostBar.js +35 -0
  143. package/dist/cli/ink/components/HookStatus.d.ts +15 -0
  144. package/dist/cli/ink/components/HookStatus.js +7 -0
  145. package/dist/cli/ink/components/PermissionPrompt.d.ts +21 -0
  146. package/dist/cli/ink/components/PermissionPrompt.js +51 -0
  147. package/dist/cli/ink/components/PlanView.d.ts +23 -0
  148. package/dist/cli/ink/components/PlanView.js +26 -0
  149. package/dist/cli/ink/components/ProviderList.d.ts +16 -0
  150. package/dist/cli/ink/components/ProviderList.js +16 -0
  151. package/dist/cli/ink/components/SessionHeader.d.ts +15 -0
  152. package/dist/cli/ink/components/SessionHeader.js +15 -0
  153. package/dist/cli/ink/components/StreamingMessage.d.ts +20 -0
  154. package/dist/cli/ink/components/StreamingMessage.js +27 -0
  155. package/dist/cli/ink/components/SuggestionBar.d.ts +15 -0
  156. package/dist/cli/ink/components/SuggestionBar.js +28 -0
  157. package/dist/cli/ink/components/SystemHealth.d.ts +20 -0
  158. package/dist/cli/ink/components/SystemHealth.js +39 -0
  159. package/dist/cli/ink/components/TaskList.d.ts +17 -0
  160. package/dist/cli/ink/components/TaskList.js +59 -0
  161. package/dist/cli/ink/components/ToolCall.d.ts +17 -0
  162. package/dist/cli/ink/components/ToolCall.js +43 -0
  163. package/dist/cli/ink/index.d.ts +23 -0
  164. package/dist/cli/ink/index.js +15 -0
  165. package/dist/cli/ink/keybindings.d.ts +35 -0
  166. package/dist/cli/ink/keybindings.js +105 -0
  167. package/dist/cli/ink/markdown-renderer.d.ts +47 -0
  168. package/dist/cli/ink/markdown-renderer.js +380 -0
  169. package/dist/cli/ink/output-styles.d.ts +39 -0
  170. package/dist/cli/ink/output-styles.js +119 -0
  171. package/dist/cli/interactive/auto-serve.d.ts +38 -0
  172. package/dist/cli/interactive/auto-serve.js +209 -0
  173. package/dist/cli/interactive/index.d.ts +25 -0
  174. package/dist/cli/interactive/index.js +23 -0
  175. package/dist/cli/interactive/picker.d.ts +28 -0
  176. package/dist/cli/interactive/picker.js +153 -0
  177. package/dist/cli/interactive/renderer.d.ts +26 -0
  178. package/dist/cli/interactive/renderer.js +555 -0
  179. package/dist/cli/interactive/repl.d.ts +14 -0
  180. package/dist/cli/interactive/repl.js +1516 -0
  181. package/dist/cli/interactive/stream-client.d.ts +59 -0
  182. package/dist/cli/interactive/stream-client.js +506 -0
  183. package/dist/cli/interactive/types.d.ts +121 -0
  184. package/dist/cli/interactive/types.js +10 -0
  185. package/dist/cli/providers.d.ts +15 -0
  186. package/dist/cli/providers.js +41 -0
  187. package/dist/cli/utils/api.d.ts +9 -0
  188. package/dist/cli/utils/api.js +58 -2
  189. package/dist/core/deployment.js +2 -0
  190. package/dist/cron/natural-language.d.ts +54 -0
  191. package/dist/cron/natural-language.js +569 -0
  192. package/dist/cron/scheduler.d.ts +34 -4
  193. package/dist/cron/scheduler.js +141 -9
  194. package/dist/cron/templates.d.ts +1 -1
  195. package/dist/cron/templates.js +219 -0
  196. package/dist/feeds/feed-engine.d.ts +149 -0
  197. package/dist/feeds/feed-engine.js +600 -0
  198. package/dist/feeds/index.d.ts +9 -0
  199. package/dist/feeds/index.js +8 -0
  200. package/dist/gateway/router.js +6 -1
  201. package/dist/hooks/built-in/audit-log.d.ts +11 -0
  202. package/dist/hooks/built-in/audit-log.js +84 -0
  203. package/dist/hooks/built-in/cost-warning.d.ts +10 -0
  204. package/dist/hooks/built-in/cost-warning.js +59 -0
  205. package/dist/hooks/built-in/dangerous-tool.d.ts +32 -0
  206. package/dist/hooks/built-in/dangerous-tool.js +80 -0
  207. package/dist/hooks/built-in/index.d.ts +14 -0
  208. package/dist/hooks/built-in/index.js +20 -0
  209. package/dist/hooks/index.d.ts +3 -0
  210. package/dist/hooks/index.js +6 -0
  211. package/dist/hooks/loader.d.ts +17 -0
  212. package/dist/hooks/loader.js +142 -0
  213. package/dist/hooks/registry.d.ts +62 -0
  214. package/dist/hooks/registry.js +127 -0
  215. package/dist/integrations/web-search.js +6 -3
  216. package/dist/memory/index.d.ts +1 -0
  217. package/dist/memory/index.js +2 -0
  218. package/dist/memory/memory-service.js +14 -13
  219. package/dist/memory/observational.d.ts +62 -0
  220. package/dist/memory/observational.js +396 -0
  221. package/dist/middleware/content-filter.d.ts +22 -0
  222. package/dist/middleware/content-filter.js +69 -0
  223. package/dist/middleware/request-validator.d.ts +159 -0
  224. package/dist/middleware/request-validator.js +93 -0
  225. package/dist/plugins/sdk.d.ts +22 -0
  226. package/dist/plugins/sdk.js +17 -1
  227. package/dist/projects/types.d.ts +10 -10
  228. package/dist/providers/ai-sdk.d.ts +11 -0
  229. package/dist/providers/ai-sdk.js +97 -18
  230. package/dist/providers/core/models.js +67 -1
  231. package/dist/providers/core/types.d.ts +3 -0
  232. package/dist/providers/index.d.ts +1 -0
  233. package/dist/providers/index.js +1 -0
  234. package/dist/providers/smart-router.d.ts +118 -0
  235. package/dist/providers/smart-router.js +685 -0
  236. package/dist/providers/types.d.ts +16 -16
  237. package/dist/routes/agents.js +50 -0
  238. package/dist/routes/auth.js +8 -1
  239. package/dist/routes/channels.d.ts +12 -0
  240. package/dist/routes/channels.js +166 -0
  241. package/dist/routes/chat.js +259 -14
  242. package/dist/routes/costs.js +32 -0
  243. package/dist/routes/cron.js +80 -5
  244. package/dist/routes/feeds.d.ts +10 -0
  245. package/dist/routes/feeds.js +168 -0
  246. package/dist/routes/mcp.js +21 -0
  247. package/dist/routes/memory.js +130 -0
  248. package/dist/routes/skills.js +129 -0
  249. package/dist/routes/teams.d.ts +9 -0
  250. package/dist/routes/teams.js +210 -0
  251. package/dist/routes/tools.js +14 -0
  252. package/dist/routes/users.js +2 -0
  253. package/dist/server/route-loader.js +4 -1
  254. package/dist/server/stream-bridge.d.ts +19 -0
  255. package/dist/server/stream-bridge.js +38 -0
  256. package/dist/server.js +421 -141
  257. package/dist/skills/marketplace.d.ts +87 -0
  258. package/dist/skills/marketplace.js +268 -0
  259. package/dist/storage/index.d.ts +14 -0
  260. package/dist/storage/index.js +64 -3
  261. package/dist/storage/schema.d.ts +4 -4
  262. package/dist/storage/schema.js +1 -1
  263. package/dist/teams/index.d.ts +110 -0
  264. package/dist/teams/index.js +365 -0
  265. package/dist/tickets/types.d.ts +39 -39
  266. package/dist/types/summary.d.ts +5 -5
  267. package/dist/utils/auto-update.d.ts +23 -0
  268. package/dist/utils/auto-update.js +157 -0
  269. package/dist/utils/offline-detect.d.ts +55 -0
  270. package/dist/utils/offline-detect.js +121 -0
  271. package/dist/utils/pid-file.d.ts +36 -0
  272. package/dist/utils/pid-file.js +125 -0
  273. package/dist/utils/prevent-sleep.d.ts +27 -0
  274. package/dist/utils/prevent-sleep.js +95 -0
  275. package/dist/utils/sanitizer.d.ts +57 -0
  276. package/dist/utils/sanitizer.js +174 -0
  277. package/docker-compose.yml +13 -137
  278. package/package.json +31 -6
  279. package/skills/deploy-cloudflare/SKILL.md +62 -0
  280. package/skills/deploy-vercel/SKILL.md +67 -0
  281. package/skills/docker-deploy/SKILL.md +158 -0
  282. package/skills/full-stack-builder/SKILL.md +92 -0
  283. package/skills/git-workflow/SKILL.md +3 -3
  284. package/skills/humanizer/SKILL.md +488 -0
  285. package/skills/profclaw-assistant/SKILL.md +85 -56
  286. package/ui/dist/assets/ActivityView-Da6HQ8ws.js +1 -0
  287. package/ui/dist/assets/AgentList-H58R0kX3.js +1 -0
  288. package/ui/dist/assets/AnalyticsDashboard-B28AeRsc.js +1 -0
  289. package/ui/dist/assets/AreaChart-DwLzeAQ8.js +1 -0
  290. package/ui/dist/assets/CartesianChart-piKQsTAR.js +36 -0
  291. package/ui/dist/assets/ChatView-Dbj8rxU5.js +10 -0
  292. package/ui/dist/assets/CostsDashboard-C6F5df0F.js +1 -0
  293. package/ui/dist/assets/EstimateSelect-wga0rUKX.js +1 -0
  294. package/ui/dist/assets/FeedDashboard-Ef6FEzEf.js +1 -0
  295. package/ui/dist/assets/FloatingChatbot-0yhkUeCq.js +2 -0
  296. package/ui/dist/assets/InviteCodeManagement-tUXfx4Bc.js +2 -0
  297. package/ui/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  298. package/ui/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  299. package/ui/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  300. package/ui/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  301. package/ui/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  302. package/ui/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  303. package/ui/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  304. package/ui/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  305. package/ui/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  306. package/ui/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  307. package/ui/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  308. package/ui/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  309. package/ui/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  310. package/ui/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  311. package/ui/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  312. package/ui/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  313. package/ui/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  314. package/ui/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  315. package/ui/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  316. package/ui/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  317. package/ui/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  318. package/ui/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  319. package/ui/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  320. package/ui/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  321. package/ui/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  322. package/ui/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  323. package/ui/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  324. package/ui/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  325. package/ui/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  326. package/ui/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  327. package/ui/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  328. package/ui/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  329. package/ui/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  330. package/ui/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  331. package/ui/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  332. package/ui/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  333. package/ui/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  334. package/ui/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  335. package/ui/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  336. package/ui/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  337. package/ui/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  338. package/ui/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  339. package/ui/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  340. package/ui/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  341. package/ui/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  342. package/ui/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  343. package/ui/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  344. package/ui/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  345. package/ui/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  346. package/ui/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  347. package/ui/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  348. package/ui/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  349. package/ui/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  350. package/ui/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  351. package/ui/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  352. package/ui/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  353. package/ui/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  354. package/ui/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  355. package/ui/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  356. package/ui/dist/assets/MarketplacePage-DGrLtolR.js +1 -0
  357. package/ui/dist/assets/{ProjectDetail-DlYUcOss.js → ProjectDetail-BgWvA6UP.js} +2 -2
  358. package/ui/dist/assets/ProjectIcon-TDuliqwg.js +1 -0
  359. package/ui/dist/assets/ProjectList-DXgM4187.js +1 -0
  360. package/ui/dist/assets/{Settings-CgPqzUvj.js → Settings-xA8Zhi3p.js} +3 -3
  361. package/ui/dist/assets/StatusIndicator-B8vfFAxg.js +1 -0
  362. package/ui/dist/assets/{SummaryDetail-CwLAfHgJ.js → SummaryDetail-eedRfTtF.js} +2 -2
  363. package/ui/dist/assets/SummaryList-CLNX2P84.js +1 -0
  364. package/ui/dist/assets/TaskDetail-BtWclNiR.js +1 -0
  365. package/ui/dist/assets/TaskList-JjODAgSc.js +1 -0
  366. package/ui/dist/assets/TeamsPage-CBK5iAyx.js +1 -0
  367. package/ui/dist/assets/TicketBoard-C9aJNNYp.js +1 -0
  368. package/ui/dist/assets/TicketDetail-CV5JmL31.js +1 -0
  369. package/ui/dist/assets/TicketList-68V4lzUz.js +1 -0
  370. package/ui/dist/assets/UserManagement-DKTb6UpE.js +2 -0
  371. package/ui/dist/assets/ViewSwitcher-BvdQht2Y.js +1 -0
  372. package/ui/dist/assets/alert-dialog-O6qBOv_f.js +1 -0
  373. package/ui/dist/assets/{archive-CGck7gxR.js → archive-CX7UvZpf.js} +1 -1
  374. package/ui/dist/assets/badge-BbfnwDJP.js +1 -0
  375. package/ui/dist/assets/{bug-DqSGPUUE.js → bug-3WX0394-.js} +1 -1
  376. package/ui/dist/assets/{calendar-BuZ4zbPW.js → calendar-buq77TaS.js} +1 -1
  377. package/ui/dist/assets/{chevron-up-DcJ4LL_N.js → chevron-up-4ymsBJRo.js} +1 -1
  378. package/ui/dist/assets/{circle-x-C78PNZjF.js → circle-x-ByXtwR6Y.js} +1 -1
  379. package/ui/dist/assets/constants-CVdDh7p0.js +1 -0
  380. package/ui/dist/assets/{cpu-Ck6AoG8c.js → cpu-CiuoCeDY.js} +1 -1
  381. package/ui/dist/assets/{database-CA2w8Lvt.js → database-BWXSBg5a.js} +1 -1
  382. package/ui/dist/assets/dialog-lDg4NvMV.js +1 -0
  383. package/ui/dist/assets/dollar-sign-Dmijd6DP.js +1 -0
  384. package/ui/dist/assets/{ellipsis-FhHKRbfl.js → ellipsis-vT-5SVIp.js} +1 -1
  385. package/ui/dist/assets/{ellipsis-vertical-CO0UqE4-.js → ellipsis-vertical-B4tAX_bL.js} +1 -1
  386. package/ui/dist/assets/feature-markdown-DBo5Q7uq.js +289 -0
  387. package/ui/dist/assets/{file-code-ChnhYYj1.js → file-code-ChOWdKXf.js} +1 -1
  388. package/ui/dist/assets/{folder-open-CHwA1-ve.js → folder-open-CkOUbIiP.js} +1 -1
  389. package/ui/dist/assets/{funnel-ByEjRFNB.js → funnel-uZkoSPP-.js} +1 -1
  390. package/ui/dist/assets/{git-branch-GTOPQgU4.js → git-branch-CwvUxj7C.js} +1 -1
  391. package/ui/dist/assets/{git-commit-horizontal-F5M84Kut.js → git-commit-horizontal-lxPzCvhD.js} +1 -1
  392. package/ui/dist/assets/{globe-CQWfr7hT.js → globe-D_WpJdsj.js} +1 -1
  393. package/ui/dist/assets/{hash-nKC9gtLg.js → hash-CA0hJ7vh.js} +1 -1
  394. package/ui/dist/assets/{history-DfFSA9xJ.js → history-CEWMLELs.js} +1 -1
  395. package/ui/dist/assets/inbox-ehtOptTV.js +1 -0
  396. package/ui/dist/assets/{index-a4Fxq0Hb.js → index-C1rhbml4.js} +3 -3
  397. package/ui/dist/assets/index-CR8gfCHv.js +1 -0
  398. package/ui/dist/assets/index-DVXMpWai.js +1 -0
  399. package/ui/dist/assets/index-DaDJMM2v.js +1 -0
  400. package/ui/dist/assets/index-Djdq_0xu.js +1 -0
  401. package/ui/dist/assets/index-M46Gcb8K.css +1 -0
  402. package/ui/dist/assets/index-sEBN7w8J.js +109 -0
  403. package/ui/dist/assets/layers-C6Mhf8Nk.js +1 -0
  404. package/ui/dist/assets/{layout-dashboard-WmgjMhWF.js → layout-dashboard-BRUM0kpT.js} +1 -1
  405. package/ui/dist/assets/{lightbulb-Bo8hEr7B.js → lightbulb-CN2q1O5l.js} +1 -1
  406. package/ui/dist/assets/{link-2-YqrpQXBH.js → link-2-CC4E28yJ.js} +1 -1
  407. package/ui/dist/assets/markdown-renderer-C6ENbB0u.js +10 -0
  408. package/ui/dist/assets/markdown-renderer-CfVKi3_s.css +1 -0
  409. package/ui/dist/assets/{message-square-CD0H5hot.js → message-square-Bbb241am.js} +1 -1
  410. package/ui/dist/assets/package-CwT3IzFx.js +1 -0
  411. package/ui/dist/assets/{pencil-DpFnj5dn.js → pencil-CDmiHzoj.js} +1 -1
  412. package/ui/dist/assets/{play-DVgea_jT.js → play-tjy5jMQX.js} +1 -1
  413. package/ui/dist/assets/popover-CFHZf3pE.js +1 -0
  414. package/ui/dist/assets/progress-BboUHuLf.js +1 -0
  415. package/ui/dist/assets/{rocket-BFxPYGx9.js → rocket--8ZHgdXJ.js} +1 -1
  416. package/ui/dist/assets/select-n108rXny.js +1 -0
  417. package/ui/dist/assets/{send-eYnW_7n-.js → send-Cu7MR2Qh.js} +1 -1
  418. package/ui/dist/assets/server-DmodxZp-.js +1 -0
  419. package/ui/dist/assets/{settings-2-scXUOuE_.js → settings-2-CkRNqkxj.js} +1 -1
  420. package/ui/dist/assets/sheet-7aUJkyzU.js +1 -0
  421. package/ui/dist/assets/{shield-check-PEr7nmVw.js → shield-check-ViI3nMKE.js} +1 -1
  422. package/ui/dist/assets/{shield-off-CLtXio62.js → shield-off-DN3tagkn.js} +1 -1
  423. package/ui/dist/assets/skeleton-C0E1Nfro.js +1 -0
  424. package/ui/dist/assets/{smartphone-DusFfY0U.js → smartphone-5FghiXpA.js} +1 -1
  425. package/ui/dist/assets/{square-e1yxB2eR.js → square-DiNSx8K7.js} +1 -1
  426. package/ui/dist/assets/star-DhdFVidS.js +1 -0
  427. package/ui/dist/assets/switch-CIgVbZqQ.js +1 -0
  428. package/ui/dist/assets/table-BC56FVMV.js +1 -0
  429. package/ui/dist/assets/{tag-tAwMmQ9G.js → tag-CH_vQliw.js} +1 -1
  430. package/ui/dist/assets/textarea-CxDiKFXZ.js +1 -0
  431. package/ui/dist/assets/{timer-Vel1N3RP.js → timer-CdGVTb60.js} +1 -1
  432. package/ui/dist/assets/tooltip-BuW9A5UO.js +1 -0
  433. package/ui/dist/assets/{trash-2-eM-CNwsZ.js → trash-2-CucZJL0N.js} +1 -1
  434. package/ui/dist/assets/trending-down-BCezC1z2.js +1 -0
  435. package/ui/dist/assets/{trending-up-BGafmjRo.js → trending-up-C5OYX2k2.js} +1 -1
  436. package/ui/dist/assets/vendor-query-C0p7b53x.js +4 -0
  437. package/ui/dist/assets/vendor-react-Mc5_kfYG.js +12 -0
  438. package/ui/dist/assets/vendor-ui-CXPAUBFv.js +51 -0
  439. package/ui/dist/assets/{wand-sparkles-DPvcd9Pi.js → wand-sparkles-BaH6pJnc.js} +1 -1
  440. package/ui/dist/assets/{wrench-B8s3zYVV.js → wrench-CjxjP6ok.js} +1 -1
  441. package/ui/dist/index.html +5 -2
  442. package/dist/chat/auto-reply.d.ts +0 -90
  443. package/dist/chat/auto-reply.js +0 -272
  444. package/dist/chat/execution/pty.d.ts +0 -92
  445. package/dist/chat/execution/pty.js +0 -285
  446. package/dist/chat/execution/tools/glinr-ops.d.ts +0 -202
  447. package/dist/chat/execution/tools/glinr-ops.js +0 -694
  448. package/dist/cli/commands/browser.d.ts +0 -3
  449. package/dist/cli/commands/browser.js +0 -138
  450. package/dist/cli/commands/canvas.d.ts +0 -3
  451. package/dist/cli/commands/canvas.js +0 -155
  452. package/dist/cli/commands/nodes.d.ts +0 -3
  453. package/dist/cli/commands/nodes.js +0 -193
  454. package/dist/diagnostics/exporters.d.ts +0 -21
  455. package/dist/diagnostics/exporters.js +0 -138
  456. package/dist/diagnostics/index.d.ts +0 -10
  457. package/dist/diagnostics/index.js +0 -9
  458. package/dist/diagnostics/middleware.d.ts +0 -12
  459. package/dist/diagnostics/middleware.js +0 -58
  460. package/dist/diagnostics/tracer.d.ts +0 -133
  461. package/dist/diagnostics/tracer.js +0 -257
  462. package/dist/discovery/index.d.ts +0 -8
  463. package/dist/discovery/index.js +0 -7
  464. package/dist/discovery/mdns.d.ts +0 -40
  465. package/dist/discovery/mdns.js +0 -249
  466. package/dist/plugins/registry.d.ts +0 -122
  467. package/dist/plugins/registry.js +0 -352
  468. package/dist/plugins/sandbox.d.ts +0 -77
  469. package/dist/plugins/sandbox.js +0 -287
  470. package/dist/plugins/search/brave.d.ts +0 -9
  471. package/dist/plugins/search/brave.js +0 -157
  472. package/dist/plugins/search/duckduckgo.d.ts +0 -9
  473. package/dist/plugins/search/duckduckgo.js +0 -171
  474. package/dist/plugins/search/index.d.ts +0 -13
  475. package/dist/plugins/search/index.js +0 -23
  476. package/dist/plugins/search/searxng.d.ts +0 -9
  477. package/dist/plugins/search/searxng.js +0 -170
  478. package/dist/plugins/search/serper.d.ts +0 -9
  479. package/dist/plugins/search/serper.js +0 -135
  480. package/dist/plugins/search/tavily.d.ts +0 -9
  481. package/dist/plugins/search/tavily.js +0 -146
  482. package/dist/providers/adapters/anthropic.d.ts +0 -26
  483. package/dist/providers/adapters/anthropic.js +0 -306
  484. package/dist/providers/adapters/base.d.ts +0 -53
  485. package/dist/providers/adapters/base.js +0 -99
  486. package/dist/providers/adapters/ollama.d.ts +0 -30
  487. package/dist/providers/adapters/ollama.js +0 -292
  488. package/dist/utils/ai-cache.d.ts +0 -72
  489. package/dist/utils/ai-cache.js +0 -204
  490. package/dist/utils/file-lock.d.ts +0 -85
  491. package/dist/utils/file-lock.js +0 -204
  492. package/ui/dist/assets/ActivityView-DIPQSJ_d.js +0 -1
  493. package/ui/dist/assets/AgentList-CqyaYrcq.js +0 -1
  494. package/ui/dist/assets/AnalyticsDashboard-C-mjuGHT.js +0 -1
  495. package/ui/dist/assets/AreaChart-CfYWmbtr.js +0 -1
  496. package/ui/dist/assets/CartesianChart-BaQ3It0c.js +0 -36
  497. package/ui/dist/assets/ChatView-DxxQy6pl.js +0 -10
  498. package/ui/dist/assets/CostsDashboard-CDBBcGkg.js +0 -1
  499. package/ui/dist/assets/EstimateSelect-CDjf5qKl.js +0 -1
  500. package/ui/dist/assets/FloatingChatbot-CZHjvJIe.js +0 -2
  501. package/ui/dist/assets/InviteCodeManagement-DTm-IPTm.js +0 -2
  502. package/ui/dist/assets/ProjectIcon-BMRpnM04.js +0 -1
  503. package/ui/dist/assets/ProjectList-D85TN0i0.js +0 -4
  504. package/ui/dist/assets/StatusIndicator-yig0uZEA.js +0 -1
  505. package/ui/dist/assets/SummaryList-BvS5Idnu.js +0 -1
  506. package/ui/dist/assets/TaskDetail-zarC1fOt.js +0 -1
  507. package/ui/dist/assets/TaskList-CyJq3ANE.js +0 -1
  508. package/ui/dist/assets/TicketBoard-BlWr3-iH.js +0 -1
  509. package/ui/dist/assets/TicketDetail-DmqWKj2W.js +0 -1
  510. package/ui/dist/assets/TicketList-Scj4pMWa.js +0 -1
  511. package/ui/dist/assets/UserManagement-BH7uG7dw.js +0 -2
  512. package/ui/dist/assets/ViewSwitcher-3HzZUdWn.js +0 -1
  513. package/ui/dist/assets/alert-dialog-DWPb2IfQ.js +0 -7
  514. package/ui/dist/assets/badge-DEmc3Z_l.js +0 -1
  515. package/ui/dist/assets/brain-CfFdLVqb.js +0 -1
  516. package/ui/dist/assets/constants-DhDjZe4P.js +0 -1
  517. package/ui/dist/assets/dialog-BOdGWhgE.js +0 -1
  518. package/ui/dist/assets/index--JkIxVXb.js +0 -67
  519. package/ui/dist/assets/index-Ca3vk7Gq.js +0 -1
  520. package/ui/dist/assets/index-DHGvP9mp.js +0 -1
  521. package/ui/dist/assets/index-DeI8lKRR.js +0 -1
  522. package/ui/dist/assets/index-WclMPci1.css +0 -1
  523. package/ui/dist/assets/index-Z4-RtZcs.js +0 -1
  524. package/ui/dist/assets/layers-DFeLExSa.js +0 -1
  525. package/ui/dist/assets/markdown-renderer-Croje6Xe.js +0 -36
  526. package/ui/dist/assets/popover-JXecfWG5.js +0 -1
  527. package/ui/dist/assets/progress-C1ghf1Fu.js +0 -1
  528. package/ui/dist/assets/select-MADP8m6Q.js +0 -1
  529. package/ui/dist/assets/server-CPMaysH8.js +0 -1
  530. package/ui/dist/assets/sheet-r6NSOxi-.js +0 -1
  531. package/ui/dist/assets/skeleton-C9oEzNlU.js +0 -1
  532. package/ui/dist/assets/switch-tNs0n-kz.js +0 -1
  533. package/ui/dist/assets/table-CXxtLicg.js +0 -1
  534. package/ui/dist/assets/textarea-BjiEeIWh.js +0 -1
  535. package/ui/dist/assets/tooltip-ZMwtfr1J.js +0 -1
package/dist/server.js CHANGED
@@ -6,15 +6,23 @@ import { createServer } from 'net';
6
6
  // Core imports - keep static only for truly essential boot utilities
7
7
  import { loadConfig } from './utils/config-loader.js';
8
8
  import { createContextualLogger } from './utils/logger.js';
9
+ import { writePidFile, removePidFile } from './utils/pid-file.js';
9
10
  const appLog = createContextualLogger('Server');
10
11
  import { validateEnvironment, getConfiguredIntegrations, getConfiguredAIProviders } from './utils/env-validator.js';
11
12
  import { initApiTokensTable, tokenAuthMiddleware } from './auth/api-tokens.js';
12
13
  import { authMiddleware } from './auth/middleware.js';
13
14
  import { rateLimit } from './middleware/rate-limit.js';
14
15
  import { CreateTaskSchema } from './types/task.js';
15
- import { getMode, getModeLabel, hasCapability } from './core/deployment.js';
16
- import { registerRouteModules } from './server/route-loader.js';
16
+ import { getMode, getModeLabel, hasCapability, getCapabilities } from './core/deployment.js';
17
+ import { getRouteDefinitionsForMode, registerRouteModules } from './server/route-loader.js';
18
+ import { validateBody, GatewayExecuteSchema } from './middleware/request-validator.js';
17
19
  const VERSION = '2.0.0';
20
+ // Module-level startup state flags
21
+ let degradedMode = false;
22
+ let inMemoryStorageReminderInterval = null;
23
+ // Runtime tracking state
24
+ let runningPort = 0;
25
+ let lastErrorMessage;
18
26
  let cachedSettings = null;
19
27
  function getAppSettings() {
20
28
  if (!cachedSettings) {
@@ -87,6 +95,13 @@ app.use('*', cors({
87
95
  // Authentication middleware — applied to all /api/* routes
88
96
  // Skips public routes (auth, setup, webhooks, messaging channels)
89
97
  app.use('/api/*', authMiddleware());
98
+ // WS-1.4: In-memory storage warning header — set on every response when storage is ephemeral
99
+ app.use('*', async (c, next) => {
100
+ await next();
101
+ if (degradedMode) {
102
+ c.res.headers.set('X-ProfClaw-Storage', 'ephemeral');
103
+ }
104
+ });
90
105
  // HTTP rate limiting (sliding window, per-IP)
91
106
  const apiRateLimiter = rateLimit({
92
107
  maxRequests: parseInt(process.env['API_RATE_LIMIT'] ?? '100', 10),
@@ -100,131 +115,332 @@ app.use('/api/*', apiRateLimiter);
100
115
  app.use('/auth/*', authRateLimiter);
101
116
  // === Core Routes ===
102
117
  // Health check
118
+ const serverStartTime = Date.now();
119
+ async function checkDatabaseHealth() {
120
+ const start = Date.now();
121
+ try {
122
+ const { getDb } = await import('./storage/index.js');
123
+ const db = getDb();
124
+ if (!db)
125
+ return { status: 'error', message: 'Database not initialised' };
126
+ // Simple liveness query
127
+ await db.run('SELECT 1');
128
+ return { status: 'ok', latencyMs: Date.now() - start };
129
+ }
130
+ catch (err) {
131
+ return {
132
+ status: 'error',
133
+ latencyMs: Date.now() - start,
134
+ message: err instanceof Error ? err.message : 'Database check failed',
135
+ };
136
+ }
137
+ }
138
+ async function checkQueueHealth() {
139
+ try {
140
+ const { getQueueType } = await import('./queue/index.js');
141
+ const queueType = getQueueType();
142
+ if (!queueType)
143
+ return { status: 'warning', message: 'Queue not initialised' };
144
+ if (queueType === 'redis') {
145
+ // Test Redis reachability with a fresh connection
146
+ const redisUrl = process.env.REDIS_URL || 'redis://localhost:6379';
147
+ const start = Date.now();
148
+ try {
149
+ const IORedis = (await import('ioredis')).default;
150
+ const probe = new IORedis(redisUrl, { lazyConnect: true, connectTimeout: 2000, maxRetriesPerRequest: 0 });
151
+ await probe.connect();
152
+ await probe.ping();
153
+ await probe.disconnect();
154
+ return { status: 'ok', type: 'redis', latencyMs: Date.now() - start };
155
+ }
156
+ catch {
157
+ return { status: 'error', type: 'redis', latencyMs: Date.now() - start, message: 'Redis unreachable' };
158
+ }
159
+ }
160
+ return { status: 'ok', type: queueType };
161
+ }
162
+ catch {
163
+ return { status: 'ok', type: 'in-memory' };
164
+ }
165
+ }
166
+ async function checkOllamaHealth() {
167
+ const ollamaUrl = process.env.OLLAMA_BASE_URL || 'http://localhost:11434';
168
+ const start = Date.now();
169
+ try {
170
+ const res = await fetch(`${ollamaUrl}/api/tags`, {
171
+ signal: AbortSignal.timeout(3000),
172
+ });
173
+ if (!res.ok)
174
+ return { status: 'error', latencyMs: Date.now() - start, message: `HTTP ${res.status}` };
175
+ const data = await res.json();
176
+ const modelCount = Array.isArray(data.models) ? data.models.length : 0;
177
+ return { status: 'ok', latencyMs: Date.now() - start, models: modelCount };
178
+ }
179
+ catch {
180
+ // Ollama is optional — not reachable means degraded, not unhealthy
181
+ return { status: 'warning', latencyMs: Date.now() - start, message: 'Ollama not reachable' };
182
+ }
183
+ }
184
+ async function checkDiskHealth() {
185
+ try {
186
+ const { statfs } = await import('node:fs/promises');
187
+ const stats = await statfs(process.cwd());
188
+ const freeBytes = stats.bfree * stats.bsize;
189
+ const freeGb = freeBytes / (1024 ** 3);
190
+ const WARN_THRESHOLD_BYTES = 100 * 1024 * 1024; // 100 MB
191
+ const status = freeBytes < WARN_THRESHOLD_BYTES ? 'warning' : 'ok';
192
+ return {
193
+ status,
194
+ freeGb: Math.round(freeGb * 100) / 100,
195
+ ...(status === 'warning' && { message: 'Low disk space (< 100 MB free)' }),
196
+ };
197
+ }
198
+ catch {
199
+ return { status: 'warning', message: 'Disk check unavailable' };
200
+ }
201
+ }
202
+ function checkMemoryHealth() {
203
+ const WARN_THRESHOLD = 90;
204
+ const mode = getMode();
205
+ const mem = process.memoryUsage();
206
+ const rssMb = Math.round(mem.rss / 1024 / 1024);
207
+ // Mode-specific RSS caps (absolute memory usage is more actionable than heap ratio)
208
+ const RSS_CAP_MB = { pico: 128, mini: 256, pro: 512 };
209
+ const capMb = RSS_CAP_MB[mode] ?? 512;
210
+ // Use RSS vs mode cap — heap ratio is misleading at small heap sizes
211
+ const usedPercent = Math.round((rssMb / capMb) * 100);
212
+ const status = usedPercent > WARN_THRESHOLD ? 'warning' : 'ok';
213
+ return {
214
+ status,
215
+ usedPercent,
216
+ ...(status === 'warning' && { message: `RSS ${rssMb}MB exceeds ${WARN_THRESHOLD}% of ${capMb}MB cap (${mode} mode)` }),
217
+ };
218
+ }
103
219
  app.get('/health', async (c) => {
104
- const { getHealthStatus } = await import('./cron/index.js');
105
- const { getQueueType } = await import('./queue/index.js');
106
- const agentHealth = getHealthStatus();
107
- return c.json({
108
- status: 'ok',
109
- service: 'profclaw',
220
+ const [database, queue, ollama, disk] = await Promise.all([
221
+ checkDatabaseHealth(),
222
+ checkQueueHealth(),
223
+ checkOllamaHealth(),
224
+ checkDiskHealth(),
225
+ ]);
226
+ const memory = checkMemoryHealth();
227
+ const services = { database, queue, ollama, disk, memory };
228
+ const statuses = Object.values(services).map((s) => s.status);
229
+ const overallStatus = statuses.some((s) => s === 'error')
230
+ ? 'unhealthy'
231
+ : statuses.some((s) => s === 'warning')
232
+ ? 'degraded'
233
+ : 'ok';
234
+ // Gather queue depth
235
+ let queueDepth = { pending: 0, running: 0, total: 0 };
236
+ try {
237
+ const { getTasks } = await import('./queue/index.js');
238
+ const pending = getTasks({ status: 'pending' }).length + getTasks({ status: 'queued' }).length;
239
+ const running = getTasks({ status: 'in_progress' }).length;
240
+ queueDepth = { pending, running, total: pending + running };
241
+ }
242
+ catch {
243
+ // Queue not yet initialised — use zeroes
244
+ }
245
+ // Gather active agent sessions
246
+ let activeSessions = 0;
247
+ try {
248
+ const { getAgentRegistry } = await import('./adapters/registry.js');
249
+ activeSessions = getAgentRegistry().getActiveAdapters().length;
250
+ }
251
+ catch {
252
+ // Registry not yet initialised
253
+ }
254
+ const memUsage = process.memoryUsage();
255
+ const { loadavg: osLoadavg } = await import('node:os');
256
+ const body = {
257
+ status: overallStatus,
258
+ services,
110
259
  version: VERSION,
111
- mode: getMode(),
112
- queue: getQueueType() || 'not_initialized',
113
- timestamp: new Date().toISOString(),
114
- agents: agentHealth,
115
- });
260
+ uptime: Math.floor((Date.now() - serverStartTime) / 1000),
261
+ sseClients: sseConnections.size,
262
+ activeSessions,
263
+ queueDepth,
264
+ process: {
265
+ pid: process.pid,
266
+ port: runningPort,
267
+ memoryMb: Math.round(memUsage.rss / 1024 / 1024),
268
+ cpuLoadAvg: process.platform !== 'win32' ? osLoadavg() : [0, 0, 0],
269
+ },
270
+ ...(lastErrorMessage !== undefined && { lastError: lastErrorMessage }),
271
+ };
272
+ const httpStatus = overallStatus === 'unhealthy' ? 503 : 200;
273
+ return c.json(body, httpStatus);
116
274
  });
117
- // API info
118
- app.get('/', (c) => {
275
+ // API info (mode-aware)
276
+ app.get('/', async (c) => {
277
+ // WS-1.3: First-run setup redirect — send to /setup when no admin users exist
278
+ try {
279
+ const [{ getDb }, { users }, { eq }] = await Promise.all([
280
+ import('./storage/index.js'),
281
+ import('./storage/schema.js'),
282
+ import('drizzle-orm'),
283
+ ]);
284
+ const db = getDb();
285
+ if (db) {
286
+ const adminCount = await db
287
+ .select({ id: users.id })
288
+ .from(users)
289
+ .where(eq(users.role, 'admin'))
290
+ .limit(1);
291
+ if (adminCount.length === 0) {
292
+ return c.redirect('/setup', 302);
293
+ }
294
+ }
295
+ }
296
+ catch {
297
+ // DB may not be ready; fall through to normal response
298
+ }
299
+ // If UI is built and web_ui capability is enabled, serve the dashboard
300
+ if (hasCapability('web_ui')) {
301
+ const { existsSync } = await import('node:fs');
302
+ const { join } = await import('node:path');
303
+ const { readFile } = await import('node:fs/promises');
304
+ const indexPath = join(process.cwd(), 'ui', 'dist', 'index.html');
305
+ if (existsSync(indexPath)) {
306
+ const html = await readFile(indexPath, 'utf-8');
307
+ return c.html(html);
308
+ }
309
+ }
310
+ // Fallback: API info JSON
311
+ const mode = getMode();
312
+ const routes = getRouteDefinitionsForMode(mode);
313
+ const routeIds = new Set(routes.map((r) => r.id));
314
+ // Build endpoints dynamically based on available routes
315
+ const endpoints = {
316
+ health: 'GET /health',
317
+ };
318
+ // Core routes (always available)
319
+ if (routeIds.has('tasks')) {
320
+ endpoints.tasks = {
321
+ list: 'GET /api/tasks',
322
+ create: 'POST /api/tasks',
323
+ get: 'GET /api/tasks/:id',
324
+ cancel: 'POST /api/tasks/:id/cancel',
325
+ retry: 'POST /api/tasks/:id/retry',
326
+ };
327
+ }
328
+ if (routeIds.has('agents'))
329
+ endpoints.agents = 'GET /api/agents';
330
+ if (routeIds.has('chat')) {
331
+ endpoints.chat = {
332
+ models: 'GET /api/chat/models',
333
+ providers: 'GET /api/chat/providers',
334
+ send: 'POST /api/chat/send',
335
+ };
336
+ }
337
+ if (routeIds.has('gateway')) {
338
+ endpoints.gateway = {
339
+ execute: 'POST /api/gateway/execute',
340
+ agents: 'GET /api/gateway/agents',
341
+ workflows: 'GET /api/gateway/workflows',
342
+ };
343
+ }
344
+ if (routeIds.has('hooks')) {
345
+ endpoints.hooks = {
346
+ toolUse: 'POST /api/hook/tool-use',
347
+ sessionEnd: 'POST /api/hook/session-end',
348
+ sessions: 'GET /api/hook/sessions',
349
+ };
350
+ }
351
+ if (routeIds.has('tools')) {
352
+ endpoints.tools = {
353
+ list: 'GET /api/tools',
354
+ execute: 'POST /api/tools/execute',
355
+ security: 'GET /api/tools/security',
356
+ };
357
+ }
358
+ if (routeIds.has('settings')) {
359
+ endpoints.settings = {
360
+ get: 'GET /api/settings',
361
+ update: 'PATCH /api/settings',
362
+ };
363
+ }
364
+ if (routeIds.has('auth'))
365
+ endpoints.auth = 'POST /auth/login';
366
+ // Dashboard/web UI routes
367
+ if (routeIds.has('costs'))
368
+ endpoints.costs = 'GET /api/costs/summary';
369
+ if (routeIds.has('memory'))
370
+ endpoints.memory = 'GET /api/memory';
371
+ if (routeIds.has('skills'))
372
+ endpoints.skills = 'GET /api/skills';
373
+ if (routeIds.has('mcp')) {
374
+ endpoints.mcp = {
375
+ status: 'GET /api/mcp',
376
+ tools: 'GET /api/mcp/tools',
377
+ };
378
+ }
379
+ if (routeIds.has('tunnels'))
380
+ endpoints.tunnels = 'GET /api/tunnels';
381
+ if (routeIds.has('teams')) {
382
+ endpoints.teams = {
383
+ list: 'GET /api/teams?userId=<id>',
384
+ create: 'POST /api/teams',
385
+ members: 'GET /api/teams/:id/members',
386
+ usage: 'GET /api/teams/:id/usage',
387
+ invites: 'POST /api/teams/:id/invites',
388
+ };
389
+ }
390
+ // Web UI data routes
391
+ if (routeIds.has('dlq'))
392
+ endpoints.deadLetterQueue = 'GET /api/dlq';
393
+ if (routeIds.has('summaries')) {
394
+ endpoints.summaries = {
395
+ list: 'GET /api/summaries',
396
+ search: 'GET /api/summaries/search',
397
+ };
398
+ }
399
+ if (routeIds.has('search')) {
400
+ endpoints.search = {
401
+ semantic: 'GET /api/search/semantic?q={query}',
402
+ text: 'GET /api/search/text?q={query}',
403
+ };
404
+ }
405
+ if (routeIds.has('stats'))
406
+ endpoints.stats = 'GET /api/stats';
407
+ // Integration routes
408
+ if (routeIds.has('tickets')) {
409
+ endpoints.tickets = {
410
+ list: 'GET /api/tickets',
411
+ create: 'POST /api/tickets',
412
+ get: 'GET /api/tickets/:id',
413
+ };
414
+ }
415
+ if (routeIds.has('projects')) {
416
+ endpoints.projects = {
417
+ list: 'GET /api/projects',
418
+ create: 'POST /api/projects',
419
+ get: 'GET /api/projects/:id',
420
+ };
421
+ }
422
+ if (routeIds.has('webhooks')) {
423
+ endpoints.webhooks = {
424
+ github: 'POST /webhooks/github',
425
+ jira: 'POST /webhooks/jira',
426
+ linear: 'POST /webhooks/linear',
427
+ };
428
+ }
429
+ // Cron routes
430
+ if (routeIds.has('cron'))
431
+ endpoints.cron = 'GET /api/cron';
432
+ // Channel routes
433
+ if (routeIds.has('channels'))
434
+ endpoints.channels = 'GET /api/channels';
435
+ // Sync routes
436
+ if (routeIds.has('sync'))
437
+ endpoints.sync = 'GET /api/sync';
119
438
  return c.json({
120
439
  name: 'profClaw',
121
440
  version: VERSION,
122
- description: 'AI Agent Task Orchestration - Autonomous Mode',
123
- docs: '/docs',
124
- endpoints: {
125
- health: 'GET /health',
126
- tasks: 'GET /api/tasks',
127
- createTask: 'POST /api/tasks',
128
- getTask: 'GET /api/tasks/:id',
129
- cancelTask: 'POST /api/tasks/:id/cancel',
130
- retryTask: 'POST /api/tasks/:id/retry',
131
- agents: 'GET /api/agents',
132
- deadLetterQueue: 'GET /api/dlq',
133
- retryDlqTask: 'POST /api/dlq/:id/retry',
134
- webhooks: {
135
- github: 'POST /webhooks/github',
136
- jira: 'POST /webhooks/jira',
137
- linear: 'POST /webhooks/linear',
138
- },
139
- hooks: {
140
- toolUse: 'POST /api/hook/tool-use',
141
- sessionEnd: 'POST /api/hook/session-end',
142
- promptSubmit: 'POST /api/hook/prompt-submit',
143
- sessions: 'GET /api/hook/sessions',
144
- sessionEvents: 'GET /api/hook/sessions/:sessionId/events',
145
- sessionSummary: 'GET /api/hook/sessions/:sessionId/summary',
146
- },
147
- agentWebhooks: {
148
- openclaw: 'POST /api/hook/webhook/openclaw',
149
- generic: 'POST /api/hook/webhook/agent',
150
- reports: 'GET /api/hook/webhook/reports',
151
- taskReports: 'GET /api/hook/webhook/tasks/:taskId/reports',
152
- },
153
- costs: {
154
- summary: 'GET /api/costs/summary',
155
- budget: 'GET /api/costs/budget',
156
- },
157
- summaries: {
158
- list: 'GET /api/summaries',
159
- create: 'POST /api/summaries',
160
- get: 'GET /api/summaries/:id',
161
- delete: 'DELETE /api/summaries/:id',
162
- search: 'GET /api/summaries/search',
163
- stats: 'GET /api/summaries/stats',
164
- recent: 'GET /api/summaries/recent',
165
- byTask: 'GET /api/tasks/:taskId/summaries',
166
- },
167
- settings: {
168
- get: 'GET /api/settings',
169
- update: 'PATCH /api/settings',
170
- reset: 'POST /api/settings/reset',
171
- },
172
- gateway: {
173
- execute: 'POST /api/gateway/execute',
174
- agents: 'GET /api/gateway/agents',
175
- workflows: 'GET /api/gateway/workflows',
176
- workflow: 'GET /api/gateway/workflows/:type',
177
- config: 'GET /api/gateway/config',
178
- updateConfig: 'PATCH /api/gateway/config',
179
- },
180
- search: {
181
- semantic: 'GET /api/search/semantic?q={query}',
182
- text: 'GET /api/search/text?q={query}',
183
- capabilities: 'GET /api/search/capabilities',
184
- },
185
- tickets: {
186
- list: 'GET /api/tickets',
187
- create: 'POST /api/tickets',
188
- get: 'GET /api/tickets/:id',
189
- update: 'PATCH /api/tickets/:id',
190
- delete: 'DELETE /api/tickets/:id',
191
- transition: 'POST /api/tickets/:id/transition',
192
- assignAgent: 'POST /api/tickets/:id/assign-agent',
193
- comments: 'GET /api/tickets/:id/comments',
194
- addComment: 'POST /api/tickets/:id/comments',
195
- links: 'GET /api/tickets/:id/links',
196
- addLink: 'POST /api/tickets/:id/links',
197
- history: 'GET /api/tickets/:id/history',
198
- },
199
- projects: {
200
- list: 'GET /api/projects',
201
- create: 'POST /api/projects',
202
- get: 'GET /api/projects/:id',
203
- update: 'PATCH /api/projects/:id',
204
- archive: 'POST /api/projects/:id/archive',
205
- delete: 'DELETE /api/projects/:id',
206
- byKey: 'GET /api/projects/by-key/:key',
207
- default: 'GET /api/projects/default',
208
- migrate: 'POST /api/projects/migrate',
209
- externalLinks: 'GET /api/projects/:id/external-links',
210
- addExternalLink: 'POST /api/projects/:id/external-links',
211
- },
212
- sprints: {
213
- list: 'GET /api/projects/:projectId/sprints',
214
- create: 'POST /api/projects/:projectId/sprints',
215
- get: 'GET /api/projects/:projectId/sprints/:sprintId',
216
- update: 'PATCH /api/projects/:projectId/sprints/:sprintId',
217
- start: 'POST /api/projects/:projectId/sprints/:sprintId/start',
218
- complete: 'POST /api/projects/:projectId/sprints/:sprintId/complete',
219
- cancel: 'POST /api/projects/:projectId/sprints/:sprintId/cancel',
220
- delete: 'DELETE /api/projects/:projectId/sprints/:sprintId',
221
- active: 'GET /api/projects/:projectId/sprints/active',
222
- tickets: 'GET /api/projects/:projectId/sprints/:sprintId/tickets',
223
- addTickets: 'POST /api/projects/:projectId/sprints/:sprintId/tickets',
224
- removeTicket: 'DELETE /api/projects/:projectId/sprints/:sprintId/tickets/:ticketId',
225
- reorderTickets: 'PUT /api/projects/:projectId/sprints/:sprintId/tickets/reorder',
226
- },
227
- },
441
+ mode,
442
+ capabilities: getCapabilities(),
443
+ endpoints,
228
444
  });
229
445
  });
230
446
  // Task statistics
@@ -395,9 +611,10 @@ app.get('/api/events', (c) => {
395
611
  });
396
612
  });
397
613
  // === Protected Gateway Endpoint ===
398
- app.post('/api/gateway/execute-secure', tokenAuthMiddleware(['gateway:execute']), async (c) => {
614
+ app.post('/api/gateway/execute-secure', tokenAuthMiddleware(['gateway:execute']), validateBody(GatewayExecuteSchema), async (c) => {
399
615
  try {
400
- const body = await c.req.json();
616
+ // validateBody middleware stores the parsed result; cast via raw context map
617
+ const body = c.get('validatedBody');
401
618
  const { getGateway } = await import('./gateway/index.js');
402
619
  const gateway = getGateway();
403
620
  const request = {
@@ -436,6 +653,7 @@ app.post('/api/plugins/:id/toggle', async (c) => {
436
653
  return c.redirect(`/api/settings/plugins/${id}/toggle`, 307);
437
654
  });
438
655
  // === Server Startup ===
656
+ const MAX_PORT_ATTEMPTS = 3;
439
657
  function checkPortAvailable(port) {
440
658
  return new Promise((resolve) => {
441
659
  const server = createServer();
@@ -449,6 +667,16 @@ function checkPortAvailable(port) {
449
667
  server.listen(port, '127.0.0.1');
450
668
  });
451
669
  }
670
+ async function resolvePort(startPort) {
671
+ for (let offset = 0; offset < MAX_PORT_ATTEMPTS; offset++) {
672
+ const candidate = startPort + offset;
673
+ const available = await checkPortAvailable(candidate);
674
+ if (available) {
675
+ return candidate;
676
+ }
677
+ }
678
+ return null;
679
+ }
452
680
  async function main() {
453
681
  // Only start IF we are the main module actually being executed
454
682
  const isMain = process.argv[1]?.endsWith('server.ts') || process.argv[1]?.endsWith('server.js');
@@ -457,15 +685,18 @@ async function main() {
457
685
  }
458
686
  const mode = getMode();
459
687
  const appSettings = getAppSettings();
460
- const PORT = getPort();
688
+ const configuredPort = getPort();
461
689
  const ENABLE_CRON = isCronEnabled();
462
690
  appLog.info('profClaw starting', { version: VERSION, mode: getModeLabel() });
463
- // Early port conflict detection
464
- const portAvailable = await checkPortAvailable(PORT);
465
- if (!portAvailable) {
466
- appLog.error(`Port ${PORT} is already in use`, new Error(`EADDRINUSE: Port ${PORT}`));
691
+ // Bind-or-retry: find an available port
692
+ const PORT = await resolvePort(configuredPort);
693
+ if (PORT === null) {
694
+ appLog.error(`All ports ${configuredPort}–${configuredPort + MAX_PORT_ATTEMPTS - 1} are in use`, new Error(`EADDRINUSE: ports ${configuredPort}-${configuredPort + MAX_PORT_ATTEMPTS - 1}`));
467
695
  process.exit(1);
468
696
  }
697
+ if (PORT !== configuredPort) {
698
+ appLog.warn(`Port ${configuredPort} in use, started on port ${PORT}`);
699
+ }
469
700
  // Validate environment variables (fail fast if critical vars missing)
470
701
  const envResult = validateEnvironment({
471
702
  exitOnError: process.env.NODE_ENV === 'production',
@@ -545,6 +776,15 @@ async function main() {
545
776
  appLog.error('Failed to initialize task queue', error instanceof Error ? error : new Error(String(error)));
546
777
  appLog.warn('Tasks will not be processed');
547
778
  }
779
+ // Register SSE broadcaster for agent summary updates
780
+ try {
781
+ const { getAgentSummaryTracker } = await import('./agents/agent-summary.js');
782
+ getAgentSummaryTracker().registerSSEBroadcaster(broadcastEvent);
783
+ appLog.info('Agent summary SSE broadcaster registered');
784
+ }
785
+ catch (error) {
786
+ appLog.error('Failed to register agent summary broadcaster', error instanceof Error ? error : new Error(String(error)));
787
+ }
548
788
  // Initialize sync engine (pro mode or if explicitly configured)
549
789
  if (hasCapability('sync_engine')) {
550
790
  try {
@@ -561,12 +801,10 @@ async function main() {
561
801
  appLog.warn('Sync engine initialization failed', { error: error instanceof Error ? error.message : String(error) });
562
802
  }
563
803
  }
564
- // Initialize cost tracking (skip in pico - loaded on first use)
565
- if (getMode() !== 'pico') {
566
- const { initTokenTracker } = await import('./costs/token-tracker.js');
567
- initTokenTracker();
568
- appLog.info('Token tracker initialized');
569
- }
804
+ // Initialize cost tracking (all modes lightweight, needed for /api/costs)
805
+ const { initTokenTracker } = await import('./costs/token-tracker.js');
806
+ initTokenTracker();
807
+ appLog.info('Token tracker initialized');
570
808
  // Initialize agents from config
571
809
  const { getAgentRegistry } = await import('./adapters/registry.js');
572
810
  const registry = getAgentRegistry();
@@ -605,11 +843,13 @@ async function main() {
605
843
  }
606
844
  }
607
845
  }
608
- // Auto-discover agents if enabled (skip in pico mode to save startup time/memory)
609
- const autoDiscover = mode !== 'pico' && (process.env.AUTO_DISCOVER_AGENTS !== undefined
846
+ // Auto-discover agents if enabled
847
+ // Pico mode: only auto-discover Ollama (cheap fetch, essential for pico users)
848
+ // Mini/Pro: full auto-discovery (Claude Code CLI check, etc.)
849
+ const autoDiscover = mode === 'pico' ? true : (process.env.AUTO_DISCOVER_AGENTS !== undefined
610
850
  ? process.env.AUTO_DISCOVER_AGENTS === 'true'
611
851
  : (appSettings.agents?.autoDiscover ?? false));
612
- if (autoDiscover && !registry.getActiveAdapters().some(a => a.type === 'claude-code')) {
852
+ if (autoDiscover && mode !== 'pico' && !registry.getActiveAdapters().some(a => a.type === 'claude-code')) {
613
853
  try {
614
854
  const { execSync } = await import('child_process');
615
855
  execSync('which claude', { stdio: 'ignore' });
@@ -655,6 +895,32 @@ async function main() {
655
895
  // Ollama not running
656
896
  }
657
897
  }
898
+ // WS-1.2: Provider validation — warn (or exit in strict mode) when no AI adapters are configured
899
+ if (registry.getActiveAdapters().length === 0) {
900
+ appLog.warn('No AI providers configured. Run `profclaw setup` or set ANTHROPIC_API_KEY / OPENAI_API_KEY');
901
+ if (process.env.PROFCLAW_STRICT_MODE === 'true') {
902
+ appLog.error('Strict mode enabled: refusing to start without an AI provider');
903
+ process.exit(1);
904
+ }
905
+ degradedMode = true;
906
+ }
907
+ // WS-1.4: In-memory storage — warn on startup and remind every 60 seconds
908
+ {
909
+ const { isStorageInMemory } = await import('./storage/index.js');
910
+ if (isStorageInMemory()) {
911
+ degradedMode = true;
912
+ appLog.warn('╔══════════════════════════════════════════════════════════╗');
913
+ appLog.warn('║ WARNING: Running with in-memory storage (ephemeral) ║');
914
+ appLog.warn('║ All data will be lost when the server restarts. ║');
915
+ appLog.warn('║ Set STORAGE_TIER=local or DATABASE_URL to persist data. ║');
916
+ appLog.warn('╚══════════════════════════════════════════════════════════╝');
917
+ inMemoryStorageReminderInterval = setInterval(() => {
918
+ appLog.warn('Reminder: server is running with ephemeral in-memory storage — data will not survive a restart');
919
+ }, 60_000);
920
+ // Allow Node to exit cleanly without waiting for this interval
921
+ inMemoryStorageReminderInterval.unref();
922
+ }
923
+ }
658
924
  // Start cron jobs (if mode supports it and enabled)
659
925
  if (ENABLE_CRON && hasCapability('cron')) {
660
926
  const { startAllCronJobs } = await import('./cron/index.js');
@@ -714,6 +980,10 @@ async function main() {
714
980
  fetch: app.fetch,
715
981
  port: PORT,
716
982
  });
983
+ // Record runtime port for health endpoint
984
+ runningPort = PORT;
985
+ // Record PID so the CLI can detect/kill existing instances
986
+ writePidFile(PORT);
717
987
  appLog.info(`profClaw ready on http://localhost:${PORT}`, { port: PORT, mode: getModeLabel() });
718
988
  if (ENABLE_CRON) {
719
989
  const heartbeatMs = parseInt(process.env.POLL_INTERVAL_HEARTBEAT || '30000', 10);
@@ -784,7 +1054,12 @@ async function gracefulShutdown(signal) {
784
1054
  sseConnections.clear();
785
1055
  clearInterval(sseCleanupInterval);
786
1056
  appLog.info('SSE connections closed');
787
- // 2. Stop cron jobs
1057
+ // 2. Stop in-memory storage reminder interval
1058
+ if (inMemoryStorageReminderInterval !== null) {
1059
+ clearInterval(inMemoryStorageReminderInterval);
1060
+ inMemoryStorageReminderInterval = null;
1061
+ }
1062
+ // 3. Stop cron jobs
788
1063
  try {
789
1064
  const { stopAllCronJobs } = await import('./cron/index.js');
790
1065
  stopAllCronJobs();
@@ -793,7 +1068,7 @@ async function gracefulShutdown(signal) {
793
1068
  catch {
794
1069
  // Cron may not be initialized
795
1070
  }
796
- // 3. Drain task queue
1071
+ // 4. Drain task queue
797
1072
  try {
798
1073
  const { closeQueue } = await import('./queue/index.js');
799
1074
  await closeQueue();
@@ -802,7 +1077,7 @@ async function gracefulShutdown(signal) {
802
1077
  catch {
803
1078
  // Queue may not be initialized
804
1079
  }
805
- // 4. Destroy HTTP rate limiters
1080
+ // 5. Destroy HTTP rate limiters
806
1081
  try {
807
1082
  apiRateLimiter.destroy();
808
1083
  authRateLimiter.destroy();
@@ -811,6 +1086,8 @@ async function gracefulShutdown(signal) {
811
1086
  catch {
812
1087
  // Rate limiters may not be initialized
813
1088
  }
1089
+ // Remove PID file so new instances know the slot is free
1090
+ removePidFile();
814
1091
  appLog.info('Clean exit');
815
1092
  clearTimeout(forceExitTimer);
816
1093
  process.exit(0);
@@ -819,12 +1096,15 @@ process.on('SIGINT', () => { gracefulShutdown('SIGINT'); });
819
1096
  process.on('SIGTERM', () => { gracefulShutdown('SIGTERM'); });
820
1097
  // Catch unhandled errors to log before crashing
821
1098
  process.on('uncaughtException', (err) => {
1099
+ lastErrorMessage = err.message;
822
1100
  appLog.error('Uncaught exception', err);
823
1101
  // Let the process crash (daemon/serve will restart it)
824
1102
  process.exit(1);
825
1103
  });
826
1104
  process.on('unhandledRejection', (reason) => {
827
- appLog.error('Unhandled rejection', reason instanceof Error ? reason : new Error(String(reason)));
1105
+ const errMsg = reason instanceof Error ? reason.message : String(reason);
1106
+ lastErrorMessage = errMsg;
1107
+ appLog.error('Unhandled rejection', reason instanceof Error ? reason : new Error(errMsg));
828
1108
  // Don't exit for unhandled rejections - log and continue
829
1109
  // This prevents a single failed promise from taking down the server
830
1110
  });