@agenticmail/enterprise 0.5.319 → 0.5.321

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 (330) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/CODE_OF_CONDUCT.md +31 -0
  3. package/README.md +118 -38
  4. package/SECURITY.md +42 -0
  5. package/dist/agent-heartbeat-3FWNHZFX.js +510 -0
  6. package/dist/agent-heartbeat-4RWHZR7H.js +510 -0
  7. package/dist/agent-heartbeat-6ZGB5ILY.js +510 -0
  8. package/dist/agent-heartbeat-BIVHLKFM.js +510 -0
  9. package/dist/agent-heartbeat-HRKVFK2T.js +510 -0
  10. package/dist/agent-heartbeat-JC5GWVXD.js +510 -0
  11. package/dist/agent-heartbeat-K6A4HMHB.js +510 -0
  12. package/dist/agent-heartbeat-LCDXWFVB.js +510 -0
  13. package/dist/agent-heartbeat-P7HZCZAQ.js +510 -0
  14. package/dist/agent-heartbeat-PUIRSNIO.js +510 -0
  15. package/dist/agent-heartbeat-SN5ILQ6Y.js +510 -0
  16. package/dist/agent-heartbeat-TW5YTDYC.js +510 -0
  17. package/dist/agent-heartbeat-Z2QQXROL.js +510 -0
  18. package/dist/agent-notify-OEQBCZLN.js +43 -0
  19. package/dist/{agent-tools-263HM5QU.js → agent-tools-3W7XLUYA.js} +1 -1
  20. package/dist/agent-tools-4QK7LLNP.js +9 -0
  21. package/dist/agent-tools-54VZGT6L.js +9 -0
  22. package/dist/{agent-tools-AT4D276V.js → agent-tools-AYYDPO27.js} +7 -7
  23. package/dist/{agent-tools-MSTAPX2I.js → agent-tools-F2X47FKF.js} +7 -7
  24. package/dist/{agent-tools-FA26SY5O.js → agent-tools-O6W3QAZL.js} +11 -6
  25. package/dist/agent-tools-OAWVZBMW.js +9 -0
  26. package/dist/agent-tools-QCCU74PN.js +13949 -0
  27. package/dist/chunk-2LHUARN6.js +4929 -0
  28. package/dist/chunk-2WVCNCYC.js +5087 -0
  29. package/dist/{chunk-6PWDS7KY.js → chunk-3FM6YQUK.js} +20 -20
  30. package/dist/chunk-3UAFHUEC.js +212 -0
  31. package/dist/{chunk-WJO57PMO.js → chunk-46GOWZT4.js} +20 -20
  32. package/dist/{chunk-BNRE7TSX.js → chunk-5KYJAUZV.js} +3 -3
  33. package/dist/chunk-6C5PKREN.js +467 -0
  34. package/dist/{chunk-447MTPZF.js → chunk-6ZMLNEHB.js} +3 -3
  35. package/dist/chunk-BPZQT5N5.js +25652 -0
  36. package/dist/chunk-BQM7MBPS.js +1380 -0
  37. package/dist/{chunk-ZRFKGPIU.js → chunk-C52OQNNY.js} +20 -20
  38. package/dist/chunk-C7HGQF4Y.js +25652 -0
  39. package/dist/chunk-CAHNZGGK.js +25656 -0
  40. package/dist/{chunk-FL3CH3ET.js → chunk-CK7R6UHE.js} +51 -27
  41. package/dist/chunk-D36RPWB7.js +25652 -0
  42. package/dist/{chunk-36NM2B4C.js → chunk-DJK2UPFH.js} +63 -93
  43. package/dist/chunk-DM7FTF7W.js +4929 -0
  44. package/dist/chunk-DMD24UFZ.js +5101 -0
  45. package/dist/{chunk-36XNMIHA.js → chunk-DXZGPUAF.js} +20 -20
  46. package/dist/chunk-F46WB5IL.js +5087 -0
  47. package/dist/chunk-F5QG5SQH.js +5087 -0
  48. package/dist/{chunk-JGEVQZDR.js → chunk-FLQ5FLHW.js} +13 -16
  49. package/dist/chunk-H7GP733U.js +5087 -0
  50. package/dist/{chunk-OZSQLOV6.js → chunk-HHBXWB5U.js} +415 -19
  51. package/dist/{chunk-D24JY75H.js → chunk-IMXS4N6W.js} +3 -3
  52. package/dist/{chunk-6PVBV6ZP.js → chunk-JNMDD7JY.js} +3 -3
  53. package/dist/chunk-JTV5LA47.js +1519 -0
  54. package/dist/chunk-KV6G7NZX.js +1519 -0
  55. package/dist/chunk-MU5MEBIK.js +1519 -0
  56. package/dist/chunk-NLT5MC7X.js +465 -0
  57. package/dist/{chunk-GTFZZUXX.js → chunk-NVLYIM4J.js} +51 -27
  58. package/dist/{chunk-6G5SXLXC.js → chunk-NZY2BIZH.js} +63 -93
  59. package/dist/chunk-O42L6G67.js +1519 -0
  60. package/dist/chunk-OCNERGGM.js +4891 -0
  61. package/dist/chunk-OJSNHONE.js +1519 -0
  62. package/dist/{chunk-2TAZJWJN.js → chunk-OWL3QVH7.js} +18 -0
  63. package/dist/{chunk-P3HVY2HS.js → chunk-OWTLNV4Q.js} +382 -7
  64. package/dist/chunk-PCNYEP6T.js +4891 -0
  65. package/dist/{chunk-YL3Z5KPR.js → chunk-PI4AQ4Z6.js} +438 -15
  66. package/dist/chunk-PN3EGTCA.js +194 -0
  67. package/dist/chunk-Q37UKNRC.js +1519 -0
  68. package/dist/chunk-QXTC6J7H.js +5087 -0
  69. package/dist/{chunk-SPBQVNDI.js → chunk-RKERL5LZ.js} +25 -21
  70. package/dist/chunk-RVBK2IOX.js +25652 -0
  71. package/dist/chunk-SAKODCZ5.js +4891 -0
  72. package/dist/{chunk-XV4TU65E.js → chunk-SALGFC5L.js} +51 -27
  73. package/dist/chunk-STGWZ2MS.js +1519 -0
  74. package/dist/chunk-UY3ZVQDP.js +25652 -0
  75. package/dist/chunk-V6OSD62M.js +5087 -0
  76. package/dist/chunk-VP6YAHX4.js +1519 -0
  77. package/dist/chunk-WDYJOEAI.js +5087 -0
  78. package/dist/chunk-WEAFQNOS.js +195 -0
  79. package/dist/chunk-XKUSAZGP.js +5087 -0
  80. package/dist/chunk-Z6K5FKAB.js +548 -0
  81. package/dist/chunk-ZGE3XAXY.js +1519 -0
  82. package/dist/chunk-ZGYVXYQQ.js +3296 -0
  83. package/dist/cli-agent-7TB2BWS6.js +2370 -0
  84. package/dist/cli-agent-AKXFFST2.js +2370 -0
  85. package/dist/cli-agent-DZTKLITB.js +2357 -0
  86. package/dist/cli-agent-FOF7PFEP.js +2357 -0
  87. package/dist/cli-agent-H74M2ZYN.js +2357 -0
  88. package/dist/cli-agent-HORWVPHB.js +2370 -0
  89. package/dist/cli-agent-HSZT6SKF.js +2423 -0
  90. package/dist/cli-agent-JLUQ4ZU6.js +2424 -0
  91. package/dist/cli-agent-MVCDH4HV.js +2370 -0
  92. package/dist/cli-agent-NZXOEPJ2.js +2357 -0
  93. package/dist/cli-agent-PADN3QRC.js +2357 -0
  94. package/dist/cli-agent-QAYEX3BE.js +2441 -0
  95. package/dist/cli-agent-QT64DT5J.js +2370 -0
  96. package/dist/cli-agent-TFL2M6UK.js +2424 -0
  97. package/dist/cli-agent-UIKXATTD.js +2357 -0
  98. package/dist/cli-agent-UJN6FYTO.js +2370 -0
  99. package/dist/cli-agent-VIQAYVY4.js +2357 -0
  100. package/dist/cli-agent-WNWFVOFM.js +2370 -0
  101. package/dist/cli-agent-XBQX67VJ.js +2423 -0
  102. package/dist/cli-agent-ZLSC6FF4.js +2357 -0
  103. package/dist/cli-serve-2IL5DTEY.js +153 -0
  104. package/dist/cli-serve-47N5UKKW.js +153 -0
  105. package/dist/cli-serve-4XGZFUV2.js +140 -0
  106. package/dist/cli-serve-6OT3UEAN.js +140 -0
  107. package/dist/cli-serve-7L6EY5UH.js +153 -0
  108. package/dist/cli-serve-BDGOOOKQ.js +260 -0
  109. package/dist/cli-serve-BFNIW2LF.js +153 -0
  110. package/dist/cli-serve-C7MN6U5Q.js +153 -0
  111. package/dist/cli-serve-CR3OY3IM.js +153 -0
  112. package/dist/cli-serve-DAJFRWQ7.js +153 -0
  113. package/dist/cli-serve-FW6FHFW4.js +153 -0
  114. package/dist/cli-serve-GEEOQS77.js +153 -0
  115. package/dist/cli-serve-H562I3ZK.js +153 -0
  116. package/dist/cli-serve-HDQZF4C4.js +153 -0
  117. package/dist/cli-serve-LICAOMEB.js +140 -0
  118. package/dist/cli-serve-LLGYLWFS.js +153 -0
  119. package/dist/cli-serve-N3OISDNB.js +153 -0
  120. package/dist/cli-serve-TIZ27EVR.js +153 -0
  121. package/dist/cli-serve-TUNI2RCN.js +153 -0
  122. package/dist/cli-serve-WNOZMAWD.js +153 -0
  123. package/dist/cli-validate-Z726VJCN.js +150 -0
  124. package/dist/cli.js +4 -4
  125. package/dist/connection-manager-KAWEUWUR.js +9 -0
  126. package/dist/dashboard/app.js +9 -3
  127. package/dist/dashboard/components/knowledge-link.js +15 -0
  128. package/dist/dashboard/components/settings-help.js +4 -2
  129. package/dist/dashboard/docs/agent-deployment.html +33 -1
  130. package/dist/dashboard/docs/settings-network.html +321 -0
  131. package/dist/dashboard/docs/settings-security.html +347 -0
  132. package/dist/dashboard/docs/settings-tool-security.html +176 -0
  133. package/dist/dashboard/docs/settings.html +36 -16
  134. package/dist/dashboard/pages/agent-detail/deployment.js +39 -6
  135. package/dist/dashboard/pages/agent-detail/tools.js +10 -0
  136. package/dist/dashboard/pages/database-access.js +4 -3
  137. package/dist/dashboard/pages/settings.js +174 -37
  138. package/dist/dashboard/pages/task-pipeline.js +400 -843
  139. package/dist/db-adapter-2T56ORSD.js +7 -0
  140. package/dist/db-adapter-IRHOUMVC.js +7 -0
  141. package/dist/index.js +41 -41
  142. package/dist/microsoft-VREAZ7M2.js +3955 -0
  143. package/dist/routes-3MMLQTB6.js +90 -0
  144. package/dist/routes-4ZUIJ4HE.js +90 -0
  145. package/dist/routes-5MXHKKH4.js +90 -0
  146. package/dist/routes-64NJFK3B.js +90 -0
  147. package/dist/routes-6AKQ2LBV.js +90 -0
  148. package/dist/routes-CRRBUDO4.js +90 -0
  149. package/dist/routes-DIAF3MC3.js +90 -0
  150. package/dist/routes-KMUNU6CY.js +90 -0
  151. package/dist/routes-LRRLXIZR.js +90 -0
  152. package/dist/routes-N647AJYG.js +90 -0
  153. package/dist/routes-SSSELAAR.js +90 -0
  154. package/dist/routes-STERVGKJ.js +90 -0
  155. package/dist/routes-ZEZZACZP.js +90 -0
  156. package/dist/runtime-5EQN4GFM.js +45 -0
  157. package/dist/runtime-5LP7PUD4.js +45 -0
  158. package/dist/runtime-6BULDBR3.js +45 -0
  159. package/dist/runtime-6YEENDN3.js +45 -0
  160. package/dist/runtime-7LQFRG3B.js +45 -0
  161. package/dist/runtime-AMXJU2MB.js +45 -0
  162. package/dist/runtime-D6WSE7FG.js +45 -0
  163. package/dist/runtime-EYVN7NFJ.js +45 -0
  164. package/dist/runtime-F6RPWQVW.js +45 -0
  165. package/dist/runtime-FYMJURFC.js +45 -0
  166. package/dist/runtime-JRNBL4O4.js +45 -0
  167. package/dist/runtime-OM2NIBMI.js +45 -0
  168. package/dist/runtime-QWPVD7CY.js +45 -0
  169. package/dist/runtime-YLIIPTE4.js +45 -0
  170. package/dist/runtime-YU6P22CG.js +45 -0
  171. package/dist/screen-unlock-4RPZBHOI.js +118 -0
  172. package/dist/server-AMCSXINC.js +28 -0
  173. package/dist/server-CU6LVQS4.js +28 -0
  174. package/dist/server-DFYGH2CV.js +28 -0
  175. package/dist/server-EELWOC3X.js +28 -0
  176. package/dist/server-EN5E2OWQ.js +28 -0
  177. package/dist/server-GW2HYJYI.js +28 -0
  178. package/dist/server-J25NCRWJ.js +28 -0
  179. package/dist/server-JDGNOTFV.js +28 -0
  180. package/dist/server-NE5HD5DJ.js +28 -0
  181. package/dist/server-NQOT7W77.js +28 -0
  182. package/dist/server-PWE5PQTR.js +28 -0
  183. package/dist/server-Q2Q32H2B.js +28 -0
  184. package/dist/server-Q77ME7TL.js +28 -0
  185. package/dist/server-WLLH4WST.js +28 -0
  186. package/dist/server-WTUJ2O3F.js +28 -0
  187. package/dist/server-X4CJTHHF.js +28 -0
  188. package/dist/server-XK3ILCJC.js +28 -0
  189. package/dist/server-ZRD3NDJE.js +28 -0
  190. package/dist/setup-44VBAO4J.js +20 -0
  191. package/dist/setup-4ONNQBWB.js +20 -0
  192. package/dist/setup-4OSBXSCL.js +20 -0
  193. package/dist/setup-4QFGRBLZ.js +20 -0
  194. package/dist/setup-6766SGAR.js +20 -0
  195. package/dist/setup-AYY24DKM.js +20 -0
  196. package/dist/setup-B34N4HPU.js +20 -0
  197. package/dist/setup-E2YLC2EY.js +20 -0
  198. package/dist/setup-ER6NXTY5.js +20 -0
  199. package/dist/setup-H2AGCBW5.js +20 -0
  200. package/dist/setup-ICOZRKCX.js +20 -0
  201. package/dist/setup-JFTJH7UF.js +20 -0
  202. package/dist/setup-PRFNI6YW.js +20 -0
  203. package/dist/setup-RAHBMYHE.js +20 -0
  204. package/dist/setup-TXPR5UQX.js +20 -0
  205. package/dist/setup-XCJMELVU.js +20 -0
  206. package/dist/setup-XIYEIFVK.js +20 -0
  207. package/dist/setup-Z4PZSHBI.js +20 -0
  208. package/dist/skills-FR7I5V7H.js +16 -0
  209. package/dist/skills-HCVBA6PK.js +16 -0
  210. package/dist/system-prompts-TM7OA32C.js +913 -0
  211. package/dist/task-queue-O7IVZYUO.js +9 -0
  212. package/dist/transport-encryption-2T7PIXKG.js +25 -0
  213. package/logs/cloudflared-error.log +61 -0
  214. package/logs/cloudflared-out.log +0 -0
  215. package/logs/enterprise-error.log +0 -0
  216. package/logs/enterprise-out.log +3 -0
  217. package/logs/fola-error.log +0 -0
  218. package/logs/fola-out.log +0 -0
  219. package/logs/john-error.log +8 -0
  220. package/logs/john-out.log +0 -0
  221. package/package.json +31 -3
  222. package/src/agent-tools/tool-resolver.ts +50 -61
  223. package/src/agent-tools/tools/enterprise-database.ts +5 -5
  224. package/src/agent-tools/tools/local/dependency-manager.ts +2 -2
  225. package/src/agent-tools/tools/microsoft/graph-api.ts +137 -26
  226. package/src/agent-tools/tools/microsoft/outlook-mail.ts +392 -100
  227. package/src/agent-tools/tools/microsoft/teams.ts +267 -48
  228. package/src/auth/routes.ts +4 -4
  229. package/src/cli-agent.ts +108 -8
  230. package/src/cli-serve.ts +140 -0
  231. package/src/dashboard/app.js +9 -3
  232. package/src/dashboard/components/knowledge-link.js +15 -0
  233. package/src/dashboard/components/settings-help.js +4 -2
  234. package/src/dashboard/docs/agent-deployment.html +33 -1
  235. package/src/dashboard/docs/settings-network.html +321 -0
  236. package/src/dashboard/docs/settings-security.html +347 -0
  237. package/src/dashboard/docs/settings-tool-security.html +176 -0
  238. package/src/dashboard/docs/settings.html +36 -16
  239. package/src/dashboard/pages/agent-detail/deployment.js +39 -6
  240. package/src/dashboard/pages/agent-detail/tools.js +10 -0
  241. package/src/dashboard/pages/database-access.js +4 -3
  242. package/src/dashboard/pages/settings.js +174 -37
  243. package/src/dashboard/pages/task-pipeline.js +400 -843
  244. package/src/database-access/agent-tools.ts +78 -63
  245. package/src/database-access/connection-manager.ts +13 -2
  246. package/src/database-access/routes.ts +13 -1
  247. package/src/db/adapter.ts +1 -0
  248. package/src/engine/agent-memory.ts +2 -1
  249. package/src/engine/agent-notify.ts +50 -0
  250. package/src/engine/agent-routes.ts +257 -4
  251. package/src/engine/db-adapter.ts +16 -0
  252. package/src/engine/lifecycle.ts +4 -0
  253. package/src/engine/routes.ts +4 -3
  254. package/src/engine/screen-unlock.ts +136 -0
  255. package/src/engine/skills/database-access.ts +78 -0
  256. package/src/engine/skills/index.ts +3 -2
  257. package/src/engine/skills.ts +2 -0
  258. package/src/engine/task-queue-routes.ts +18 -0
  259. package/src/engine/task-queue.ts +15 -2
  260. package/src/middleware/transport-encryption.ts +1 -4
  261. package/src/runtime/agent-loop.ts +4 -0
  262. package/src/runtime/index.ts +15 -6
  263. package/src/server.ts +14 -1
  264. package/src/system-prompts/google/index.ts +1 -2
  265. package/src/system-prompts/index.ts +1 -1
  266. package/src/system-prompts/microsoft/contacts.ts +34 -0
  267. package/src/system-prompts/microsoft/excel.ts +52 -0
  268. package/src/system-prompts/microsoft/index.ts +31 -0
  269. package/src/system-prompts/microsoft/onedrive.ts +41 -0
  270. package/src/system-prompts/microsoft/onenote.ts +36 -0
  271. package/src/system-prompts/microsoft/outlook-calendar.ts +37 -0
  272. package/src/system-prompts/microsoft/outlook-mail.ts +46 -0
  273. package/src/system-prompts/microsoft/planner.ts +37 -0
  274. package/src/system-prompts/microsoft/powerbi.ts +38 -0
  275. package/src/system-prompts/microsoft/powerpoint.ts +35 -0
  276. package/src/system-prompts/microsoft/sharepoint.ts +44 -0
  277. package/src/system-prompts/microsoft/teams.ts +49 -0
  278. package/src/system-prompts/microsoft/todo.ts +37 -0
  279. package/src/types/hono-env.ts +4 -0
  280. package/.github/CODEOWNERS +0 -23
  281. package/.github/workflows/publish-community-skills.yml +0 -121
  282. package/.github/workflows/validate-community-skills.yml +0 -172
  283. package/agriculture_southwest_nigeria_research.txt +0 -10
  284. package/boa_credit_cards_research.txt +0 -10
  285. package/customer_support_research_feb2026.txt +0 -10
  286. package/dist/agent-tools-LRA7PPXG.js +0 -13922
  287. package/dist/agent-tools-VAU5DOQB.js +0 -13910
  288. package/dist/agent-tools-VWV7OWXU.js +0 -13922
  289. package/dist/chunk-2Z7MWTCX.js +0 -4977
  290. package/dist/chunk-3T4XU3VV.js +0 -5010
  291. package/dist/chunk-445QM4NX.js +0 -5061
  292. package/dist/chunk-5TW3Y7DJ.js +0 -1519
  293. package/dist/chunk-6I7VY3LT.js +0 -5060
  294. package/dist/chunk-6W5EK3UP.js +0 -4977
  295. package/dist/chunk-AQMSHJQT.js +0 -5069
  296. package/dist/chunk-ASSQW7HX.js +0 -5051
  297. package/dist/chunk-CIN27FGC.js +0 -5037
  298. package/dist/chunk-CMXY3NUB.js +0 -4977
  299. package/dist/chunk-DRLMRUDP.js +0 -5052
  300. package/dist/chunk-EHI7Z446.js +0 -1519
  301. package/dist/chunk-FEAILFAQ.js +0 -1519
  302. package/dist/chunk-GA3PYBZL.js +0 -1519
  303. package/dist/chunk-GWX63G5J.js +0 -1519
  304. package/dist/chunk-HHMZ4UY6.js +0 -1519
  305. package/dist/chunk-HVQMNF7E.js +0 -4921
  306. package/dist/chunk-HXM7F3YN.js +0 -1519
  307. package/dist/chunk-K6NGOUXG.js +0 -5060
  308. package/dist/chunk-KPG5WINJ.js +0 -4977
  309. package/dist/chunk-LBCUBYDL.js +0 -1519
  310. package/dist/chunk-LIRQSWLR.js +0 -5014
  311. package/dist/chunk-LRCKO5KE.js +0 -1519
  312. package/dist/chunk-M7XL3DJD.js +0 -5069
  313. package/dist/chunk-MHJULEIQ.js +0 -1519
  314. package/dist/chunk-MJGGW6MC.js +0 -106
  315. package/dist/chunk-MMYBDHDB.js +0 -4921
  316. package/dist/chunk-MQT5FXKD.js +0 -1519
  317. package/dist/chunk-OIMPEQF5.js +0 -4977
  318. package/dist/chunk-OOU7JUYE.js +0 -542
  319. package/dist/chunk-OW4GLBHP.js +0 -1519
  320. package/dist/chunk-Q4K4MMLU.js +0 -4977
  321. package/dist/chunk-RUK4CRPF.js +0 -1519
  322. package/dist/chunk-T7H65XQY.js +0 -1519
  323. package/dist/chunk-TQVFWG57.js +0 -5064
  324. package/dist/chunk-UEPK3IMC.js +0 -1519
  325. package/dist/chunk-VUWTXJH6.js +0 -1519
  326. package/dist/chunk-WCPGGSAD.js +0 -1519
  327. package/dist/chunk-WO63NZOJ.js +0 -1519
  328. package/dist/chunk-YPJDRVUM.js +0 -5064
  329. package/dist/chunk-ZROMH5DL.js +0 -4921
  330. package/src/dashboard/docs/_template.txt +0 -92
package/src/cli-agent.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  * AGENTICMAIL_AGENT_ID — Agent UUID from the enterprise DB
11
11
  *
12
12
  * Optional env vars:
13
- * PORT — Health check HTTP port (default: 3000)
13
+ * PORT — Health check HTTP port (default: 4100)
14
14
  * AGENTICMAIL_MODEL — Override model (e.g. "anthropic/claude-sonnet-4-20250514")
15
15
  * AGENTICMAIL_THINKING — Thinking level (e.g. "low", "medium", "high")
16
16
  * ANTHROPIC_API_KEY — Anthropic API key
@@ -26,6 +26,30 @@ import { beforeSpawn } from './engine/task-queue-before-spawn.js';
26
26
  import { afterSpawn, markInProgress } from './engine/task-queue-after-spawn.js';
27
27
  import { TaskPoller } from './engine/task-poller.js';
28
28
 
29
+ // ─── Production Log Level Filter ─────────────────────────
30
+ // Set LOG_LEVEL=warn to suppress info/debug console.log noise.
31
+ // Levels: debug < info < warn < error (default: info)
32
+ const _LOG_LEVEL = (process.env.LOG_LEVEL || 'info').toLowerCase();
33
+ const _LOG_LEVELS: Record<string, number> = { debug: 0, info: 1, warn: 2, error: 3 };
34
+ const _LOG_THRESHOLD = _LOG_LEVELS[_LOG_LEVEL] ?? 1;
35
+ const _origLog = console.log.bind(console);
36
+ const _origWarn = console.warn.bind(console);
37
+ if (_LOG_THRESHOLD > 1) {
38
+ // Suppress console.log (info level) — keep warn and error
39
+ console.log = function(...args: any[]) {
40
+ // Always allow critical prefixes through even at warn level
41
+ const first = typeof args[0] === 'string' ? args[0] : '';
42
+ if (first.includes('[error]') || first.includes('[fatal]') || first.includes('ERROR') || first.includes('FATAL')) {
43
+ _origLog(...args);
44
+ }
45
+ // Suppress everything else
46
+ };
47
+ }
48
+ if (_LOG_THRESHOLD > 2) {
49
+ // Suppress console.warn too — only errors
50
+ console.warn = function() {};
51
+ }
52
+
29
53
  // ════════════════════════════════════════════════════════════
30
54
  // SYSTEM DEPENDENCY AUTO-INSTALLER
31
55
  // ════════════════════════════════════════════════════════════
@@ -375,7 +399,7 @@ export async function runAgent(_args: string[]) {
375
399
  const DATABASE_URL = process.env.DATABASE_URL;
376
400
  const JWT_SECRET = process.env.JWT_SECRET;
377
401
  const AGENT_ID = process.env.AGENTICMAIL_AGENT_ID;
378
- const PORT = parseInt(process.env.PORT || '3000', 10);
402
+ const PORT = parseInt(process.env.PORT || '4100', 10);
379
403
 
380
404
  if (!DATABASE_URL) { console.error('ERROR: DATABASE_URL is required'); process.exit(1); }
381
405
  if (!JWT_SECRET) { console.error('ERROR: JWT_SECRET is required'); process.exit(1); }
@@ -650,6 +674,9 @@ export async function runAgent(_args: string[]) {
650
674
  // ─── Real-Time Status Reporting ─────────────────────────
651
675
  // Report status to the enterprise server (separate process)
652
676
  const ENTERPRISE_URL = process.env.ENTERPRISE_URL || 'http://localhost:3100';
677
+
678
+ // Notify enterprise SSE subscribers when tasks change (standalone agent → enterprise webhook)
679
+ taskQueue.webhookUrl = `${ENTERPRISE_URL}/api/engine/task-pipeline/webhook`;
653
680
  const _reportStatus = (update: any) => {
654
681
  fetch(`${ENTERPRISE_URL}/api/engine/agent-status/${AGENT_ID}`, {
655
682
  method: 'POST',
@@ -739,7 +766,7 @@ export async function runAgent(_args: string[]) {
739
766
  const session = await runtime.spawnSession({
740
767
  agentId,
741
768
  message: `[System — Task Recovery] You have a stuck task to complete:\n\nTask: ${task.title}\nID: ${task.id}\nCategory: ${task.category}\nPriority: ${task.priority}\nDescription: ${task.description}\n\nPlease complete this task now.`,
742
- model: task.model || undefined,
769
+ model: (task.model || undefined) as any,
743
770
  });
744
771
  if (session?.id) {
745
772
  sessionRouter.register({
@@ -892,6 +919,79 @@ export async function runAgent(_args: string[]) {
892
919
 
893
920
  app.get('/ready', (c) => c.json({ ready: true, agentId: AGENT_ID }));
894
921
 
922
+ // General config reload endpoint — called by enterprise server when ANY config changes
923
+ // Supports: db-access, permissions, config, guardrails, budget, all
924
+ app.post('/reload-db-access', async (c) => c.redirect('/reload?scope=db-access', 307));
925
+ app.post('/reload', async (c) => {
926
+ const scope = c.req.query('scope') || 'all';
927
+ const reloaded: string[] = [];
928
+
929
+ try {
930
+ // 1. Database connections
931
+ if (scope === 'all' || scope === 'db-access') {
932
+ const dbManager = (runtime as any).config?.databaseManager;
933
+ if (dbManager && engineDb) {
934
+ await dbManager.setDb(engineDb);
935
+ reloaded.push('db-access');
936
+ }
937
+ }
938
+
939
+ // 2. Permission profiles
940
+ if (scope === 'all' || scope === 'permissions') {
941
+ try {
942
+ const { permissionEngine } = await import('./engine/routes.js');
943
+ await permissionEngine.setDb(engineDb);
944
+ reloaded.push('permissions');
945
+ } catch { /* non-fatal */ }
946
+ }
947
+
948
+ // 3. Agent config (re-read from managed_agents table)
949
+ if (scope === 'all' || scope === 'config') {
950
+ try {
951
+ const row = await engineDb.get<any>('SELECT * FROM managed_agents WHERE id = $1', [agentId]);
952
+ if (row) {
953
+ const config = typeof row.config === 'string' ? JSON.parse(row.config) : row.config;
954
+ const managed = routes.lifecycle.getAgent(agentId);
955
+ if (managed) {
956
+ Object.assign(managed.config, config);
957
+ managed.updatedAt = row.updated_at;
958
+ if (row.display_name) { managed.name = row.display_name; managed.displayName = row.display_name; }
959
+ reloaded.push('config');
960
+ }
961
+ }
962
+ } catch { /* non-fatal */ }
963
+ }
964
+
965
+ // 4. Budget config
966
+ if (scope === 'all' || scope === 'budget') {
967
+ try {
968
+ const row = await engineDb.get<any>('SELECT budget_config FROM managed_agents WHERE id = $1', [agentId]);
969
+ if (row?.budget_config) {
970
+ const managed = routes.lifecycle.getAgent(agentId);
971
+ if (managed) {
972
+ managed.budgetConfig = typeof row.budget_config === 'string' ? JSON.parse(row.budget_config) : row.budget_config;
973
+ reloaded.push('budget');
974
+ }
975
+ }
976
+ } catch { /* non-fatal */ }
977
+ }
978
+
979
+ // 5. Guardrails
980
+ if (scope === 'all' || scope === 'guardrails') {
981
+ try {
982
+ const { guardrails } = await import('./engine/routes.js');
983
+ await (guardrails as any).loadFromDb?.();
984
+ reloaded.push('guardrails');
985
+ } catch { /* non-fatal */ }
986
+ }
987
+
988
+ console.log(`[agent] Config reloaded: ${reloaded.join(', ') || 'nothing to reload'} (scope: ${scope})`);
989
+ return c.json({ ok: true, reloaded, scope });
990
+ } catch (e: any) {
991
+ return c.json({ ok: false, error: e.message, reloaded }, 500);
992
+ }
993
+ });
994
+
895
995
  // Mount runtime API if available
896
996
  if (runtimeApp) {
897
997
  app.route('/api/runtime', runtimeApp);
@@ -1105,7 +1205,7 @@ export async function runAgent(_args: string[]) {
1105
1205
 
1106
1206
  if (isMessagingSource) {
1107
1207
  // Build messaging-specific system prompt
1108
- const { buildScheduleInfo } = await import('./system-prompts/google/index.js');
1208
+ const { buildScheduleInfo } = await import('./system-prompts/index.js');
1109
1209
  const sendToolName = ctx.source === 'whatsapp' ? 'whatsapp_send'
1110
1210
  : 'telegram_send';
1111
1211
  const platformName = ctx.source === 'whatsapp' ? 'WhatsApp'
@@ -1140,7 +1240,7 @@ export async function runAgent(_args: string[]) {
1140
1240
  `- "${sendToolName}" is ALREADY LOADED — do NOT call request_tools, do NOT search for tools, do NOT use grep. Just call ${sendToolName} directly.`,
1141
1241
  `- NEVER use google_chat_send_message — this is ${platformName}.`,
1142
1242
  `- Keep messages concise and conversational — this is a chat, not an email.`,
1143
- `- No markdown formatting plain text only.`,
1243
+ `- ABSOLUTELY NO MARKDOWN. No **, no ##, no *, no bullet lists, no code blocks. Plain text ONLY. Write like a human texting — short paragraphs separated by line breaks. This is non-negotiable.`,
1144
1244
  `- For simple greetings/questions, reply in ONE tool call. Do not overthink.`,
1145
1245
  '',
1146
1246
  `DEPENDENCY & TOOL MANAGEMENT:`,
@@ -1172,7 +1272,7 @@ export async function runAgent(_args: string[]) {
1172
1272
  if (dp.sudoPassword) {
1173
1273
  lines.push(`- You HAVE ${elevatedLabel} access. The system password is pre-configured — install_dependency handles it automatically. You do NOT need to ask the user for it.`);
1174
1274
  } else {
1175
- lines.push(`- You HAVE ${elevatedLabel} access. ${os === 'win32' ? 'Elevated commands should work if the agent process is running as admin.' : 'No password set — works if NOPASSWD is configured or credentials are cached.'}`);
1275
+ lines.push(`- You HAVE ${elevatedLabel} access. ${process.platform === 'win32' ? 'Elevated commands should work if the agent process is running as admin.' : 'No password set — works if NOPASSWD is configured or credentials are cached.'}`);
1176
1276
  }
1177
1277
  } else {
1178
1278
  lines.push('- You do NOT have elevated/admin access. Commands requiring admin privileges (sudo on Mac/Linux, admin on Windows) will fail.');
@@ -1199,7 +1299,7 @@ export async function runAgent(_args: string[]) {
1199
1299
  ].filter(Boolean).join('\n');
1200
1300
 
1201
1301
  } else {
1202
- const { buildGoogleChatPrompt, buildScheduleInfo } = await import('./system-prompts/google/index.js');
1302
+ const { buildGoogleChatPrompt, buildScheduleInfo } = await import('./system-prompts/index.js');
1203
1303
  systemPrompt = buildGoogleChatPrompt({
1204
1304
  agent: { name: agentName, role: identity?.role || 'professional', personality: identity?.personality },
1205
1305
  schedule: buildScheduleInfo(agentSchedule, agentTimezone),
@@ -2143,7 +2243,7 @@ async function startCalendarPolling(
2143
2243
  const identity = config.identity || {};
2144
2244
 
2145
2245
  try {
2146
- const { buildMeetJoinPrompt, buildScheduleInfo } = await import('./system-prompts/google/index.js');
2246
+ const { buildMeetJoinPrompt, buildScheduleInfo } = await import('./system-prompts/index.js');
2147
2247
 
2148
2248
  const managerEmail = (config as any)?.manager?.email || '';
2149
2249
  const agentEmail = config?.identity?.email || (config as any)?.email || '';
package/src/cli-serve.ts CHANGED
@@ -138,6 +138,29 @@ export async function runServe(_args: string[]) {
138
138
  await server.start();
139
139
  console.log(`AgenticMail Enterprise server running on :${PORT}`);
140
140
 
141
+ // Start prevent-sleep if configured
142
+ try {
143
+ const { startPreventSleep } = await import('./engine/screen-unlock.js');
144
+ const adminDb = (server as any).getAdminDb?.() || (server as any).adminDb;
145
+ if (adminDb) {
146
+ const settings = await adminDb.getSettings?.().catch(() => null);
147
+ const screenAccess = settings?.securityConfig?.screenAccess;
148
+ if (screenAccess?.enabled && screenAccess?.preventSleep) {
149
+ startPreventSleep();
150
+ console.log('[startup] Prevent-sleep enabled — system will stay awake while agents are active');
151
+ }
152
+ }
153
+ } catch {}
154
+
155
+ // ─── Auto-configure system persistence ──────────────────
156
+ // Ensures the server auto-starts on reboot, crash-recovers with backoff,
157
+ // and log rotation is in place. Runs once — idempotent.
158
+ try {
159
+ await setupSystemPersistence();
160
+ } catch (e: any) {
161
+ console.warn('[startup] System persistence setup skipped: ' + e.message);
162
+ }
163
+
141
164
  // Auto-start cloudflared if tunnel token is present
142
165
  const tunnelToken = process.env.CLOUDFLARED_TOKEN;
143
166
  if (tunnelToken) {
@@ -172,3 +195,120 @@ export async function runServe(_args: string[]) {
172
195
  }
173
196
  }
174
197
  }
198
+
199
+ // ─── System Persistence ─────────────────────────────────
200
+ // Automatically sets up PM2 startup, log rotation, and process saving.
201
+ // Idempotent — safe to run on every boot.
202
+
203
+ async function setupSystemPersistence(): Promise<void> {
204
+ const { execSync, spawnSync } = await import('child_process');
205
+ const { existsSync: exists, writeFileSync, mkdirSync } = await import('fs');
206
+ const { join: pathJoin } = await import('path');
207
+ const platform = process.platform;
208
+
209
+ // Only works if running under PM2
210
+ if (!process.env.PM2_HOME && !process.env.pm_id) {
211
+ // Not running under PM2 — skip (user is running directly via node/npx)
212
+ return;
213
+ }
214
+
215
+ const markerDir = pathJoin(homedir(), '.agenticmail');
216
+ const markerFile = pathJoin(markerDir, '.persistence-configured');
217
+
218
+ // Check if already configured (avoid running pm2 commands every boot)
219
+ if (exists(markerFile)) {
220
+ // Already configured — just save current process list silently
221
+ try { execSync('pm2 save --silent', { timeout: 10000, stdio: 'ignore' }); } catch {}
222
+ return;
223
+ }
224
+
225
+ console.log('[startup] Configuring system persistence (one-time setup)...');
226
+
227
+ // 1. Set up PM2 startup — auto-start on boot
228
+ // pm2 startup outputs the command needed (may require sudo on Linux)
229
+ try {
230
+ if (platform === 'darwin') {
231
+ // macOS: launchd — pm2 startup creates plist and loads it
232
+ const result = spawnSync('pm2', ['startup', 'launchd', '--silent'], {
233
+ timeout: 15000, stdio: 'pipe', encoding: 'utf-8',
234
+ });
235
+ // If it outputs a sudo command, try running it
236
+ const output = (result.stdout || '') + (result.stderr || '');
237
+ const sudoMatch = output.match(/sudo\s+env\s+.*pm2\s+startup.*/);
238
+ if (sudoMatch) {
239
+ // Can't run sudo without password — log the command for the user
240
+ console.log('[startup] PM2 startup requires sudo. Run this once:');
241
+ console.log(' ' + sudoMatch[0]);
242
+ } else {
243
+ console.log('[startup] PM2 startup configured (launchd)');
244
+ }
245
+ // Try loading the plist if it exists
246
+ const plistPath = pathJoin(homedir(), 'Library', 'LaunchAgents', `pm2.${process.env.USER || 'user'}.plist`);
247
+ if (exists(plistPath)) {
248
+ try { execSync(`launchctl load -w "${plistPath}"`, { timeout: 5000, stdio: 'ignore' }); } catch {}
249
+ }
250
+ } else if (platform === 'linux') {
251
+ // Linux: systemd (Debian/Ubuntu/RHEL/Pi)
252
+ const result = spawnSync('pm2', ['startup', 'systemd', '--silent'], {
253
+ timeout: 15000, stdio: 'pipe', encoding: 'utf-8',
254
+ });
255
+ const output = (result.stdout || '') + (result.stderr || '');
256
+ const sudoMatch = output.match(/sudo\s+env\s+.*pm2\s+startup.*/);
257
+ if (sudoMatch) {
258
+ // Try running it — if we have sudo access
259
+ try {
260
+ execSync(sudoMatch[0], { timeout: 15000, stdio: 'ignore' });
261
+ console.log('[startup] PM2 startup configured (systemd)');
262
+ } catch {
263
+ console.log('[startup] PM2 startup requires root. Run this once:');
264
+ console.log(' ' + sudoMatch[0]);
265
+ }
266
+ } else {
267
+ console.log('[startup] PM2 startup configured (systemd)');
268
+ }
269
+ } else if (platform === 'win32') {
270
+ // Windows: use pm2-windows-startup
271
+ try {
272
+ execSync('npm list -g pm2-windows-startup', { timeout: 10000, stdio: 'ignore' });
273
+ } catch {
274
+ console.log('[startup] Installing pm2-windows-startup...');
275
+ try {
276
+ execSync('npm install -g pm2-windows-startup', { timeout: 60000, stdio: 'ignore' });
277
+ execSync('pm2-startup install', { timeout: 15000, stdio: 'ignore' });
278
+ console.log('[startup] PM2 startup configured (Windows Service)');
279
+ } catch (e: any) {
280
+ console.warn('[startup] Could not install pm2-windows-startup: ' + e.message);
281
+ }
282
+ }
283
+ }
284
+ } catch (e: any) {
285
+ console.warn('[startup] PM2 startup setup: ' + e.message);
286
+ }
287
+
288
+ // 2. Install log rotation (if not already present)
289
+ try {
290
+ const moduleList = execSync('pm2 ls --silent 2>/dev/null || true', { timeout: 10000, encoding: 'utf-8' });
291
+ if (!moduleList.includes('pm2-logrotate')) {
292
+ console.log('[startup] Installing pm2-logrotate...');
293
+ execSync('pm2 install pm2-logrotate --silent', { timeout: 60000, stdio: 'ignore' });
294
+ // Configure: 10MB max, keep 5 rotated files, compress
295
+ execSync('pm2 set pm2-logrotate:max_size 10M --silent', { timeout: 5000, stdio: 'ignore' });
296
+ execSync('pm2 set pm2-logrotate:retain 5 --silent', { timeout: 5000, stdio: 'ignore' });
297
+ execSync('pm2 set pm2-logrotate:compress true --silent', { timeout: 5000, stdio: 'ignore' });
298
+ console.log('[startup] Log rotation configured (10MB, 5 files)');
299
+ }
300
+ } catch {}
301
+
302
+ // 3. Save current process list for resurrection on reboot
303
+ try {
304
+ execSync('pm2 save --silent', { timeout: 10000, stdio: 'ignore' });
305
+ console.log('[startup] Process list saved');
306
+ } catch {}
307
+
308
+ // 4. Write marker file so we don't repeat this setup
309
+ try {
310
+ if (!exists(markerDir)) mkdirSync(markerDir, { recursive: true });
311
+ writeFileSync(markerFile, new Date().toISOString() + '\n' + `platform=${platform}\n`, { mode: 0o600 });
312
+ console.log('[startup] System persistence configured successfully');
313
+ } catch {}
314
+ }
@@ -156,7 +156,8 @@ function App() {
156
156
  const [selectedOrgId, setSelectedOrgId] = useState('');
157
157
  const [selectedOrg, setSelectedOrg] = useState(null);
158
158
  const [orgVersion, setOrgVersion] = useState(0);
159
- const onOrgChange = useCallback((id, org) => { setSelectedOrgId(id); setSelectedOrg(org); setOrgVersion(v => v + 1); }, []);
159
+ const [companyName, setCompanyName] = useState((window.__EM_BRANDING__ && window.__EM_BRANDING__.companyName) || '');
160
+ const onOrgChange = useCallback((id, org) => { setSelectedOrgId(id); setSelectedOrg(org); setOrgVersion(v => v + 1); if (org && org.name) setCompanyName(org.name); }, []);
160
161
 
161
162
  // Check if already authenticated via cookie on mount, and check setup state
162
163
  useEffect(() => {
@@ -189,6 +190,11 @@ function App() {
189
190
  if (window.__transportEncryption) await window.__transportEncryption.waitForReady();
190
191
  }
191
192
  } catch {}
193
+ // Fetch company name for sidebar
194
+ try {
195
+ var s = await apiCall('/settings');
196
+ if (s && s.name) setCompanyName(s.name);
197
+ } catch {}
192
198
  setAuthed(true);
193
199
  setAuthChecked(true);
194
200
  }).catch(() => setAuthChecked(true));
@@ -457,7 +463,7 @@ function App() {
457
463
  const PageComponent = canAccessPage ? (pages[page] || DashboardPage) : null;
458
464
  const sidebarClass = 'sidebar' + (sidebarPinned ? ' expanded' : sidebarHovered ? ' hover-expanded' : '') + (mobileMenuOpen ? ' mobile-open' : '');
459
465
 
460
- return h(AppContext.Provider, { value: { toast, toasts, user, theme, setPage, permissions, impersonating, startImpersonation, stopImpersonation, selectedOrgId, selectedOrg, onOrgChange } },
466
+ return h(AppContext.Provider, { value: { toast, toasts, user, theme, setPage, permissions, impersonating, startImpersonation, stopImpersonation, selectedOrgId, selectedOrg, onOrgChange, companyName, setCompanyName } },
461
467
  h('div', { className: 'app-layout' },
462
468
  // Mobile hamburger
463
469
  h('button', { className: 'mobile-hamburger', onClick: () => setMobileMenuOpen(true) },
@@ -473,7 +479,7 @@ function App() {
473
479
  h('div', { className: sidebarClass, onMouseEnter: onSidebarEnter, onMouseLeave: onSidebarLeave },
474
480
  h('div', { className: 'sidebar-brand' },
475
481
  h('img', { src: (window.__EM_BRANDING__ && window.__EM_BRANDING__.logo) || '/dashboard/assets/logo.png', alt: 'AgenticMail', style: { width: 28, height: 28, objectFit: 'contain' } }),
476
- h('div', { className: 'sidebar-brand-text' }, h('h2', null, (window.__EM_BRANDING__ && window.__EM_BRANDING__.companyName) || 'AgenticMail'), h('span', null, 'Enterprise')),
482
+ h('div', { className: 'sidebar-brand-text' }, h('h2', null, companyName || 'AgenticMail'), h('span', null, 'Enterprise')),
477
483
  h('button', { className: 'sidebar-toggle' + (sidebarPinned ? ' pinned' : ''), onClick: toggleSidebarPin, title: sidebarPinned ? 'Unpin sidebar' : 'Pin sidebar' }, sidebarPinned ? I.chevronLeft() : I.panelLeft())
478
484
  ),
479
485
  h('div', { className: 'sidebar-nav' },
@@ -62,3 +62,18 @@ export var AGENT_TAB_DOCS = {
62
62
  deployment: 'agent-deployment',
63
63
  'memory-transfer': 'memory-transfer',
64
64
  };
65
+
66
+ /** Map of settings tab IDs to doc filenames */
67
+ export var SETTINGS_TAB_DOCS = {
68
+ general: 'settings',
69
+ models: 'settings',
70
+ 'api-keys': 'settings',
71
+ authentication: 'settings',
72
+ platform: 'settings',
73
+ email: 'settings',
74
+ deployments: 'settings',
75
+ 'security-system': 'settings-security',
76
+ 'tool-security': 'settings-tool-security',
77
+ network: 'settings-network',
78
+ integrations: 'settings',
79
+ };
@@ -138,7 +138,8 @@ export var SETTINGS_HELP = {
138
138
  h('li', null, h('strong', null, 'Rate Limiting'), ' \u2014 Limits how many times each tool can be called per minute per agent. Prevents any single agent from overwhelming the system. Adjust limits per tool type in the table.'),
139
139
  h('li', null, h('strong', null, 'Circuit Breaker'), ' \u2014 Automatically pauses a tool that keeps failing (after 5 consecutive errors). Waits 30 seconds before retrying. Prevents error cascading when an external service is down.'),
140
140
  h('li', null, h('strong', null, 'Telemetry'), ' \u2014 Collects performance metrics: call duration, success rates, and output sizes. Useful for identifying slow tools or agents using resources inefficiently.')
141
- )
141
+ ),
142
+ h('p', { style: { marginTop: 12 } }, h('a', { href: '/docs/settings-tool-security', style: { fontSize: 12 } }, 'View full Tool Security documentation \u2192'))
142
143
  );
143
144
  }
144
145
  },
@@ -163,7 +164,8 @@ export var SETTINGS_HELP = {
163
164
  h('li', null, h('strong', null, 'Rate Limiting'), ' \u2014 Limits API requests per IP per minute. Protects against abuse and denial-of-service. "Skip Paths" excludes health-check endpoints.'),
164
165
  h('li', null, h('strong', null, 'HTTPS Enforcement'), ' \u2014 Forces all connections to use encrypted HTTPS. Highly recommended for production.'),
165
166
  h('li', null, h('strong', null, 'Security Headers'), ' \u2014 Browser security policies: HSTS forces HTTPS, X-Frame-Options prevents clickjacking, Content-Type-Options prevents MIME sniffing. The defaults are recommended for most deployments.')
166
- )
167
+ ),
168
+ h('p', { style: { marginTop: 12 } }, h('a', { href: '/docs/settings-network', style: { fontSize: 12 } }, 'View full Network & Firewall documentation \u2192'))
167
169
  );
168
170
  }
169
171
  },
@@ -162,12 +162,39 @@
162
162
  <h3 id="local">Local (In-Process)</h3>
163
163
  <p>For development and single-server deployments:</p>
164
164
  <ul>
165
- <li><strong>Port</strong> — HTTP port the agent listens on.</li>
165
+ <li><strong>Port</strong> — HTTP port the agent listens on. Validated in real-time (see below).</li>
166
166
  <li><strong>Host</strong> — Hostname or IP (default: localhost).</li>
167
167
  <li><strong>Process Manager</strong> — PM2 (with install detection), systemd, Manual, or In-Process (embedded).</li>
168
168
  <li><strong>Process Name</strong> — PM2/systemd service name for lifecycle management.</li>
169
169
  <li><strong>Working Directory</strong> — Auto-detected or manually specified.</li>
170
170
  </ul>
171
+
172
+ <h4>Port Validation</h4>
173
+ <p>When you enter a port number, the system automatically checks whether it's available before allowing you to save:</p>
174
+ <div class="card">
175
+ <table>
176
+ <thead><tr><th>Indicator</th><th>Meaning</th></tr></thead>
177
+ <tbody>
178
+ <tr><td><strong style="color:var(--success)">Green border + checkmark</strong></td><td>Port is available — you can save and deploy.</td></tr>
179
+ <tr><td><strong style="color:var(--danger)">Red border + error</strong></td><td>Port is already in use — save is blocked. The error shows which process is using it (e.g., "Port 3100 is in use by node (PID 12345)").</td></tr>
180
+ </tbody>
181
+ </table>
182
+ </div>
183
+ <ul>
184
+ <li><strong>Debounced</strong> — Validation triggers 500ms after you stop typing, preventing excessive API calls.</li>
185
+ <li><strong>Bind test</strong> — Uses <code>net.createServer()</code> to attempt binding the port. This is the most reliable availability check.</li>
186
+ <li><strong>Process identification</strong> — If the port is in use, the system runs <code>lsof</code> (macOS/Linux) or <code>netstat</code> (Windows) to identify the process name and PID.</li>
187
+ <li><strong>Save blocked</strong> — You cannot save deployment configuration while a port conflict exists.</li>
188
+ </ul>
189
+
190
+ <div class="card">
191
+ <h3>API Reference</h3>
192
+ <pre><code>POST /system/check-port
193
+ Body: { "port": 4100 }
194
+ Response (available): { "available": true, "port": 3102 }
195
+ Response (in use): { "available": false, "port": 3102, "process": "node (PID 12345)" }</code></pre>
196
+ </div>
197
+
171
198
  <div class="tip">
172
199
  <strong>Tip:</strong> PM2 availability is auto-detected. If PM2 isn't installed, the dashboard offers a one-click "Install PM2" button.
173
200
  </div>
@@ -268,6 +295,11 @@ Response: { "agentId": "...", "assigned": ["kb-1", "kb-2"], "count": 2 }</code><
268
295
  <p>The agent process may have crashed. Check logs on the deployment platform. Common causes: missing environment variables, incorrect model configuration, or insufficient memory.</p>
269
296
  </div>
270
297
 
298
+ <div class="card">
299
+ <h3>Port is in use — can't save</h3>
300
+ <p>The port validation detected another process using the port. Either change the port number or stop the conflicting process. The error message shows the process name and PID — you can stop it with <code>kill &lt;PID&gt;</code> or choose a different port. Common conflicts: another agent already running on that port, or a development server.</p>
301
+ </div>
302
+
271
303
  <div class="card">
272
304
  <h3>Delete confirmation name doesn't match</h3>
273
305
  <p>The typed name must match exactly (case-insensitive) with the agent's display name. Check the agent name shown in the header.</p>